• 全局大喇叭——BroadcastReceiver



    title: 全局大喇叭——BroadcastReceiver
    tags:

    • Android
    • 消息通信
      categories: 移动开发
      abbrlink: 17555
      date: 2020-12-04 12:10:00

    本篇文章记述了Android的四大组件之一 —— BroadcastReceiver(广播接收者)。广播的作用、广播注册的方式、自定义广播、广播的类型以及在较新的Android系统中使用BroadcastReceiver需要注意的问题。Android 应用与 Android 系统和其他 Android 应用之间可以相互收发广播消息,这与发布-订阅设计模式相似,这些广播会在所关注的事件发生时发送出去。

    关于系统广播

    举例来说,Android 系统会在发生各种系统事件时发送广播,例如系统启动或设备开始充电时。应用可以发送自定义广播来通知其他应用它们可能感兴趣的事件(例如,一些新数据已下载)。应用可以注册接收特定的广播。广播发出后,系统会自动将广播传送给同意接收这种广播的应用。

    系统会在发生各种系统事件时自动发送广播,例如当系统进入和退出飞行模式时,系统广播会被发送给所有同意接收相关事件的应用,或者在手机低电量的时候,系统也会发出一个手机电量低的广播:

    常见的系统广播:

    <!-- 开机广播 -->
    <action android:name="android.intent.action.BOOT_COMPLETED"/>
    
    <!-- 开始充电广播 -->
    <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
    
    <!-- 低电量广播 -->
    <action android:name="android.intent.action.BATTERY_LOW"/>
    
    <!-- 应用卸载广播 -->
    <action android:name="android.intent.action.PACKAGE_REMOVED"/>
    
    <!-- 应用安装广播 -->
    <action android:name="android.intent.action.PACKAGE_ADDED"/>
    
    <!-- 声明这个数据类型才可以收到应用安装/卸载的广播 -->
    <data android:scheme="package"/>
    
    <... />
    

    系统广播的注册

    应用程序可以通过两种方式接收广播:通过清单文件声明的广播接收者和上下文注册的广播接收者。

    静态注册

    首先作为四大组件之一肯定是要在清单文件声明的,创建一个AppReceiver用来接收App安装和卸载的广播,此类要继承BroadcastReceiver:

    public class AppReceiver extends BroadcastReceiver {
        private static final String TAG = "AppReceiver";
        
        @Override
        public void onReceive(Context context, Intent intent) {
            // 接收广播
            if(intent != null){
                // 判断收到的是什么广播
                String action = intent.getAction();
                assert action != null;
                switch (action){
                    case Intent.ACTION_PACKAGE_REMOVED:
                        Log.i(TAG, "onReceive: ACTION_PACKAGE_REMOVED " + "应用被卸载");
                        break;
                    case Intent.ACTION_PACKAGE_ADDED:
                        Log.i(TAG, "onReceive: ACTION_PACKAGE_ADDED " + "应用被安装");
                        break;
                }
            }
        }
    }
    

    AndroidManifest.xml

    <!-- 静态注册广播接收者 -->
    <receiver android:name=".AppReceiver"
              android:enabled="true"
              android:exported="true">
        <intent-filter>
            <!-- 应用卸载广播 -->
            <action android:name="android.intent.action.PACKAGE_REMOVED"/>
            <!-- 应用安装广播 -->
            <action android:name="android.intent.action.PACKAGE_ADDED"/>
            <!-- 声明这个数据类型才可以收到应用安装/卸载的广播 -->
            <data android:scheme="package"/>
        </intent-filter>
    </receiver>
    

    在进行应用卸载和安装的时候,就可以收到对应的广播了:

    动态注册

    AppReceiver.java还是和上面的一样,但是现在不在AndroidManifest.xml中声明,而是使用动态注册的方式,下面的例子演示了如何在Activity创建的动态的注册广播接收者:

    MainActivity.java

    public class MainActivity extends AppCompatActivity {
    
        private BroadcastReceiver receiver;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // 新建一个广播接收器
            receiver = new AppReceiver();
            
            // 接收哪些广播
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addDataScheme("package");
            intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
            intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
    
            // 注册广播接收者
            registerReceiver(receiver, intentFilter);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            // 取消注册广播接收器(不取消会导致内存泄漏)
            if(receiver != null){
                unregisterReceiver(receiver);
            }
        }
    }
    

    不过动态注册广播接收者千万别忘记在Activity销毁的时候解除之前注册的广播接收者。

    静态注册需要在AndroidManifest.xml中声明,只要APP启动过一次,所静态注册的广播就会生效,无论当前的APP处于停止使用还是正在使用状态。只要相应的广播事件发生,系统就会遍历所有的清单文件,通知相应的广播接收者接收广播,然后调用广播接收者的onReceiver方法。

    动态注册动态注册方式依赖于所注册的组件,当APP关闭后,组件对象都不在了动态注册的代码都不存在了,所动态注册监听的Action自然就不再生效了。静态注册的广播传播速度要远远慢于动态注册的广播。

    如果即使用了动态注册,又使用了静态注册,那么动态动态注册的广播的优先级大于静态注册的广播。

    广播的生命周期

    1、BroadCastReceiver 的生命周期很短暂,当接收到广播的时候创建,当onReceive()方法结束后销毁

    2、正因为BroadCastReceiver的声明周期很短暂,所以不要在广播接收器中去创建子线程做耗时的操作,因为广播接受者被销毁后,这个子进程就会成为空进程,很容易被杀死

    3、因为BroadCastReceiver是运行在主线程的,所以不能直接在BroadCastReceiver中去做耗时的操作,否则就会出现ANR异常 ,耗时的较长的工作最好放到Service中去完成。这里不能使用子线程来解决 , 因为BroadcastReceiver的生命周期很短,子线程可能还没有结束BroadcastReceiver就先结束了,BroadcastReceiver一旦结束,此时BroadcastReceiver的所在进程很容易在系统需要内存时被优先杀死,因为它属于空进程 ( 没有任何活动组件的进程 ),如果它的宿主进程被杀死,那么正在工作的子线程也会被杀死,所以采用子线程来解决是不可靠的。

    自定义Broadcast

    MainActivity.java

    public class MainActivity extends AppCompatActivity {
    
        public static final String MY_ACTION = "cn.tim.action.MY_ACTION";
        public static final String MY_ACTION_EXTRA_KEY = "input_content";
        private EditText etContent;
        private CustomReceiver receiver;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            etContent = findViewById(R.id.et_content);
            TextView tvShow = findViewById(R.id.tv_show);
    
            receiver = new CustomReceiver();
            receiver.tvShow = tvShow;
    
            IntentFilter filter = new IntentFilter();
            filter.addAction(MY_ACTION);
            registerReceiver(receiver, filter);
        }
    
        public void send(View view) {
            // 新建广播
            Intent intent = new Intent(MY_ACTION);
            // 放入广播要携带的数据
            intent.putExtra(MY_ACTION_EXTRA_KEY, etContent.getText().toString());
            sendBroadcast(intent);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            // 解除注册
            if(receiver != null){
                unregisterReceiver(receiver);
            }
        }
    }
    

    CustomReceiver.java

    public class CustomReceiver extends BroadcastReceiver {
        TextView tvShow;
    
        @Override
        public void onReceive(Context context, Intent intent) {
            if(intent != null){
                String action = intent.getAction();
                if(MainActivity.MY_ACTION.equals(action)){
                    String inputContent = intent.getStringExtra(MainActivity.MY_ACTION_EXTRA_KEY);
                    tvShow.setText(inputContent);
                }
            }
        }
    }
    

    不同的应用间通信,也是上面同样的代码,只要写相同的ACTION,那么就OK了:

    Broadcast分类

    广播的发送,可以分为有序广播、无序广播、本地广播以及粘性广播。

    有序广播

    有序广播是一种分先后广播接收器的广播,广播接收者的优先级越高,越先接收广播。优先级高的广播先收到广播,收到广播后可以修改广播的内容,也可以拦截广播不让广播向下传递。

    如果在CReceiver中终止广播,那么优先级比较低的A与B都收不到广播了。

    MainActivity.java

    public class MainActivity extends AppCompatActivity {
        public static final String MY_ACTION = "cn.tim.action.MY_ACTION";
        public static final String KEY = "cn.tim.action.MY_ACTION";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // 新建广播
            Intent intent = new Intent(MY_ACTION);
            // 放入广播要携带的数据
            Bundle bundle = new Bundle();
            bundle.putInt(KEY, 100);
            intent.putExtras(bundle);
            //sendBroadcast(intent);
    
            // 发送顺序广播,参数二:权限
            sendOrderedBroadcast(intent, null);
        }
    }
    
    

    注意:如果要修改数据的话前提得是Intent发送数据得格式必须是Bundle。

    // 优先级 2
    public class CReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if(intent != null){
                String action = intent.getAction();
                if(MainActivity.MY_ACTION.equals(action)){
                    Bundle bundle = intent.getExtras();
                    Toast.makeText(context, "C:" + bundle.getInt(MainActivity.KEY), Toast.LENGTH_SHORT).show();
                    Bundle newBundle = new Bundle();
                    newBundle.putInt(MainActivity.KEY, 90);
                    setResultExtras(newBundle);
                }
            }
        }
    }
    
    // 优先级 1
    public class AReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if(intent != null){
                String action = intent.getAction();
                if(MainActivity.MY_ACTION.equals(action)){
                    Bundle bundle = getResultExtras(true);
                    Toast.makeText(context, "A:" + bundle.getInt(MainActivity.KEY), Toast.LENGTH_SHORT).show();
                    Bundle newBundle = new Bundle();
                    newBundle.putInt(MainActivity.KEY, 80);
                    setResultExtras(newBundle);
                }
            }
        }
    }
    
    // 优先级 0
    public class BReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if(intent != null){
                String action = intent.getAction();
                if(MainActivity.MY_ACTION.equals(action)){
                    Bundle bundle = getResultExtras(true);
                    Toast.makeText(context, "B:" + bundle.getInt(MainActivity.KEY), Toast.LENGTH_SHORT).show();
                }
            }
        }
    }
    

    无序广播

    无序广播指所有与之匹配的广播接收者都能收到广播,没有先后顺序,直到没有广播接收者接收广播为止才会停止广播的传递。

    默认情况下有广播发送时,系统会遍历全部APP的Receiver。如果想使得本APP的Receiver不被外界的广播所干扰,可以在Receiver节点添加android:exported="false"属性,这样系统遍历全部APP清单文件的广播接收者时不会对本App的Receiver进行判断及处理。

    本地广播

    本地广播仅仅在APP内传播,其他的程序无法收到这个广播。这种广播保证安全性,不会传播到外界。同时由于LocalBroadcastManager不需要用到跨进程机制,因此相对 BroadcastReceiver 而言要更为高效。LocalBroadcastManager只在动态广播时使用,静态广播不能使用LocalBroadcastManager。

    implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
    

    MainActivity.java

    public class MainActivity extends AppCompatActivity {
        public static final String MY_NEW_ACTION = "cn.tim.action.MY_NEW_ACTION";
        
        private LocalReceiver localReceiver;
        private LocalBroadcastManager localBroadcastManager;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // 获得LocalBroadcastManager对象
            localBroadcastManager = LocalBroadcastManager.getInstance(this);
            // 动态注册广播
            localReceiver = new LocalReceiver();
            IntentFilter filter = new IntentFilter();
            filter.addAction(MY_NEW_ACTION);
            localBroadcastManager.registerReceiver(localReceiver, filter);
        }
    
        public void sendLocalBroadcast(View view) {
            // 发送本地广播
            Intent intent = new Intent(MY_NEW_ACTION);
            localBroadcastManager.sendBroadcast(intent);
        }
    
    
        static class LocalReceiver extends BroadcastReceiver {
            @Override
            public void onReceive(Context context, Intent intent){
                Toast.makeText(context,"Received LocalBroadcast!",Toast.LENGTH_SHORT).show();
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            localBroadcastManager.unregisterReceiver(localReceiver);
        }
    }
    

    new SDK中使用广播

    随着 Android 平台的发展,它会不定期地更改系统广播的行为方式。如果要在Android 7.0(API 级别 24)或更高版本的SDK使用广播,必须注意以下更改:

    Android 7.0 API 24

    Android 7.0(API 级别 24)及更高版本不发送以下系统广播:

    ACTION_NEW_PICTURE
    ACTION_NEW_VIDEO
    

    此外,以 Android 7.0 及更高版本为目标平台的应用必须使用动态注册的方式,无法在清单中声明广播接收器,所以以后所有广播直接全部使用动态注册的方式吧,如果非要用静态注册的方式,可以发送显式广播,(即指定指定包名再发送,但是我们有时不知道有谁要接收广播,所以显式广播用的比较少),这个时候可以在发送广播的时候携带intent.addFlags(0x01000000); 就能让广播突破隐式广播限制,但是依旧不建议静态注册,还是动态注册比较好。

    Android 9.0 API 28

    从 Android 9(API 级别 28)开始,NETWORK_STATE_CHANGED_ACTION广播不再接收有关用户位置或个人身份数据的信息。通过 WLAN 接收的系统广播不包含 SSID、BSSID连接信息或扫描结果,如果要获取这些信息,可以调用 WifiManager.getConnectionInfo()。

    关于广播的文档可以看这里:《Receiving broadcasts》

  • 相关阅读:
    HTML5 drag拖动事件
    echarts 实现立体柱子图
    团队管理(七)
    echarts环比图实现
    父组件调用图表组件根据按钮切换展示数据
    echarts 折柱图绘制图表标注
    团队管理(六)
    团队管理(五)
    css 绘制圆角三角形
    团队管理(四)
  • 原文地址:https://www.cnblogs.com/timdevelop/p/14086014.html
Copyright © 2020-2023  润新知