Android — 之广播接收器

来源:互联网 发布:js数组方法大全 编辑:程序博客网 时间:2024/05/18 13:44

一、广播的概念

广播,额,顾名思义,就是…..就是广播嘛…..(别打脸,Please~~)。
广播是Android中 一种 重要的消息传输机制,消息可以是在应用程序之间,也阔以只在程序内部进行传播。在Android系统中,应用程序可以只对自己感兴趣的广播进行“注册“,这样改程序只会接收到自己所关心的广播内容(举个例子:XXX,你的快递~),但是,要接收广播就需要使用“广播接收器”(即 Broadcast Receiver)。

二、广播的分类

Android中的广播主要可以分为两种类型,标准广播(Normal broadcast)和有序广播(Ordered broadcast),可能不同的人叫法不同,但是意思应该是一样的。
标准广播:是一种异步执行的广播,在广播发出去以后,所有的广播接收器几乎会在同一时刻接收到这条广播,这种广播效率比较高,同时也无法被截断。如下图1所示。
有序广播:是一种同步执行的广播,在广播发出去以后,同一时刻只会有一个广播接收器能够接收到这条广播,当这个广播接收器中的逻辑执行完后,才会继续传递,但也有可能被当前的广播接收器截断,不再进行传播。如下图2所示。

这里写图片描述
图1.标准广播

这里写图片描述
图2.有序广播

三、广播接收器的使用

前面已经说过,若想接收广播需要使用广播接收器,广播接收器可以自由的对自己感兴趣的广播进行注册,注册的方式一般可分为两种:一是在代码中进行注册,也称为动态注册;一是在AndroidManifest.xml文件中注册,也被称为静态注册。所接收的广播可以是系统发出的广播,也可以是应用程序发出的自定义广播。

上面提出的新概念有很多,什么标准广播,有序广播,动态广播,静态广播,系统广播,自定义广播,真是让人蛋疼的啊,那我们只能通过代码,来一一进行解释了。

1). 广播接收器

不管要接收什么样的广播,我们都需要使用广播接收器,啰嗦这么多,到底怎么创建一个广播接收器呢?方法并不复杂,只需要实现一个类,一个继承自BroadcastReceiver的类,并重写父类的onReceive()方法就行了,因为接收到感兴趣的广播的时候,该方法会得到执行,即可去做你希望的想做的事情。不过!!!不要在 onReceive()方法中添加过多的逻辑或者进行任何的耗时操作,因为在广播接收器中是不允许开启线程的,当 onReceive()方法运行了较长时间而没有结束时,程序就会报错。它的生命周期大概只有10秒左右!!

如:当我接收到广播的时候的,使用Toast弹出一个提醒。

package com.yu.brocasttext;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.widget.Toast;public class MyBroadcastReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {        Toast.makeText(context, "Receiverd in MyBroadcatReceiver", Toast.LENGTH_SHORT).show();    }}

当然,若是想弹出提醒,前提是得接收到感兴趣的广播才行,那么问题来了,该怎么注册感兴趣的广播呢?我们接下来分类(静态和动态)进行阐述。

2). 动态注册广播

例.使用动态注册的方式编写一个能够监听网络变化的广播程序
MainActivity.java :

