Android 面试题之 BroadcastReceiver 使用+实例

简介: Broadcast Receiver是什么Broadcast Receiver使用场景Broadcast Receiver的种类按发送顺序按发送范围按Broadcast Receiver的实现Broadcast Receiver的使用4.1静态注册4.2动态广播4.2.1自定义广播接受者,4.2.2注册广播4.2.3发送广播4.2.4注销广播4.3无序广播4.4有序广播Broadcast Receiver实现原理LocalBroadcastManager特点Broadcast Receiver注意事项实例:BroadcastReceiverDemo.zip

Broadcast Receiver是什么


       Broadcast Receiver是Android四大组件之一,是一种广泛运用在应用程序之间传输信息的机制,通过发送Intent来传送我们的数据。

微信图片_20220520135345.png

Broadcast Receiver使用场景


  • 应用内多个不同组件之间的消息通信。
  • 跨应用组件之间的消息通信。


Broadcast Receiver的种类


微信图片_20220520135454.png


按发送顺序


  • 标准广播:也叫无序广播, 其实也就是无序广播,一起发送给所有人完全异步执行的广播,所有广播接收器几乎同时收到,效率高,无法截断。


  • 有序广播:同步执行的广播,按优先级顺序发送,同一时间只有一个广播接收,前面的广播可以截断后面的。


按发送范围


  • 全局广播:发出的广播可以被其他任何的任何应用程序接收到,并且我们也可以接收来自于其他任何应用程序的广播,数据不安全。


  • 本地广播:只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播,其他软件不会监听到我们的广播数据。


按Broadcast Receiver的实现


  • 静态注册:在AndroidManifest.xml中通过receive标签进行申明。该方式注册的广播为常驻广播,注册后,即使应用处于非运行状态,也可以接收到广播,例如很常用的开机启动监听。


  • 动态注册:在代码中注册,必须启动软件后才能接收到广播;此外,动态注册的广播接收器一定都要取消注册。跟随Activity的生命周期


Broadcast Receiver的使用


微信图片_20220520140139.png


广播的使用包括以下几个步骤:


  • 自定义广播接收者:重写onReceiver()

  • 注册广播接收者到消息中心(AMS):动态(代码中registerReceiver)或者静态注册(AndroidManifest.xml中申明)

  • 定义及发送广播到消息中心(AMS):Context.sendBroadcast()、Context.sendOrderedBroadcast;


  • AMS选择并发送广播给合适的广播接受者(根据Intent-filter,Permission)


  • 广播接受者通过消息循环获取广播,并调用onReceive()进行处理


「以上1~3步骤需要开发者完成,4~5步骤由Android系统完成。」

「样例:」


4.1静态注册


4.1.1自定义广播接受者,继承BroadcastReceiver基类并重写onReceive()即可:


public class SCCReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.e(getClass().getName(),"SCCReceiver,传递内容:"+intent.getStringExtra("scc"));
    }
}


4.1.2在AndroidManifest.xml中通过receive标签进行申明


<receiver
    android:name=".SCCReceiver"
    android:enabled="true"
    android:exported="true"/>


  • 4.1.3发送广播


  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical">
    <Button
        android:id="@+id/btn_send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="发送静态广播" />
</LinearLayout>


MainActivity.java


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_send).setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_send:
                Log.e(getClass().getName(),"点击R.id.btn_send");
                Intent intent = new Intent(this, SCCReceiver.class);
                intent.putExtra("scc", "真的帅");
                sendBroadcast(intent);
                break;
        }
    }
}


  • 点击发送静态广播,实现效果如下


微信图片_20220520140530.png


4.2动态广播


4.2.1自定义广播接受者,


  • 继承BroadcastReceiver基类并重写onReceive()即可:


public class SCCReceiver2 extends BroadcastReceiver {
    public static final String ACTION = "com.scc.broadcastreceiver.SCCReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.e(getClass().getName(),"SCCReceiver2,传递内容:"+intent.getStringExtra("scc"));
    }
}


4.2.2注册广播


在代码中通过registerReceiver(BroadcastReceiver receiver, IntentFilter filter)来动态注册广播,该方法包含两个参数,receiver即我们自己定义的SCCReceiver2,IntentFilter即需要过滤的条件。


