频道栏目
首页 > 资讯 > Android > 正文

Android四大组件开发教程之BroadCastReceiver介绍

17-11-24        来源:[db:作者]  
收藏   我要投稿

1、广播

1、定义

BroadCastReceiver 意为广播接收者。

Android应用程序可以发送或接收来自Android系统和其他Android应用程序的广播消息。这些广播是在感兴趣的事件发生时发送的。例如,Android系统在发生各种系统事件时发送广播,例如当系统启动或设备开始充电时。应用程序也可以发送自定义广播,例如,通知其他应用程序他们可能感兴趣的东西(例如,一些新的数据已被下载)。

应用程序可以注册以接收特定的广播。当广播被发送时,系统自动将广播路由到已经预订接收该特定类型的广播的应用。

一般来说,广播可以用作应用程序之间和正常用户流程之外的消息传递系统。但是,必须小心,不要滥用机会回应广播并在后台运行作业,这可能会导致系统性能降低。

2、作用

用于监听 / 接收 应用发出的广播消息,并做出响应。

应用场景:

同一 app 内部的同一组件内的消息通信(单个或多个线程之间)。 同一 app 内部的不同组件之间的消息通信(单个进程)。 同一 app 具有多个进程的不同组件之间的消息通信。 不同 app 之间的组件之间消息通信。 Android 系统在特定情况下与 app 之间的消息通信,如当电话呼入时、网络可用时

3、工作原理

Android中的广播使用了设计模式中的观察者模式:基于消息的发布/订阅事件模型。

因此,Android将广播的发送者 和 接收者 解耦,使得系统方便集成,更易扩展
模型中有3个角色:

消息订阅者(广播接收者) 消息发布者(广播发布者) 消息中心(AMS,即Activity Manager Service)

Android广播分为两个方面:广播发送者和广播接收者,通常情况下,BroadcastReceiver指的就是广播接收者(广播接收器)。

原理描述:

广播接收者 通过 Binder机制在 AMS 注册 广播发送者 通过 Binder 机制向 AMS 发送广播 AMS 根据 广播发送者 要求,在已注册列表中,寻找合适的广播接收者 寻找依据:IntentFilter 或 Permission AMS将广播发送到合适的广播接收者相应的消息循环队列中 广播接收者通过 消息循环 拿到此广播,并回调 onReceive()

注意:广播发送者 和 广播接收者的执行 是 异步 的,发出去的广播不会关心有无接收者接收,也不确定接收者到底是何时才能接收到。

上图就是广播从发送到接收的基本流程,我们关于组件 BroadCastReceiver 的使用在开发者完成的这部分。

2、自定义广播接收者

BroadCastReceiver 是一个抽象类,所以我们要使用它,就要使用继承自 BroadcastReceivre 的类,并且必须复写 onReceive() 方法。

广播接收者接收到相应广播后,会自动回调 onReceive() 方法。 一般情况下,onReceive方法会涉及与其他组件之间的交互,如发送 Notification、启动 service 等。 默认情况下,广播接收器运行在UI线程,因此,onReceive方法不能执行耗时操作,否则将导致 ANR。
public class MyBroadCastReceiver extends BroadcastReceiver {

    private static final String TAG = "TAG";

    @Override
    public void onReceive(Context context, Intent intent) {
        StringBuilder sb = new StringBuilder();
        sb.append("Action: " + intent.getAction() + "\n");
        sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
        String log = sb.toString();
        Log.d(TAG, log);
        Toast.makeText(context, log, Toast.LENGTH_LONG).show();
    }
}

广播消息本身包装在一个 Intent 对象中,该对象的操作字符串标识表示了发生的事件(例如 android.intent.action.AIRPLANE_MODE)。intent 还可能包括额外的信息指定到额外的领域。例如,飞行模式 intent 包括一个 boolean 来指示飞行模式是否开启。

所以我们可以通过 onReceive() 的 intent 记录并显示广播的内容。

3、注册接收者

写好了一个 BroadCastReceiver 类,但我们不能立刻使用它,广播接收者就像 Activity 和 Service 一样必须被注册后才能使用。

注册的方式分为两种:静态注册、动态注册,也称为清单声明和上下注册。

1、Manifest-declared

如果在清单中声明广播接收器,系统会在发送广播时启动我们的应用程序(如果应用程序尚未运行)。

在AndroidManifest.xml里通过标签声明,它有如下属性:

name:继承BroadcastReceiver子类的类名。

enabled:用于定义系统是否能够实例化这个广播接收器,如果设置为true,则能够实例化,如果设置为false,则不能被实例化。默认值是true。标签有它自己的 enabled 属性,这个属性会应用给应用程序的所有组件,包括广播接收器。的这个属性都必须是 true,这个广播接收器才能够被启用。如果有一个被设置为false,该广播接收器会被禁止实例化。