package com.yu.brocasttext;import android.app.Activity;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.net.ConnectivityManager;import android.net.NetworkInfo;import android.os.Bundle;import android.util.Log;import android.widget.Toast;public class MainActivity extends Activity {    private IntentFilter intentFilter;    private NetworkChangeReceiver networkChangeReceiver;        @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        Log.d("BroadcastText", "run at boot");        setContentView(R.layout.activity_main);        //动态注册广播  接收系统广播        //构建IntentFilter实例        intentFilter = new IntentFilter();        //为IntentFilter添加感兴趣的action ;  当网络状态发生变化时,        //系统发出的正是一条值为 android.net.conn.CONNECTIVITY_CHANGE 的广播        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");        //创建(网络变化)广播接收器对象        networkChangeReceiver = new NetworkChangeReceiver();        //注册广播接收器        registerReceiver(networkChangeReceiver, intentFilter);    }    @Override    protected void onDestroy() {        super.onDestroy();        //动态注册的广播一定注意要取消注册,否则可能会造成内存泄漏        unregisterReceiver(networkChangeReceiver);    }    //构建一个继承自BroadcastReceiver的类,当网络发生变化时会收到广播    class NetworkChangeReceiver extends BroadcastReceiver {        @Override        public void onReceive(Context context, Intent intent) {            //获取ConnectivityManager实例,这是一个系统服务类,专门用于管理网络连接            ConnectivityManager connectionManager = (ConnectivityManager)                     getSystemService(Context.CONNECTIVITY_SERVICE);            //获取NetWorkInfo实例            NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo();            //判断网络当前是否可用            if(networkInfo != null && networkInfo.isAvailable()) {                Toast.makeText(context, "network is available", Toast.LENGTH_SHORT);            }else {                Toast.makeText(context, "network is unavailable", Toast.LENGTH_SHORT).show();            }        }    }}

Android 系统为了保证应用程序的安全性做了规定,如果程序需要访问一些系统的关键性信息,必须在配置文件中声明权限才可以,否则程序将会 直 接 崩 溃 !!
AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.yu.brocasttext"    android:versionCode="1"    android:versionName="1.0" >    //...,...    **<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>**    <application        android:allowBackup="true"        android:icon="@drawable/ic_launcher"        android:label="@string/app_name"        android:theme="@style/AppTheme" >        <activity             android:name=".MainActivity">            <intent-filter >                <action android:name="android.intent.action.MAIN"/>                <category android:name="android.intent.category.LAUNCHER"/>            </intent-filter>        </activity>    </application></manifest>

上例中,
我们首先实现了一个继承自BroadcastReceiver的类NetworkChangeReceiver ,在这个类的onReceive方法中实现了一些逻辑,来判断当前网络的状态,并做出相应的提醒。
然后使用 registerReceiver(BroadcastReceiver receiver, IntentFilter filter),来注册一个广播,这个方法接收两个参数:第一个参数是要注册的广播接收器对象,第二个是一个IntentFilter对象,告诉Android系统该广播接收器希望接收什么类型的请求行为。这两个参数创建的方法在上例的注释中写的应该还算清楚,这里就不累述了。

3).静态注册广播

可以看到动态注册广播的方式,比较灵活,但是必须得启动该程序,但要想实现不启动程序就能接收广播,就需要使用静态的方式进行注册。
由于静态方式是在AndroidManifest文件将使用的广播接收器的类名进行注册,所以不能再使用内部类的方式实现继承BroadcastReceiver类。
例如,实现一个程序能够接收开机广播
新建一个类BootCompleteReceiver让它继承自BroadcastReceiver类并重写onReceive()方法
BootCompleteReceiver.java

package com.yu.brocasttext;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.widget.Toast;public class BootCompleteReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {        //该程序很简单,就是当系统开机完成的时候,弹出一条提醒        Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();    }}

代码很简单,不在多说,然后在AndroidManifest.xml文件中注册该接收器

<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.yu.brocasttext"    android:versionCode="1"    android:versionName="1.0" >    //...,...    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>    <application        android:allowBackup="true"        android:icon="@drawable/ic_launcher"        android:label="@string/app_name"        android:theme="@style/AppTheme" >        <receiver android:name=".BootCompleteReceiver">            <intent-filter >                <action android:name="android.intent.action.BOOT_COMPLETED"/>            </intent-filter>        </receiver>        <activity             android:name=".MainActivity">            <intent-filter >                <action android:name="android.intent.action.MAIN"/>                <category android:name="android.intent.category.LAUNCHER"/>            </intent-filter>        </activity>    </application></manifest>

我们看到和动态注册其实很相似,也是使用IntentFilter告知Android系统该接收器能响应的动作(当Android系统开机完成后,会发出一条名为 android.intent.action.BOOT_COMPLETED 的广播),当然这里也许要权限的申请。

4).自定义广播 (发送自定义有序广播和自定义标准广播)

前面我们说到的都是接收系统的广播,但这远远是不够的,有时我们需要能够定义自己想要发送的广播去通知程序中的广播接收器做一些响应。
首先,还是需要定义广播接收器来接收广播(本例定义两个广播接收器,一个接收有序广播,一个接收标准广播)
然后,注册广播接收器(动态或静态注册都可以),本例采用静态注册的方式,当然各位看官可以仿照上述的动态方式进行注册。
最后,控制在程序适当的时候发出一条广播接收器能接收到的广播

第一步,分别新建MyBroadcastReceiver (处理标准广播) 和 MyOrderBroadcastReceiver(处理有序广播) 继承自BroadcastReceiver,并分别重写onReceive(…)方法

MyBroadcastReceiver.java :

package com.yu.brocasttext;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.widget.Toast;public class MyBroadcastReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {        Toast.makeText(context, "Receiverd in MyBroadcatReceiver", Toast.LENGTH_SHORT).show();    }}

上述代码,很简单,就是接收到标准广播后,弹出Toast提示

