BroadcastReceiver详解及应用
vxlx9100
8年前
<p>使用Android手机的时候,我们的手机管家中经常会出现开机自启动某某app,那么对于这个某某APP来说,他是怎么知道系统什么时候开机的呢?还有,系统短信怎么知道收到了短信?以及屏幕点亮与关闭、应用卸载与安装等等。</p> <p>这就讲到了Android四大组件之一: <strong>BroadcastReceiver</strong> ,翻译是 <strong>广播接收者</strong> 。意思就是接收广播用的。他可以接收到系统开机完成的广播,以及系统电量不足的广播,以及系统收到短信的广播,等等。我们收到广播后就可以做我们想做的事了。现实中使用广播时,有发送广播的电台,接收广播的收音机以及广播传递的媒介电磁波。而在Android中的广播机制与现实中一样,发送广播的是 <strong>Broadcast</strong> ,接收广播的 <strong>BroadcastReceiver</strong> 及广播之间传递数据的 <strong>Intent</strong> 。</p> <h2>注册BroadcastReceiver接收广播</h2> <p>继承BroadcastReceiver这是一个抽象类,</p> <p>public abstract class BroadcastReceiver {</p> <ul> <li><strong>实现抽象方法</strong> public abstract void onReceive(Context context, Intent intent); * 当收到注册的广播时,onReceive方法会被调用。 <ul> <li>context是上下文,Intent就是广播携带的数据。 <pre> <code class="language-java">public class MyBroadcastReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { String action = intent.getAction();//获取到收到的广播的名称 Log.e("hui", "收到的广播的Action是:"+action); }}</code></pre> </li> </ul> </li> <li><strong>注册BroadcastReceiver</strong> ,作为四大组件之一,当然需要注册。<br> BroadcastReceiver有两种注册方式: <ul> <li>静态注册(在AndroidManifest.xml清单文件中注册)</li> <li>动态注册(在代码中注册)</li> </ul> </li> </ul> <h2>广播接收者静态注册方式</h2> <p>当我们需要一直接收某种广播时,可以使用静态注册方式。</p> <p>以 <strong>监听手机打电话</strong> 为例子。</p> <pre> <code class="language-java"><uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/> <receiver android:name=".MyBroadcastReceiver"> <intent-filter> <action android:name="android.intent.action.NEW_OUTGOING_CALL" /> </intent-filter> </receiver></code></pre> <p>上面的receiver 表示这个MyBroadcastReceiver是广播接收者。action表示要监听的广播类型,这里的表示开机完成的广播。 因为监听用户的电话状态属于侵犯用户隐私,所以需要添加 android.permission.PROCESS_OUTGOING_CALLS 权限。</p> <p>下图是接收打电话广播:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/6d4db8714d49abdeef917346f2e6cf10.gif"></p> <p>接收打电话广播</p> <p>实战 <strong>开机自启动APP</strong> : <a href="/misc/goto?guid=4959728123013077012" rel="nofollow,noindex">链接</a></p> <h2>广播接收者动态注册方式</h2> <p>当我们不需要一直接收某种广播时,可以使用动态注册广播接收者的方式。</p> <p>以 <strong>监听屏幕点亮与关闭</strong> 为例子。</p> <pre> <code class="language-java">public class MainActivity extends Activity { private MyBroadcastReceiver receiver ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); registerMyReceiver();//在activity创建的时候进行注册监听 } private void registerMyReceiver() { receiver = new MyBroadcastReceiver(); IntentFilter filter = new IntentFilter();//创建IntentFilter对象 filter.addAction(Intent.ACTION_SCREEN_OFF);//IntentFilter对象中添加要接收的关屏广播 filter.addAction(Intent.ACTION_SCREEN_ON);//添加点亮屏幕广播 registerReceiver(receiver, filter); } private void unRegisterMyReceiver(){ if(receiver != null){ unregisterReceiver(receiver);//反注册广播,也就是注销广播接收者,使其不起作用 } } }</code></pre> <p>下图是接收屏幕点亮与关闭广播:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/b3d246d2f95060311c859eb1c11b1f71.gif"></p> <p>接收屏幕点亮与关闭广播</p> <p>下图是退出APP的状况:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/9e302faf2526c4b498bf40f5f023b36c.gif"></p> <p>退出APP接收屏幕点亮与关闭广播</p> <p>可以看到,退出APP后,接收打电话广播任然起作用,但是接收屏幕点亮与关闭的广播却没效果。为什么呢?看下述差异:</p> <p>实战 <strong>短信验证码自动填入</strong> : <a href="/misc/goto?guid=4959728123115608354" rel="nofollow,noindex">链接在这</a></p> <h3>广播接收者静态注册方式与静态注册方式差异</h3> <ul> <li>静态注册 静态注册依附于清单文件,只要APP启动过一次,所静态注册的广播就会生效,无论当前的APP处于停止使用还是正在使用状态。只要相应的广播事件发生,系统就会遍历所有的清单文件,通知相应的广播接收者接收广播,然后调用广播接收者的onReceiver方法。</li> <li>动态注册动态注册方式依赖于所注册的组件,当APP关闭后,组件对象都不在了动态注册的代码都不存在了,所动态注册监听的action自然不在生效。</li> <li>静态注册的广播传播速度要远远慢于动态注册的广播。</li> </ul> <h2>对广播接收者同时使用静态与动态注册</h2> <p>上面例子中MyBroadcastReceiver使用静态注册监听用户打电话,使用动态注册监听用户屏幕点亮与关闭。所以,监听到屏幕的开关只有在APP运行的状态才可以,但是监听打电话的状态无论此时app是否在运行,都可以监听到。</p> <p>需要注意:动态注册的广播的优先级大于静态注册的广播。至于这个是为什么呢?额(⊙o⊙)…谷歌写的源代码的时候先对动态广播进行处理然后在对静态广播进行处理。后面我们了解到广播的优先级后会实例证明的。</p> <h2>BroadcastReceiver分类</h2> <p>广播的发送,可以分为 <strong>有序广播</strong> 、 <strong>无序广播</strong> 、 <strong>本地广播</strong> 以及 <strong>sticky广播</strong> 。</p> <h2>有序广播</h2> <p>有序广播是一种分先后广播接收器的广播,广播接收者的优先级越高,越先接收广播。优先级高的广播先收到广播,收到广播后可以修改广播的内容,也可以拦截广播不让广播向下传递。就像皇上通知知府每人赏金100两,知府通知知县每人赏金100两,最后才是农民知道了赏金的事,一旦知府或者知县不告诉下级赏金的的事,那么农民就不知道赏金的事了,这就是有序广播的拦截广播;当然知府或者知县也可以向下级通知只有赏金10两的事,这就是有序广播的修改广播内容。</p> <h2>无序广播</h2> <p>无序广播指所有与之匹配的广播接收者都能收到广播,没有先后顺序,直到没有广播接收者接收广播为止才会停止广播的传递。就像皇上贴告示,昭告天下每人赏金100两银子一样,那么所有的农民都知道了这件事,没有先后之分,当农民直到了钱的事之后这件事就算了结了。</p> <p>前文讲过,有广播发送时,系统会遍历全部APP的receiver。如果想使得本APP的广播不被外界的广播所干扰,可以在receiver节点添加android:exported="false"属性 ,这样系统遍历全部APP清单文件的广播接收者时不会对本receiver进行判断及处理。</p> <p>这个值为FALSE表示不予其他APP相交互。</p> <h2>本地广播</h2> <p>与有序和无序广播的全局广播(任何一方发出广播本手机的任何一个程序都能收到对应的广播)相比,本地广播是局部的广播基于本程序的广播,其他的程序无法收到这个广播。本地广播就类似当地的知县单独给农民发一两银子,只有当地人才知道,其他的人不知道。这个广播是API 21的V4包中新增的,用来保证广播是独家私有的。这种广播是安全的,外界不会干扰他,广播也不会被其他进程所收到。</p> <h2>sticky广播</h2> <p>sticky粘性的意思。这种广播一般不会终止,只要有符合条件的广播接收者能接收广播,那么就会发送给他广播。永远不会终止发送广播,除非某个广播接收者告诉它不要再发送广播了。</p> <h2>发送自定义广播</h2> <p>实例演练:创建两个广播接收者:ZhiFuReceiver/ZhiXianReceiver</p> <p>创建:</p> <pre> <code class="language-java">public class ZhiXianReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d("hui", "ZhiXianReceiver = " + intent.getStringExtra("qian"));//取出广播中携带的数据,因为我存数据的时候是intent.putExtra("qian", "100");存入的。遵循如何存如何取得原则取数据 }} public class ZhiFuReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d("hui", "ZhiFuReceiver = " + intent.getStringExtra("qian")); }}</code></pre> <p>清单文件如下配置:</p> <pre> <code class="language-java"><receiver android:name=".ZhiFuReceiver"> <intent-filter > <action android:name="my.broadcast.faqian"/>//自定义的广播接收者接收的广播名称 </intent-filter> </receiver> <receiver android:name=".ZhiXianReceiver"> <intent-filter > <action android:name="my.broadcast.faqian"/> </intent-filter> </receiver></code></pre> <h2>发送无序广播</h2> <pre> <code class="language-java">public void sendCustomBroadcast(View view){ Intent intent = new Intent("my.broadcast.faqian");//action是my.broadcast.faqian,清单文件中的action与之一致方可收到广播 intent.putExtra("qian", "100");//广播中携带的数据 sendBroadcast(intent);//发送无序广播 }</code></pre> <p style="text-align:center"><img src="https://simg.open-open.com/show/87570fc0a8f1b2cb5427a9e3063acba7.gif"></p> <p>这里写图片描述</p> <p>虽然这里打印顺序有先后但是这个先后顺序是无意义的,总体来看还是无序的。</p> <h2>发送有序广播</h2> <p>发送方式一:</p> <pre> <code class="language-java">public void sendCustomBroadcast(View view){ Intent intent = new Intent("my.broadcast.faqian");//action是my.broadcast.faqian,清单文件中的action与之一致方可收到广播 intent.putExtra("qian", "100");//广播中携带的数据 /** * sendOrderedBroadcast(Intent intent, String receiverPermission); */ sendOrderedBroadcast(intent, null);//发送有序广播 }</code></pre> <p>清单文件配置</p> <pre> <code class="language-java"><receiver android:name=".ZhiFuReceiver"> <intent-filter android:priority="100">//设置优先级,为整数,越大优先级越高 <action android:name="my.broadcast.faqian"/> </intent-filter> </receiver> <receiver android:name=".ZhiXianReceiver"> <intent-filter android:priority="200" > <action android:name="my.broadcast.faqian"/> </intent-filter> </receiver></code></pre> <p style="text-align:center"><img src="https://simg.open-open.com/show/216574e2cf99817a1e7383ed941a9906.gif"></p> <p>这里写图片描述</p> <p>ZhiXianReceiver优先级大于ZhiFuReceiver优先级,故ZhiXianReceiver先收到广播。</p> <p>发送方式二:</p> <p>sendOrderedBroadcast的另一个重载方法如下。</p> <pre> <code class="language-java">sendOrderedBroadcast( Intent intent,//封装了action及其他数据 String receiverPermission, //广播接收者需要的权限 BroadcastReceiver resultReceiver,// Handler scheduler, int initialCode, String initialData, Bundle initialExtras);</code></pre> <p>参数解释:</p> <ul> <li>intent 封装了action及其他数据</li> <li>receiverPermission, //广播接收者需要的权限</li> <li>resultReceiver 有序广播是支持拦截的,一旦被拦截可以修改广播中数据甚至直接终止广播,这个resultReceiver表示无论当广播传播结束以后我任然会受到广播。(下面会有栗子演示)</li> <li>initialCode 发送广播的时候默认携带的数据</li> <li>initialData 发送广播的时候默认携带的数据</li> <li>initialExtras 发送广播的时候默认携带的数据 实例:将上面例子中的发送广播的方法修改如下</li> </ul> <pre> <code class="language-java">public void sendCustomBroadcast(View view){ Intent intent = new Intent("my.broadcast.faqian");//action是my.broadcast.faqian,清单文件中的action与之一致方可收到广播 Bundle bundle = new Bundle(); bundle.putString("qian","100");//广播中携带的bundle数据 sendOrderedBroadcast( intent, null, //permission为null new ZhiFuReceiver(), //这里的new ZhiFuReceiver()为最终的广播接收者,也就是说无论他曾经有没有收到广播都会再次收到广播。 null, 666,//initCode "我是initialData",//initData bundle);//bundle //以上所有入参都会携带在广播中,如何取出呢? }</code></pre> <p>接收广播</p> <pre> <code class="language-java">public class ZhiXianReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { /***********获取数据*************/ int initCode = getResultCode();//获取传递过来的initCode String initData = getResultData();//获取传递过来的initData Bundle initBundle = getResultExtras(true);//获取传递过来的Bundle Log.d("hui", "ZhiXianReceiver = " +"initCode = "+initCode +" ,initdata = " +initData +" ,bundle = " +initBundle.getString("qian")); }} public class ZhiFuReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { /***********获取数据*************/ int initCode = getResultCode();//获取传递过来的initCode String initData = getResultData();//获取传递过来的initData Bundle initBundle = getResultExtras(true);//获取传递过来的Bundle Log.d("hui", "ZhiFuReceiver = " +"initCode = "+initCode +" ,initdata = " +initData +" ,bundle = " +initBundle.getString("qian")); }}</code></pre> <p>结果:</p> <pre> <code class="language-java">ZhiXianReceiver = initCode = 666 ,initdata = 我是initialData ,bundle = 100ZhiFuReceiver = initCode = 666 ,initdata = 我是initialData ,bundle = 100ZhiFuReceiver = initCode = 666 ,initdata = 我是initialData ,bundle = 100</code></pre> <p style="text-align:center"><img src="https://simg.open-open.com/show/3af4baa8ce5200e9a1bd684abe6e71bb.gif"></p> <p>拿到BroadcastReceiver数据</p> <p>有一点需要说明,这里ZhiFuReceiver 收到了两次数据。为什么呢?ZhiXianReceiver 得优先级大于ZhiFuReceiver ,同时ZhiXianReceiver 未拦截广播,所以会先ZhiXianReceiver 一次后ZhiFuReceiver 一次,而发送广播的时候声明了ZhiFuReceiver 为最终接受者,所以无论他曾经有没有收到广播都会再次收到广播。</p> <p>图示:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3ad686f97e8c5741aac2e4a9eed8764e.png"></p> <p>理解ResultBroadcastReceiver</p> <p>下面我们看看拦截后会有什么效果。</p> <h3>有序广播的拦截与修改数据</h3> <p>拦截广播将上面例子中的ZhiXianReceiver 添加一行拦截广播的代码,看看结果。</p> <pre> <code class="language-java">public class ZhiXianReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { /***********获取数据*************/ int initCode = getResultCode();//获取传递过来的initCode String initData = getResultData();//获取传递过来的initData Bundle initBundle = getResultExtras(true);//获取传递过来的Bundle Log.d("hui", "ZhiXianReceiver = " +"initCode = "+initCode +" ,initdata = " +initData +" ,bundle = " +initBundle.getString("qian")); abortBroadcast();//拦截广播,广播被终止,以后不会有其他广播接收者再收到广播了。 }}</code></pre> <p style="text-align:center"><img src="https://simg.open-open.com/show/92aaf1377a8a7c44ed1bb5062e0816cb.gif"></p> <p>拦截广播</p> <p>这里abortBroadcast()拦截了有序广播,不是说每人能再收到广播了么?为什么ZhiFuReceiver 还能收到广播呢?这是因为ZhiFuReceiver 是广播的最终接受者,广播从优先级高的广播接收者优先接收,一层一层向优先级较低的传送。当广播被拦截后,广播部分的层层发送这里链路发送完毕,但是有最终广播接收者,故最终广播接收者会收到最后的广播。故ZhiFuReceiver 会收到广播。</p> <p>下图理解:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/b90eca198e3cf66859f7655f2fcb019d.png"></p> <p>理解拦截广播以及ResultBroadcastReceiver</p> <p>修改广播中内容</p> <pre> <code class="language-java">public class ZhiXianReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { /***********获取数据*************/ int initCode = getResultCode();//获取传递过来的initCode String initData = getResultData();//获取传递过来的initData Bundle initBundle = getResultExtras(true);//获取传递过来的Bundle Log.d("hui", "ZhiXianReceiver = " +"initCode = "+initCode +" ,initdata = " +initData +" ,bundle = " +initBundle.getString("qian")); /**************修改数据****************/ setResultCode(8989);//修改initCode setResultData("ZhiXianReceiver修改了数据"); //修改initData //修改bundle数据 Bundle bundle = new Bundle(); bundle.putString("qian", "10"); setResultExtras(bundle); }}</code></pre> <p style="text-align:center"><img src="https://simg.open-open.com/show/f719acfb8f788086e78b0593eb5050a6.gif"></p> <p>修改广播中数据</p> <p>上面例子中我把几个广播接收者都写在一个APP中了,如果把每个广播接收者分别放在不同的app中一样都能收到广播(如果广播不被拦截)。如果我只想发送的广播给我自己APP种的广播接收到,可以使用本地广播,这种广播是安全的,外界不会干扰他,广播也不会被其他进程所收到。</p> <h3>发送本地广播</h3> <p>本地广播的使用是写在代码中的,因为本地广播发送广播时是直接在代码中注册的广播中进行匹配从而调用其onReceiver的。</p> <p>简单看下源码:</p> <pre> <code class="language-java">public void sendBroadcastSync(Intent intent) { if (sendBroadcast(intent)) { executePendingBroadcasts(); } } private void executePendingBroadcasts() { while (true) { BroadcastRecord[] brs = null; synchronized (mReceivers) { final int N = mPendingBroadcasts.size(); if (N <= 0) { return; } brs = new BroadcastRecord[N]; mPendingBroadcasts.toArray(brs); mPendingBroadcasts.clear(); } for (int i=0; i<brs.length; i++) { BroadcastRecord br = brs[i]; for (int j=0; j<br.receivers.size(); j++) { //在这里直接调用其onReceiver方法了 br.receivers.get(j).receiver.onReceive(mAppContext, br.intent); } } } }</code></pre> <p>使用localBroadcastManager.registerReceiver( myBroadCastReceiver, intentFilter)注册:</p> <pre> <code class="language-java">/** * 本地广播接收者进行注册,必须在代码中注册,清单文件注册是无效的 */ public void registerMyAPPReceiver(View view) { //创建广播接收者 MyBroadCastReceiver myBroadCastReceiver = new MyBroadCastReceiver(); MyBroadcastReceiver2 myBroadCastReceiver2 = new MyBroadcastReceiver2(); //封装要接收的广播类型 IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("my.broadcast.faqian2"); //拿到LocalBroadcastManager对象,对固定的Receiver进行注册,成为本地广播接收者 LocalBroadcastManager localBroadcastManager =LocalBroadcastManager.getInstance(MainActivity.this); localBroadcastManager.registerReceiver( myBroadCastReceiver, intentFilter); localBroadcastManager.registerReceiver(myBroadCastReceiver2, intentFilter); }</code></pre> <p>注意:</p> <ol> <li>registerReceiver注册一个广播接收者可以多次执行,比如:我把‘ocalBroadcastManager.registerReceiver( myBroadCastReceiver, intentFilter);’写两遍,那么myBroadCastReceiver的onReceiver会被调用两次,不建议这样写。</li> <li>本地广播不能拦截</li> <li>registerReceiver对应的还有unregisterReceiver(receiver)</li> </ol> <pre> <code class="language-java">/** * 发送本地广播 * @param view */ public void sendMyAPPBroadcat(View view){ Intent intent = new Intent("my.broadcast.faqian2");//action是my.broadcast.faqian,清单文件中的action与之一致方可收到广播 Bundle bundle = new Bundle(); bundle.putString("qian","100");//广播中携带的bundle数据 intent.putExtra("bundle_data", bundle); //使用LocalBroadcastManager发送广播 LocalBroadcastManager.getInstance(MainActivity.this).sendBroadcastSync(intent);//发送 }</code></pre> <h3>发送sticky广播</h3> <p>添加权限: <user-permission android:name="android.permission.BROADCAST_STICKY"/><br> 发送 context.sendStickyBroadcast()<br> 停止使用 context.removeStickyBroadcast()</p> <p>如有错误,不吝赐教啊</p> <p>累死了,北京时间:2016.12.5 凌晨 0:27</p> <p> </p> <p>来自:http://www.jianshu.com/p/989e7c2f9293</p> <p> </p>