exported:这个接收者能否接收其它 app 发出的广播。默认值是由receiver 中有无 intent-filter 决定的。如果有 intent-filter,默认值为 true,否则为 false。

icon:这个属性定义了一个代表广播接收器的图标,这个属性必须用包含图片定义的可绘制资源来设定。如果没有设置这个属性,会是应用的 icon 属性值来代替。无论是这个属性还是的 icon 属性,它们设置的图标也是所有的接收器的 Intent 过滤器的默认图标。

label:给广播接收器设定一个用户可读的懂的文本标签。如果这个属性没有设置,那么就会使用的 label 属性值来代替。

permission:具有相应权限的广播发送者发送的广播才能被这个 BroadcastReceiver 所接收。如果没有设置这个属性,那么的permission属性所设置的权限就适用于这个广播接收器。如果也没有设置权限,那么该接收器就不受权限的保护。

process:BroadCastReceiver 运行所处的进程,默认为 app 的进程,可以指定独立的进程。的 process 属性能够给它的所有组件设置一个不同的默认进程,但是它的每个组件自己的 process 属性能够覆盖这个默认设置,这样就允许把一个应用程序分离到多个进程中。
如果这个属性值用“:”开头,则在需要的时候系统会创建一个新的、应用程序私有的进程,并且该广播接收器也会运行在这个进程中。如果这个属性值用小写字母开头,那么接收器就会运行在以这个属性值命名的全局进程中,它提供使其工作的权限,这样就允许不同的应用程序组件来共享这个进程。

intent-filter:用于指定此广播接收器将接收的广播类型。


    
        
        
    

这样就完成清单注册了,在 intent-filter 中的 action 代表着这个广播接收者可以接收到同样注册这一 action 的广播。

public class ReceiverActivity extends AppCompatActivity {

    private Button mSend;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.receiver_layout);

        mSend = (Button) findViewById(R.id.btn_send);
        mSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                send();
            }
        });
    }
    private void send() {
        Intent intent = new Intent("android.intent.action.MY_BROADCAST");
        intent.putExtra("msg", "Hello BroadcastReceiver");
        sendBroadcast(intent);
    }
}

系统包管理器在手机安装应用程序时注册接收器,接收器就成为一个单独的入口点到这个应用程序。这意味着,如果该应用程序当前不在运行,系统可以启动应用程序,并提供广播。

系统创建一个新的 BroadcastReceiver 组件对象来处理它接收到的每个广播。此对象仅在调用 onReceive(Context, Intent) 期间有效。一旦你的代码从这个方法返回,系统就认为这个组件不再处于活动状态。

2、Context-registered

在代码中通过调用 Context 的 registerReceiver() 方法进行动态注册 BroadcastReceiver。

public class ReceiverActivity extends AppCompatActivity {

    private Button mSend;
    private MyBroadcastReceiver receiver;

    @Override
    protected void onResume() {
        super.onResume();
        receiver = new MyBroadcastReceiver();

        IntentFilter filter = new IntentFilter();
        filter.addAction("android.intent.action.MY_BROADCAST");

        this.registerReceiver(receiver, filter);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.receiver_layout);

        mSend = (Button) findViewById(R.id.btn_send);
        mSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                send();
            }
        });
    }
    private void send() {
        Intent intent = new Intent("android.intent.action.MY_BROADCAST");
        intent.putExtra("msg", "Hello BroadcastReceiver");
        sendBroadcast(intent);
    }

    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(receiver);
    }
}

首先要得到一个 BroadcastReceiver 的实例,通过这个实例来对广播接收者进行注册,发送广播和注销。

动态广播最好在 Activity 的 onResume() 中注册、onPause() 中注销。

不在 onCreate() & onDestory() 或 onStart() & onStop()注册、注销是因为:

当系统因为内存不足(优先级更高的应用需要内存)要回收 Activity 占用的资源时,Activity 在执行完 onPause() 方法后就会被销毁,有些生命周期方法 onStop(),onDestory() 就不会执行。当再回到此 Activity 时,是从 onCreate() 开始执行。

假设我们将广播的注销放在 onStop(),onDestory() 里的话,有可能在 Activity 被销毁后还未执行 onStop(),onDestory(),即广播仍还未注销,从而导致内存泄露。

但是,onPause() 一定会被执行,从而保证了广播在 App 死亡前一定会被注销,从而防止内存泄露。

3、注册方式的不同

4、权限处理