MyOrderBroadcastReceiver.java :

package com.yu.brocasttext;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.util.Log;import android.widget.Toast;public class MyOrderBroadcastReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {        Toast.makeText(context, "Receiver OrderBroadcast In BrocasrTest", Toast.LENGTH_SHORT).show();        String flag = intent.getStringExtra("order_flag");        if(flag.equalsIgnoreCase("yes")){            //截断广播            abortBroadcast();        }else {            //不截断广播            Log.d("MyOrderBroadcastReceiver","OrderBroadcast continue");        }    }}

上述代码也很简单,接收到有序广播后,弹出Toast进行提示,并且根据从Intent中取出的 order_flag 标志,判断是否有必要截断该广播的传播。

第二步,在AndroidManifest.xml文件中注册该广播接收器

 <manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.yu.brocasttext"    android:versionCode="1"    android:versionName="1.0" >    //...,...    <application        android:allowBackup="true"        android:icon="@drawable/ic_launcher"        android:label="@string/app_name"        android:theme="@style/AppTheme" >        <!-- 这里提及receiver的android:exported 属性,该属性表明            当前broadcast Receiver 是否可以从当前应用外部获取Receiver message 。            true,可以;false 不可以。参考链接:            http://blog.csdn.net/watermusicyes/article/details/46460347-->        <receiver             android:name=".MyBroadcastReceiver" >            <intent-filter android:priority="100">                <action android:name="com.yu.brocasttext.MY_BROADCAST"/>            </intent-filter>        </receiver>        <receiver android:name=".MyOrderBroadcastReceiver">            <intent-filter android:priority="100">                <action android:name="com.yu.brocasttext.MY_ORDER_BROADCAST"/>            </intent-filter>        </receiver>        <activity             android:name=".MainActivity">            <intent-filter >                <action android:name="android.intent.action.MAIN"/>                <category android:name="android.intent.category.LAUNCHER"/>            </intent-filter>        </activity>    </application></manifest>

上例中 MyBroadcastReceiver 接收一条值为com.yu.brocasttext.MY_BROADCAST 的广播,因此待会儿在发送广播的时候,我们就需要发出这样的一条广播,同理com.yu.brocasttext.MY_ORDER_BROADCAST。
android:priority属性给广播接收器设置了优先级,优先级比较高的广播接收器可以先收到广播,但是对于标准广播来说这个设置应该是无效的。

第三步,发送广播
首先我们修改布局文件,增加三个按钮,按钮功能分别是一个发送标准广播,一个发送有序广播,一个发送有序广播并让程序截断该广播
布局文件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:orientation="vertical" >    <Button         android:id="@+id/button"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="发送标准广播"/>    <Button         android:id="@+id/button2"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="发送有序广播" />"    <Button         android:id="@+id/button3"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="发送截断有序广播" /></LinearLayout>

在MainActivity中实现按钮的功能,发送相应的广播,如
MainActivity.java :

package com.yu.brocasttext;import android.app.Activity;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.net.ConnectivityManager;import android.net.NetworkInfo;import android.os.Bundle;import android.util.Log;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.Toast;public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        Log.d("BroadcastText", "run at boot");        setContentView(R.layout.activity_main);        //自定义广播        //发送自定义标准广播        Button button = (Button) findViewById(R.id.button);        button.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                Intent intent = new Intent("com.yu.brocasttext.MY_BROADCAST");                //发送标准广播  而且参数是Intent对象,也就是说我们可以传递一些数据给广播接收器                sendBroadcast(intent);            }        });        //发送自定义有序广播        Button button2 = (Button) findViewById(R.id.button2);        button2.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                Intent intent = new Intent("com.yu.brocasttext.MY_ORDER_BROADCAST");                intent.putExtra("order_flag", "no");                //发送有序广播                sendOrderedBroadcast(intent,null);            }        });        //发送自定义有序广播,并在接收器那截断该广播        Button button3 = (Button) findViewById(R.id.button3);        button3.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                Intent intent = new Intent("com.yu.brocasttext.MY_ORDER_BROADCAST");                intent.putExtra("order_flag", "yes");                //发送有序广播                sendOrderedBroadcast(intent,null);            }        });    }}

上面的代码中,我们先使用构建了一个Intent对象,这是我们的”通知意图”, 然后使用函数 sendBroadcast(intent); 发送标准广播;使用 sendOrderedBroadcast(intent,null) 发送有序广播,这个方法接收两个参数,第一个参数是时我们的广播意图,就是告诉系统,我们想发送一条怎样的广播,第二个参数是一个与权限相关的字符串,广播接收器必须同时满足这个权限才能接收到该广播,这里传入null,表示不需要权限声明。
为了更好的证明,我们自定义的广播可以被本程序以外的其他程序所接收到,我们再实现一个新的广播接收程序,让它们注册能够接受相同的广播,如 BroadcastTest2:
实现的方法,基本上和上面相同,实现两个广播接收器类,并使用Toast弹出提示,接着注册广播即可。
先运行BroadcastText2,不退出,然后再运行BroadcastTest,点击按钮发送广播,查看运行情况。
运行结果如图
这里写图片描述
图4.1标准广播1

这里写图片描述
图4.2标准广播2

这里写图片描述
图4.3有序广播1

这里写图片描述
图4.4有序广播2

这里写图片描述
图4.5有序广播被截断

上面的截图可以看出,我们发送的广播可以被本程序即本程序以外的程序接收,并且有序广播还能被截断。

5.本地广播

从上面的例子可以看出,目前为止我们发送或接收的广播都是全局的广播,这样就导致了一个很重要的问题,那就是安全。为了简单的解决这个问题,Android系统为引入了一套本地广播机制,使用这个机制发出的广播只能在应用程序内部进行传播,并且(我更乐意说但是)广播接收器也只能接收本程序发出的广播。本地广播的机制并不复杂,就是使用 LocalBroadcastManager 进行管理广播。话不多说,且看代码:
MainActivity.java :

package com.yu.localbroadcasttest;import android.app.Activity;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.os.Bundle;import android.support.v4.content.LocalBroadcastManager;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;public class MainActivity extends Activity {    //本地广播管理器    private LocalBroadcastManager localBroadcastManager;    //广播接收器    private LocalReceiver localReceiver;    private IntentFilter intentFilter;    private TextView textView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //获取LocalBroadcastManager实例        localBroadcastManager = LocalBroadcastManager.getInstance(this);        textView = (TextView) findViewById(R.id.text_view);        Button localBroadButton = (Button) findViewById(R.id.local_button);        localBroadButton.setOnClickListener(new OnClickListener() {            //点击按钮就发送本地广播            @Override            public void onClick(View v) {                //广播为 com.yu.localbroadcasttest.LOCAL_BROADCAST的Intent对象                Intent intent = new Intent("com.yu.localbroadcasttest.LOCAL_BROADCAST");                //通过Intent向广播接收器传递数据,这里传递一个1000以内的随机数                intent.putExtra("random", ""+Math.random()*1000);                //使用本地广播管理器发送标准广播                localBroadcastManager.sendBroadcast(intent);            }        });        //动态注册广播接收器        intentFilter = new IntentFilter();        //通过IntentFilter告知系统,可接收一条名为com.yu.localbroadcasttest.LOCAL_BROADCAST的广播        intentFilter.addAction("com.yu.localbroadcasttest.LOCAL_BROADCAST");//接收广播的值        //创建广播接收器对象        localReceiver = new LocalReceiver();//接收广播的对象        //通过本地广播管理器,注册广播接收器        localBroadcastManager.registerReceiver(localReceiver, intentFilter);//注册本地广播接收器    }    protected void onDestroy() {        super.onDestroy();        //取消注册        localBroadcastManager.unregisterReceiver(localReceiver);    }    //本地广播接收器    class LocalReceiver extends BroadcastReceiver {        @Override        public void onReceive(Context context, Intent intent) {            //Toast进行提示            Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();            //从Intent中取出传递的数据            String random = intent.getStringExtra("random");            //显示在TextView中            textView.setText("接收广播 : "+random);        }    }}

很熟悉有木有!!!没错,这不就和前面看到的动态注册广播接收器一毛一样?!唯一的区别就是使用了LocalBroadcastManager来进行限制(管理),也就是说这里调用的 注册广播接收器,发送广播,取消广播接收器的方法都是LocalBroadcastManager类中的方法。注释我都写在代码中,不再累述。
布局文件,我这里就不列出,从上面的代码也可以看出,布局文件就是一个TextView用于显示收到的广播消息,还有一个按钮来发送本地广播。运行程序,结果如下图:
这里写图片描述
图5.1发送本地广播前

这里写图片描述
图5.2发送本地广播后

呼~~ 打完收工(新浪就是个坑B啊,发表博文后,代码都展示不完全,更不能忍的是,只成功发表了一半….. 还有一半内容不翼而飞了…..我特么已经写了三遍了…..所以我放弃了新浪)

以上,纯属个人理解,有任何不对之处,还请各大婶指出,不胜感激涕零 T.T.T.T

0 0