美文网首页Android基础知识毕业设计
Android中的广播使用小结

Android中的广播使用小结

作者: 曹丰斌 | 来源:发表于2016-12-22 23:52 被阅读4800次

    1.广播的分类

    (1)按照发送的方式分类

    • 标准广播
      是一种异步的方式来进行传播的,广播发出去之后,所有的广播接收者几乎是同一时间收到消息的。他们之间没有先后顺序可言,而且这种广播是没法被截断的。
    • 有序广播
      是一种同步执行的广播,在广播发出去之后,同一时刻只有一个广播接收器可以收到消息。当广播中的逻辑执行完成后,广播才会继续传播。

    (2)按照注册的方式分类

    • 动态注册广播
      顾名思义,就是在代码中注册的。
    • 静态注册广播
      动态注册要求程序必须在运行时才能进行,有一定的局限性,如果我们需要在程序还没启动的时候就可以接收到注册的广播,就需要静态注册了。主要是在AndroidManifest中进行注册。

    (3)按照定义的方式分类

    • 系统广播
      Android系统中内置了多个系统广播,每个系统广播都具有特定的intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的BroadcastReceiver接收。系统广播在系统内部当特定事件发生时,由系统自动发出。
    • 自定义广播
      由应用程序开发者自己定义的广播

    2.动态注册广播的实现

    一段比较典型的实现代码为:

    (1)实现一个广播接收器

    public class MyBroadcastReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
            abortBroadcast();
        }
    }
    

    主要就是继承一个BroadcastReceiver,实现onReceive方法,在其中实现自己的业务逻辑就可以了。

    (2)注册广播

    public class MainActivity extends AppCompatActivity {
    
        private IntentFilter intentFilter;
        private MyBroadcastReceiver myBroadcastReceiver;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            intentFilter = new IntentFilter();
            intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
            myBroadcastReceiver = new MyBroadcastReceiver();
            registerReceiver(myBroadcastReceiver, intentFilter);
            
            Button button = (Button) findViewById(R.id.button);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent("android.net.conn.CONNECTIVITY_CHANGE");
                    sendBroadcast(intent); // 发送广播
                }
            });
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unregisterReceiver(myBroadcastReceiver);
        }
    }
    
    

    这样MyBroadcastReceiver就可以收到相应的广播消息了。


    3.静态注册广播的实现

    还是用上面的按个MyBroadcastReceiver,只不过这次采用静态注册的方式
    在manifest文件中增加如下的代码:

            <receiver
                android:name=".MyBroadcastReceiver"
                android:enabled="true"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
                </intent-filter>
            </receiver>
    

    几个相关解释:

    • android:exported
      此BroadcastReceiver能否接收其他App发出的广播(其默认值是由receiver中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false);
    • android:name
      此broadcastReceiver类名;
    • android:permission
      如果设置,具有相应权限的广播发送方发送的广播才能被此broadcastReceiver所接收;
    • android:process
      broadcastReceiver运行所处的进程。默认为App的进程。可以指定独立的进程(Android四大组件都可以通过此属性指定自己的独立进程);

    4.静态注册广播与动态注册广播的区别

    • 静态注册即使App退出,仍然能接收到广播
    • 动态注册时,当Activity退出,就接收不到广播了
    • 但是静态注册即使App退出,仍然能接收到广播这种说法自Android 3.1开始有可能不再成立

    说明:
    Android 3.1开始系统在Intent与广播相关的flag增加了参数:
    A. FLAG_INCLUDE_STOPPED_PACKAGES:包含已经停止的包(停止:即包所在的进程已经退出)
    B. FLAG_EXCLUDE_STOPPED_PACKAGES:不包含已经停止的包
    自Android3.1开始,系统本身增加了对所有App当前是否处于运行状态的跟踪。在发送广播时,不管是什么广播类型,系统默认直接增加了值为FLAG_EXCLUDE_STOPPED_PACKAGES的flag,导致即使是静态注册的广播接收器,对于其所在进程已经退出的App,同样无法接收到广播。
    因此对于系统广播,由于是系统内部直接发出的,无法更改此intent的flag值,因此,从3.1开始对于静态注册的接收系统广播的BroadcastReceiver,如果App进程已经退出,将不能接收到广播
    但是对于自定义的广播,可以通过覆写此flag为FLAG_INCLUDE_STOPPED_PACKAGES,使得静态注册的BroadcastReceiver,即使所在App进程已经退出,也能接收到广播,并会启动应用进程,但此时的BroadcastReceiver是新建的。
    实现代码为:

    Intent intent = new Intent();
    intent.setAction(BROADCAST_ACTION);
    intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
    sendBroadcast(intent);
    

    在3.1以前,不少App可能通过静态注册方式监听各种系统广播,以此进行一些业务上的处理(如即使App已经退出,仍然能接收到,可以启动service等..),3.1后,静态注册接受广播方式的改变,将直接导致此类方案不再可行。于是,通过将Service与App本身设置成不同的进程已经成为实现此类需求的可行替代方案

    API变化详情参见Android官方文档


    5.有序广播

    前面介绍过,有序广播是异步方式传播的。指的是发送出去的广播被BroadcastReceiver按照先后循序接收。有序广播的定义过程与普通广播无异,只是其的主要发送方式变为:

        /**
         * Broadcast the given intent to all interested BroadcastReceivers, delivering
         * them one at a time to allow more preferred receivers to consume the
         * broadcast before it is delivered to less preferred receivers.  This
         * call is asynchronous; it returns immediately, and you will continue
         * executing while the receivers are run.
         * @param intent The Intent to broadcast; all receivers matching this
         *               Intent will receive the broadcast.
         * @param receiverPermission (optional) String naming a permissions that
         *               a receiver must hold in order to receive your broadcast.
         *               If null, no permission is required.
         */
        public abstract void sendOrderedBroadcast(Intent intent,
                @Nullable String receiverPermission);
    
    

    其他的几种重载的方法可以参见官方文档。
    有序广播的主要特点:

    • 同级别接收是随机的(结合下一条)
    • 同级别动态注册(代码中注册)高于静态注册(AndroidManifest中注册)
    • 排序规则为:将当前系统中所有有效的动态注册和静态注册的BroadcastReceiver按照priority属性值从大到小排序
    • 先接收的BroadcastReceiver可以对此有序广播进行截断,使后面的BroadcastReceiver不再接收到此广播,也可以对广播进行修改,使后面的BroadcastReceiver接收到广播后解析得到错误的参数值。当然,一般情况下,不建议对有序广播进行此类操作,尤其是针对系统中的有序广播。实现截断的代码为:
    abortBroadcast();
    

    6.标准广播

    标准广播的主要特点为:

    • 同级别接收先后是随机的(无序的)
    • 级别低的后接收到广播
    • 接收器不能截断广播的继续传播,也不能处理广播
    • 同级别动态注册(代码中注册)高于静态注册(AndroidManifest中注册)

    7.广播的安全性问题

    由前文阐述可知,Android中的广播可以跨进程甚至跨App直接通信,且exported属性在有intent-filter的情况下默认值是true,由此将可能出现的安全隐患如下:

    • 其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理;
    • 其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播具体信息。

    无论哪种情形,这些安全隐患都确实是存在的。由此,业界常见的一些增加安全性的方案包括:

    • 对于同一App内部发送和接收广播,将exported属性人为设置成false,使得非本App内部发出的此广播不被接收;
    • 在广播发送和接收时,都增加上相应的permission,用于权限验证;
    • 发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
    • 采用LocalBroadcastManager的方式

    下文分析几种常见的处理方案。


    8.本地广播LocalBroadcastManager

    (1)LocalBroadcastManager的概念

    为了解决安全性问题,Android在android.support.v4.content包中引入了LocalBroadcastManager。按照官方文档的描述,使用LocalBroadcastManager有如下的优势:

    Helper to register for and send broadcasts of Intents to local objects within your process. This has a number of advantages over sending global broadcasts with sendBroadcast(Intent):
    (1)You know that the data you are broadcasting won't leave your app, so don't need to worry about leaking private data.
    (2)It is not possible for other applications to send these broadcasts to your app, so you don't need to worry about having security holes they can exploit.
    (3)It is more efficient than sending a global broadcast through the system.
    

    也就是说,使用该机制发出的广播只能够在应用程序内部进行传递,并且广播接收器也只能接收来自本地应用程序发出的广播,这样所有的安全性问题都不存在了。

    (2)LocalBroadcastManager使用范例

    public class MainActivity extends AppCompatActivity {
        private IntentFilter intentFilter;
        private LocalReceiver localReceiver;
        private LocalBroadcastManager localBroadcastManager;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            localBroadcastManager = LocalBroadcastManager.getInstance(this); // 获取实例
            Button button = (Button) findViewById(R.id.button);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
                    localBroadcastManager.sendBroadcast(intent); // 发送本地广播
                }
            });
            intentFilter = new IntentFilter();
            intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
            localReceiver = new LocalReceiver();
            localBroadcastManager.registerReceiver(localReceiver, intentFilter); // 注册本地广播监听器
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            localBroadcastManager.unregisterReceiver(localReceiver);
        }
    
        class LocalReceiver extends BroadcastReceiver {
            @Override
            public void onReceive(Context context, Intent intent) {
                Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
            }
        }
    }
    

    LocalBroadcastManager的官方文档链接


    9.自定义广播权限

    (1)一个自定义权限的示例

       <permission
            android:name="com.self.define.permission.test"
            android:label="BroadcastReceiverPermission"
            android:protectionLevel="signature">
        </permission>
    

    在自定义权限时,通常会指定protectionLevel属性,常用的如下:

    • normal:默认的,应用安装前,用户可以看到相应的权限,但无需用户主动授权。
    • dangerous:normal安全级别控制以外的任何危险操作。需要dangerous级别权限时,Android会明确要求用户进行授权。常见的如:网络使用权限,相机使用权限及联系人信息使用权限等。
    • signature:它要求权限声明应用和权限使用应用使用相同的keystore进行签名。如果使用同一keystore,则该权限由系统授予,否则系统会拒绝。并且权限授予时,不会通知用户。它常用于应用内部。把protectionLevel声明为signature。如果别的应用使用的不是同一个签名文件,就没办法使用该权限,从而保护了自己的接收者

    (2)广播接收者

    如果采用静态注册的方式:

             <receiver
                android:name=".common.MyBroadcastReceiver"
                android:exported="false"
                android:permission="com.self.define.permission.test">
                <intent-filter>
                    <action android:name="action.name"/>
                </intent-filter>
            </receiver>
    

    如果采用动态注册的方式
    相应的API有:

    (1)registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)
    Register to receive intent broadcasts, to run in the context of scheduler.
    (2)sendBroadcast(Intent intent, String receiverPermission)
    Broadcast the given intent to all interested BroadcastReceivers, allowing an optional required permission to be enforced.
    (3)sendOrderedBroadcast(Intent intent, String receiverPermission)
    Broadcast the given intent to all interested BroadcastReceivers, delivering them one at a time to allow more preferred receivers to consume the broadcast before it is delivered to less preferred receivers.
    

    receiver是动态注册时,需要创建自己的使用权限,并且将protectionLevel设置为signature。这样,当别的应用和receiver所在的应用使用的签名不一样时,便不会启动该receiver。例如:

    MyBroadcastReceiver receiver = new MyBroadcastReceiver();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("com.example.broadcast.test");
    intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
    //注册receiver时,直接指定发送者应该具有的权限。不然外部应用依旧可以触及到receiver
    registerReceiver(receiver, intentFilter, "com.self.define.permission.test", null);
    

    在注册的时候,最关键的一点是用registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)进行注册,而不是平常用的是registerReceiver(BroadcastReceiver, IntentFilter)。相较于后者,前者在注册的时候要求了发送者必须具有的权限。如果发送者没有该权限,那么发送者发送的广播即使经过IntentFilter的过滤,也不会被receiver接收。此时如果再自定义一个权限,并且将权限的protectionLevel设置为signature,那么外部应用便无法使用该权限,也就无法触及到该receiver。
    发送广播的代码为:

    Intent intent = new Intent("com.example.broadcast.test.permission");
    sendBroadcast(intent,"com.self.define.permission.test");
    

    另外需要在manifest文件中定义权限并声明

    <permission
        android:name="com.self.define.permission.test"
        android:label="BroadcastReceiverPermission"
        android:protectionLevel="signature">
    </permission>
    <uses-permission android:name="com.self.define.permission.test"/>
    

    切记要在<application>同级的位置配置使用到的权限


    10.其他几点补充

    (1)不同注册方式onReceive(context, intent)中的context具体类型

    两个参数的官方定义为:

    Context: The Context in which the receiver is running.
    Intent: The Intent being received.
    

    BroadcastReceiver本身不是Context,其内部也不含有Context,但在onReceive(Context context, Intent intent)中有context参数。这个context随着receiver的注册方式的不同而不同:
    静态注册:context为ReceiverRestrictedContext
    动态注册:context为Activity的context
    LocalBroadcastManager的动态注册:context为Application的context

    (2)ANR问题

    官方文档的描述:
    When it runs on the main thread you should never perform long-running operations in it (there is a timeout of 10 seconds that the system allows before considering the receiver to be blocked and a candidate to be killed). You cannot launch a popup dialog in your implementation of onReceive().


    参考

    BroadcastReceiver文档链接
    四大组件之广播(Broadcast)
    BroadcastReceiver与自定义权限
    Android总结篇系列:Android广播机制
    面试题-BroadcastReceiver

    相关文章

      网友评论

        本文标题:Android中的广播使用小结

        本文链接:https://www.haomeiwen.com/subject/shscvttx.html