在注册广播接收者的时候,也可以给它加上我们想给的权限,那么就只有同样具有这种权限的广播才会被其接收。




标签是用来定义我们自己的权限,然后再用就可以在程序中使用了。

清单注册只需加上 permission 这个属性即可。

上下文注册,我们需要用:

registerReceiver(receiver, filter, broadcastPermission, scheduler)

sendBroadcast(intent, receiverPermission)
this.registerReceiver(receiver, filter, "ht.MY_BROADCAST", null);

sendBroadcast(intent, "ht.MY_BROADCAST");

这样就可以发送和接收到带有权限的广播了。

5、广播类型

广播的类型主要分为4类:

普通广播(Normal Broadcast) 系统广播(System Broadcast) 有序广播(Ordered Broadcast) App应用内广播(Local Broadcast)

1、普通广播

即开发者自身定义intent的广播(最常用)。以未定义的顺序向所有接收者发送广播。这被称为普通广播。这样更有效率,但意味着接收者不能从其他接收者读取结果,传播从广播接收到的数据,或者中止广播。

普通广播是一种完全异步执行的广播,在广播发出之后,所有的广播接收者几乎都会在同一时刻接收到这条广播消息。效率较高,但无法被截断。

2、系统广播

Android中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播。

在如今的系统中,已经不允许我们发送系统广播,所以当要使用系统广播时,只需要在注册广播接收者时定义相关的action即可,并不需要手动发送广播,当系统有相关操作时会自动进行系统广播。

每个广播都有特定的Intent - Filter(包括具体的action),Android系统广播的 action 大家可以在这里查到 。

3、有序广播

发送出去的广播被广播接收者按照先后顺序接收,有序是针对广播接收者而言的。

广播接受者接收广播的顺序规则(同时面向静态和动态注册的广播接受者):

按照 Priority 属性值从大到小排序,大的先接收 Priority 属性相同者,动态注册的广播优先

特点:

接收广播按顺序接收 先接收的广播接收者可以对广播进行截断,即后接收的广播接收者不再接收到此广播 先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播

有序广播的使用过程与普通广播非常类似,差异仅在于广播的发送方式:

sendOrderedBroadcast(intent);

这是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收者能够收到这条广播消息,当这个广播接收者中的逻辑执行完毕后,广播才会继续传递。所以前面的广播接收者还可以截断正在传递的广播。

4、应用内广播

Android中的广播可以跨App直接通信(exported 对于有 intent-filter 情况下默认值为 true),这就会引发一些可能的问题:

其它 App 针对性发出与当前 App 的 intent-filter 相匹配的广播,由此导致当前 App 不断接收广播并处理; 其它 App 注册与当前 App 一致的 intent-filter 用于接收广播,获取广播具体信息,即会出现安全性 & 效率性的问题。

而使用应用内广播可以解决这些问题,应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个 App。相比于全局广播(普通广播),App应用内广播优势体现在:安全性高 & 效率高。

1、将全局广播设置成局部广播

注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收; 在广播发送和接收时,增设相应权限permission,用于权限验证;

发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。

通过intent.setPackage(packageName)指定报名

2、LocalBroadcastManager

使用方式上与全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将参数的 context 变成了 LocalBroadcastManager 的单一实例。

首先通过 LocalBroadcastManager 的 getInstance() 方法获得实例,用这个对象去做处理:

private LocalBroadcastManager manager;
manager = LocalBroadcastManager.getInstance(this);

manager.registerReceiver(receiver, filter);
manager.sendBroadcast(intent);
manager.unregisterReceiver(receiver);

注:对于 LocalBroadcastManager 方式发送的应用内广播,只能通过 LocalBroadcastManager 动态注册,不能静态注册。

6、onReceive与context

对于不同注册方式的广播接收者回调方法 onReceive(Context context,Intent intent) 中的 context 值是不一样的:

对于静态注册(全局+应用内广播),onReceive(context, intent) 中的 context 返回值是 ReceiverRestrictedContext。

对于全局广播的动态注册,回调 onReceive(context, intent) 中的 context 返回值是 Activity Context。

对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context。

对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context;

在简书中有一篇不错的文章,本文摘抄了其中的许多内容,有兴趣的朋友可以看看。

7、进程影响

我们 BroadcastReceiver 的状态(无论是否在运行)影响其包含进程的状态,这反过来可能影响系统被杀的可能性。例如,当一个进程执行一个接收者(也就是说,当前在其 onReceive() 方法中运行代码)时,它被认为是一个前台进程。系统保持进程运行,除非存在极大的内存压力。