private SCCReceiver2 sccReceiver2;
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btn_reg:
            Log.e(getClass().getName(),"点击R.id.btn_reg");
            if (sccReceiver2 == null) {//防止重复注册
                sccReceiver2 = new SCCReceiver2();
                registerReceiver(sccReceiver2, new IntentFilter(SCCReceiver2.ACTION));
            }
            break;
    }
}


4.2.3发送广播


  • actvitiy_main.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical">
    <Button
        android:id="@+id/btn_reg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="注册动态广播" />
    <Button
        android:id="@+id/btn_unreg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="注销动态广播" />
    <Button
        android:id="@+id/btn_send_action"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="发送动态广播" />
</LinearLayout>


  • MainActivity.java


private SCCReceiver2 sccReceiver2;
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btn_reg:
            Log.e(getClass().getName(),"点击R.id.btn_reg");
            if (sccReceiver2 == null) {//防止重复注册
                sccReceiver2 = new SCCReceiver2();
                registerReceiver(sccReceiver2, new IntentFilter(SCCReceiver2.ACTION));
            }
            break;
        case R.id.btn_unreg:
            Log.e(getClass().getName(),"点击R.id.btn_unreg");
            if (sccReceiver2 != null) {//防止重复注销
                unregisterReceiver(sccReceiver2);
                sccReceiver2 = null;
            }
            break;
        case R.id.btn_send_action:
            Log.e(getClass().getName(),"点击R.id.btn_send_action");
            Intent intentAction = new Intent(SCCReceiver2.ACTION);
            intentAction.putExtra("scc", "动态,真的帅");
            sendBroadcast(intentAction);
            break;
    }
}


  • 点击注册动态>发送动态广播,实现效果如下:


微信图片_20220520140748.png



4.2.4注销广播


       使用unregisterReceiver(BroadcastReceiver receiver)来注销注册,动态注册的广播在应用停止运行后无法接收广播,比如在MainActivity中注册,则应当在MainActivity销毁前,使用unregisterReceiver(BroadcastReceiver receiver)来注销注册


对于动态广播注意:


1.有注册就必然得有注销,否则会导致内存泄露


2.重复注册、重复注销也不允许


4.3无序广播


       无序广播直接通过Context.sendBroadcast()来发送。上面的4.1静态广播和4.2动态广播实现都是无序广播。无序广播被发送后,BroadCastReceiver之间是无顺序,完全异步的,各个Receiver之间无关联。无序广播无法通过abortBroadcast终止,也无法使用setResult和getResult来传递处理结果。


4.4有序广播


有序广播需要通过Context.sendOrderedBroadcast()来发送。有序广播发送后会按照优先级顺序被不同的广播接收器接收,优先级可以通过intent-filter的android:priority属性来设置,定义范围为-1000~1000,数值越大,优先级越高(如果优先级相同,动态注册的广播优先)。


先接收的广播接收者可以对广播进行截断abortBroadcast,即后接收的广播接收者不再接收到此广播;


先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播。


  • 4.4.1不设置android:priority点击发送有序广播运行效果:


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.scc.broadcastreceiver">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.BroadcastReceiverDemo">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/Theme.BroadcastReceiverDemo.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver
            android:name=".SCCReceiver"
            android:enabled="true"
            android:exported="true"/>
        <receiver
            android:name=".SCCReceiver3">
            <intent-filter>
                <action android:name="com.scc.broadcastreceiver.SCCReceiver"/>
            </intent-filter>
        </receiver>
        <receiver
            android:name=".SCCReceiver4">
            <intent-filter>
                <action android:name="com.scc.broadcastreceiver.SCCReceiver"/>
            </intent-filter>
        </receiver>
        <receiver
            android:name=".SCCReceiver5">
            <intent-filter>
                <action android:name="com.scc.broadcastreceiver.SCCReceiver"/>
            </intent-filter>
        </receiver>
    </application>
</manifest>


case R.id.btn_send_ordered:
    Log.e(getClass().getName(),"点击R.id.btn_send_ordered");
    Intent intentActionOrdered = new Intent(SCCReceiver2.ACTION);
    intentActionOrdered.putExtra("scc", "有序,真的牛");
    sendOrderedBroadcast(intentActionOrdered,null);
    break;


  • 运行效果:
SCCReceiver3>SCCReceiver4>SCCReceiver5


  • 4.4.2设置android:priority点击发送有序广播运行效果:


<receiver
    android:name=".SCCReceiver3">
    <intent-filter android:priority="-100">
        <action android:name="com.scc.broadcastreceiver.SCCReceiver"/>
    </intent-filter>
</receiver>
<receiver
    android:name=".SCCReceiver4">
    <intent-filter android:priority="0">
        <action android:name="com.scc.broadcastreceiver.SCCReceiver"/>
    </intent-filter>
</receiver>
<receiver
    android:name=".SCCReceiver5">
    <intent-filter android:priority="100">
        <action android:name="com.scc.broadcastreceiver.SCCReceiver"/>
    </intent-filter>
</receiver>


运行效果:


SCCReceiver5(100)>SCCReceiver4(0)>SCCReceiver3(-100)


  • 4.4.3修改广播拦截广播


  • 4.4.3.1 SCCReceiver5.java


public class SCCReceiver5 extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.e(getClass().getName(),"SCCReceiver5,传递内容:"+intent.getStringExtra("scc"));
        Bundle bundle = getResultExtras(true);
        bundle.putString("scc","改了数据");
        setResultExtras(bundle);
    }
}


  • 4.4.3.2 SCCReceiver4.java


1.public class SCCReceiver4 extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Bundle bundle = getResultExtras(true);
        String name = bundle.getString("scc");
        Log.e(getClass().getName(),"SCCReceiver4,传递内容:"+name);
        abortBroadcast();
        Log.e(getClass().getName(),"SCCReceiver4,拦截");
    }
}


运行效果:


SCCReceiver5(100)修改数据>SCCReceiver4(0)拦截>没有SCCReceiver3(-100)


Broadcast Receiver实现原理


在Android中,广播的出现是为了组件间的通信。其实在Android中,进程间通信有Binder,而同进程的通信方式就更多了,之所以使用广播,发送者与接受者都不需要知道对方的存在,这样带来的好处便是,系统的各个组件可以松耦合地组织在一起,这样系统就具有高度的可扩展性,容易与其它系统进行集成。


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


「广播模型中包含三个角色:」


  • 消息发布者(广播发布者)


  • 消息中心(AMS,即Activity Manager Service)


  • 消息订阅者(广播接收者)


实现步骤:


  • 广播接受者通过Binder机制在AMS中注册监听


  • 广播发布者通过Binder机制向AMS发送广播


  • AMS根据发送者的需求,在已注册表中获取到合适的广播接受者(根据Intent-filter,Permission)


  • AMS将广播发送给合适的广播接受者的消息循环队列中


  • 广播接受者通过消息循环获取到广播并回调onReceive()


整个广播发送与接收过程中,发送者与接收者是异步的,发送者不需要知道是否有接受者,也不需要知道接受者何时收到广播。


LocalBroadcastManager特点


  • 本地广播只能在自身App内传播,不必担心泄漏隐私数据


  • 本地广播不允许其他App对你的App发送该广播,不必担心安全漏洞被利用


  • 本地广播比全局广播更高效


Broadcast Receiver注意事项


  • 动态注册的广播,在不需要使用时或者载体即将销毁时进行注销,即每一个registerReceiver需要有一个对应的unregisterReceiver。


  • 不要在广播接收器onReceive()中进行耗时操作,否则会引起ANR(10s)。


  • 不要在广播接收器onReceive()中开启异步任务,否则因为其生命周期的结束会出现空线程,导致任务丢失或者出现ANR等情况。


  • 耗时任务请开启service进行处理,且应当使用startService,而不应该使用bindService。


  • 应用内的广播尽量使用LocalBroadcas,因为其使用Handler,较Broadcast的Binder机制开销更小,且安全性更高。


  • 动态注册的广播优先度比静态注册高(当配置的优先级一致时),且可以控制其注册与注销,开销更小,所以能满足功能的情况下优先使用动态注册


  • 如果接受不到自己发送的广播,请注意是否是因为权限问题。