但是,一旦我们从 onReceive() 返回,BroadcastReceiver 不再处于活动状态。接收者的主机进程就会变得与在其中运行的其他应用程序组件一样程度的重要。如果该进程仅托管清单声明的接收方(对于用户从来没有或最近没有进行过交互的应用程序的常见情况),则在返回 onReceive() 时,系统将其进程视为低优先级进程,并可能将其终止为其他更重要的流程提供资源。

一个BroadcastReceiver 对象只有在被调用onReceive(Context, Intent)的才有效的,当从该函数返回后,该对象就是无效的了,结束了生命周期。

因此,我们不应该从广播接收器开始长时间的后台运行线程。onReceive() 之后,系统可以随时终止进程来回收内存,这样做会终止在进程中运行的衍生线程。为避免这种情况,我们应该调用 goAsync()(如果希望有更多时间在后台线程中处理广播)。

这个方法可以在应用程序的 onReceive(Context, Intent) 方法中调用,允许函数返回后仍保持广播活跃 。这并不改变期望的广播相对响应时间(10 s完成),却允许实现活动相关工作在另一个线程,以避免故障。

下面的代码片段显示了一个 BroadcastReceiver 使用 goAsync() 到它需要更多的时间来完成后 onReceive() 完成。如果你想完成的工作 onReceive() 足够长,导致 UI 线程错过一帧(> 16ms),这使得它更适合于后台线程,这是特别有用的。

public class MyBroadCastReceiver extends BroadcastReceiver {

    private static final String TAG = "TAG";

    @Override
    public void onReceive(Context context, final Intent intent) {

        final PendingResult pendingResult = goAsync();
        AsyncTask asyncTask = new AsyncTask() {
            @Override
            protected String doInBackground(String... params) {
                StringBuilder sb = new StringBuilder();
                sb.append("Action: " + intent.getAction() + "\n");
                sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
                String log = sb.toString();
                Log.d(TAG, log);
                // Must call finish() so the BroadcastReceiver can be recycled.
                pendingResult.finish();
                return log;
            }
        };
        asyncTask.execute();
    }
}

我们用 PendingResult 说明使用异步处理,不在 UI 线程中完成任务。最后需要 finish 掉这个 PendingResult,使 BroadcastReceiver 可以被回收。

8、特别注意

如果不需要将广播发送到应用程序之外的组件,则我们可以使用 LocalBroadcastManager 来进行广播发送和接收本地广播。LocalBroadcastManager 效率要高得多(不需要进程间通信),并可以让我们避免考虑其他应用程序能够接收或发送我们的广播的安全问题。

如果很多应用程序都在清单中注册接收相同的广播的广播接收者,则可能会导致系统启动大量应用程序,从而对设备性能和用户体验产生重大影响。为了避免这种情况,更喜欢使用上下文注册而不是清单。有时,Android系统强制使用上下文注册的接收器。例如,CONNECTIVITY_ACTION 广播只传送给上下文注册的接收者。

不要使用隐式 Intent 来传播敏感信息。这些信息可以被任何注册接收广播的应用读取。有三种方法可以控制谁可以接收我们的广播:

可以在发送广播时指定权限。

在Android 4.0及更高版本,可以指定一个 包与 setPackage(String)发送广播时。系统将广播限制为与包匹配的一组应用程序。

可以发送应用内广播 LocalBroadcastManager。

当我们注册接收器时,任何应用程序都可能将恶意广播发送到应用程序的接收器。有三种方法可以限制我们的应用收到的广播:

可以在注册广播接收机时指定权限。

对于清单声明的接收者,可以在清单中将 android:exported 属性设置为“false”。接收器不接收来自应用程序之外的来源的广播。

你可以限制自己只有本地广播LocalBroadcastManager。

广播操作的命名空间是全局的。确保动作名称和其他字符串是用我们自己的名称空间编写的,否则可能会与其他应用程序无意中冲突。

因为接收者的 onReceive(Context, Intent) 方法在主线程上运行,所以应该快速执行并返回。如果需要执行长时间运行的工作,要认真考虑线程或启动后台服务,因为系统可能会在 onReceive() 返回后终止整个进程。建议调用 goAsync() 接收者的 onReceive() 方法并将其传递 BroadcastReceiver.PendingResult 给后台线程。这从广播返回后保持广播活动onReceive()。但是,即使采用这种方法,系统也会很快(10秒以内)完成广播。它允许你将工作移动到另一个线程,以避免干扰主线程。

不要从广播接收者开始 Activity,因为给用户太大震撼,特别是如果有多个接收器的话。这时,我们应该考虑显示通知。

相关TAG标签
上一篇:ASP.NET Web Forms - Repeater 控件
下一篇:ASP.NET Web Forms - TextBox 控件
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站