实例:BroadcastReceiverDemo.zip




相关文章
|
5天前
|
Java Android开发
Android面试题经典之Glide取消加载以及线程池优化
Glide通过生命周期管理在`onStop`时暂停请求,`onDestroy`时取消请求,减少资源浪费。在`EngineJob`和`DecodeJob`中使用`cancel`方法标记任务并中断数据获取。当网络请求被取消时,`HttpUrlFetcher`的`cancel`方法设置标志,之后的数据获取会返回`null`,中断加载流程。Glide还使用定制的线程池,如AnimationExecutor、diskCacheExecutor、sourceExecutor和newUnlimitedSourceExecutor,其中某些禁止网络访问,并根据CPU核心数动态调整线程数。
16 2
|
3天前
|
存储 安全 Java
Android面试题之ArrayList源码详解
ArrayList是Java中基于数组实现的列表,提供O(1)的索引访问,但插入和删除操作平均时间复杂度为O(n)。默认容量为10,当需要时会通过System.arraycopy扩容。允许存储null,非线程安全。面试常问:List是接口,ArrayList是其实现之一,推荐使用List接口编程以实现更好的灵活性。更多详情见[ArrayList源码](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/util/ArrayList.java#ArrayList.Node)。
10 2
|
3天前
|
Android开发
Android面试题经典之如何全局替换App的字体
在Android应用中替换字体有全局和局部方法。全局替换涉及在`Application`的`onCreate`中设置自定义字体,并创建新主题。局部替换则可在布局中通过`ResourcesCompat.getFont()`加载字体文件并应用于`TextView`。
18 2
|
5天前
|
算法 Java API
Android性能优化面试题经典之ANR的分析和优化
Android ANR发生于应用无法在限定时间内响应用户输入或完成操作。主要条件包括:输入超时(5秒)、广播超时(前台10秒/后台60秒)、服务超时及ContentProvider超时。常见原因有网络、数据库、文件操作、计算任务、UI渲染、锁等待、ContentProvider和BroadcastReceiver的不当使用。分析ANR可借助logcat和traces.txt。主线程执行生命周期回调、Service、BroadcastReceiver等,避免主线程耗时操作
18 3
|
5天前
|
API Android开发
Android 监听Notification 被清除实例代码
Android 监听Notification 被清除实例代码
|
10天前
|
SQL XML Java
Android 这 13 道 ContentProvider 面试题,你都会了吗?
Android 这 13 道 ContentProvider 面试题,你都会了吗?
|
7天前
|
缓存 编解码 安全
Android经典面试题之Glide的缓存大揭秘
Glide缓存机制包括内存和硬盘缓存。内存缓存使用弱引用的ActiveResources和LRU策略,硬盘缓存利用DiskLruCache。Engine.load方法首先尝试从内存和弱引用池加载,然后从LRU缓存中加载图片,增加引用计数并移出LRU。若缓存未命中,启动新任务或加入现有任务。内存大小根据设备内存动态计算,限制在0.4以下。DiskLruCache使用自定义读写锁,保证并发安全,写操作通过锁池管理,确保高效。
9 0
|
10天前
|
安全 Android开发 iOS开发
探索安卓与iOS开发的差异:平台特性与用户体验的深度对比
在移动应用开发的广阔天地中,安卓和iOS两大平台各占半壁江山。本文旨在通过数据驱动的分析方法,深入探讨这两大操作系统在开发环境、用户界面设计及市场表现等方面的差异。引用最新的行业报告和科研数据,结合技术专家的观点,本文将提供对开发者和市场分析师均有价值的洞见。
|
2天前
|
移动开发 Android开发 iOS开发
探索安卓与iOS开发的差异:平台选择对应用性能的影响
在移动开发的广阔舞台上,安卓与iOS这两大操作系统各据一方,引领着技术潮流与市场需求。本文深入探讨了这两个平台在开发过程中的关键差异,并分析了这些差异如何影响应用的性能和用户体验。通过对比分析,我们将揭示开发者在选择平台时应考虑的技术细节,以及这些选择如何塑造最终产品的命运。文章不仅为开发者提供了实用的指导,也为那些对移动开发感兴趣的读者提供了深刻的洞见。