处女男学Android(十五)---Android 广播机制之Broadcast Receiver



前言



转载请标明出处:http://blog.csdn.net/wlwlwlwl015/article/details/44280423

这篇文章写了一半在草稿箱放了半年多

处女男学Android(十五)---Android 广播机制之Broadcast Receiver_第1张图片

之前由于换工作的原因搁置了Android的学习,但相比服务端开发,我还是对Android更有感觉一些,所以决定以后会继续坚持做Android,废话不多说,继续我的安卓学习之路,半年多没接触Android发现移动端的开发技术确实是日新月异,和传统的服务端开发技术相比更新速度太快了,听说Broadcast已渐渐的被EventBus这个东东替代,但是针对系统广播来讲应该还是无法被替代的,不管是否过时,学了再说吧。学习Broadcast Recivier参考的主要是官方文档和郭神的《第一行代码》之中的部分内容,本篇blog就记录一下我的学习过程以及自己的一些总结体会吧!



广播简介



为了方便系统级别的消息通知,Android也引入了一整套完整的API用于发送广播or接收广播,而用于接收广播的“接收器”也正是大名鼎鼎的Android四大组件之一的BroadcastReceiver,首先看一下官方文档中对它的介绍:

处女男学Android(十五)---Android 广播机制之Broadcast Receiver_第2张图片

简单看一下,BroadCastReceiver可以接收到由sendBroadcast()发出的意图(广播),如果不需要跨应用去发送广播,可以考虑使用LocalBroadcastManager去替代它,LocalBroadcastManager可以带给你更为高效的实现,并且让你避免考虑当其它应用接收或发送你的广播时的一些安全问题。你也可以通过Context.registerReceiver()方法去动态的注册一个BroadcastReceiver实例,亦或是通过在AndroidMannifest.xml中定义一个<receiver>标签来静态的声明一个BroadcastReceiver。

根据这段话了解到注册一个广播接收器有两种方式:

  • 动态注册,通过Context.registerReceiver()
  • 静态注册,通过<receiver>标签
稍候我们会通过这两种方式分别来接收“系统广播”感受一下,这里又提到了系统广播,什么是系统广播?广播又有哪些分类?其实广播可以按照两种方式划分,首先可以按照系统广播or自定义广播来分,也可以按照标准广播or有序广播来分,具体稍后介绍。那么系统广播都有哪些?比如当网络状态发生变化(开/关WIFI、数据)、开/关机、时间/时区发生改变等状况发生时,Android系统都会发出一条广播,这些都属于系统广播。

上图文档中的这段话还提到了这个类——LocalBroadcastManager,官方文档中也做了解释——do not need to send broadcast across applications的时候考虑使用它,也就是说由LocalBroadcastManager这个类发送的广播只能在应用内部传递,那么用它又有什么好处呢?广播机制本身就应该是一个全局性的通知,那么LocalBroadcastManager存在的价值又体现在哪里?其实这个就是“本地广播”,具体内容会在后面做详细说明:

这里有一条提示信息,如果你在Activity的onResume方法中注册了一个广播接收器,那么你应当在Activity的onPause方法中去解除注册该广播接收器。不要在Activity暂停时再去接收intents,这将会减少不必要的系统开销。不要在Activity的onSaveInstanceState方法中去解除注册某个广播接收器,因为当用户回到历史栈时onSaveInstanceState方法不会被调用。

上面这段话是官方给我们的一些小建议,包括怎样以最佳的方式去注册/解除注册一个广播接收器等。到目前为止我们大致了解了接收广播的一些概念和方法,那么现在就通过一个例子来感受一下如何接收系统广播。前面也提到了注册接收器的两种方式——静态or动态,下面就分别写一下,首先是动态注册。

动态注册BroadcastReceiver

我们从上面的文档中可以看到,动态注册是通过Context.registerReceiver()这个方法实现的,那么现在就具体看一下这个方法的说明:
处女男学Android(十五)---Android 广播机制之Broadcast Receiver_第3张图片
可以在源码中看到,这个方法会接收2个参数:BroadcastReceiver和IntentFilter。简单的讲,第一个参数定义了一个广播接收器,即接收广播的容器,而第二个参数定义了广播内容,即需要接收的是什么样的广播。那么下面就看一个完整的小例子。
package com.wl.broadcastdemo;

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.widget.Toast;

public class MainActivity extends Activity {

	private IntentFilter intentFilter;
	private NetWorkRecevier netWorkRecevier;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		intentFilter = new IntentFilter();
		intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
		netWorkRecevier = new NetWorkRecevier();
		registerReceiver(netWorkRecevier, intentFilter);
	}

	class NetWorkRecevier extends BroadcastReceiver {
		@Override
		public void onReceive(Context context, Intent intent) {
			// TODO Auto-generated method stub
			ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
			NetworkInfo networkInfo = connectivityManager
					.getActiveNetworkInfo();
			if (networkInfo != null && networkInfo.isAvailable()) {
				Toast.makeText(context, "network is available",
						Toast.LENGTH_SHORT).show();
			} else {
				Toast.makeText(context, "network is unavailable",
						Toast.LENGTH_SHORT).show();
			}

		}

	}

	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		unregisterReceiver(netWorkRecevier);
	}

}

这个例子很简单,就是监听网络状态并进行提示,当我们启用或停用网络时(wifi或数据),系统就会发出一条“网络状态改变”的系统广播,这时候只要我们注册了 BroadcastReceiver,那么这个Receiver就可以完美的receive到这么一条系统广播,还有一点需要注意,Android系统为了保证应用的安全性做了规定,如果程序需要访问一些系统的关键性信息需要声明权限(比如获取网络状态),所以这里我们需要在Manifest文件中声明权限:
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
这样就OK了,运行程序之后,我们改变手机的网络状态,均可以看到Toast的提示信息。

静态注册BroadcastReceiver

除了上面的动态注册,当然还可以静态注册,即官方文档中说的: statically publish an implementation through the <receiver> tag in your AndroidManifest.xml.在manifest文件中通过receiver标签来注册广播接收器。那么什么情况下需要静态注册,或者说静态注册广播接收器有什么好处呢?相比较动态注册接收器,我们不难发现:需要动态注册接收器的话是通过registerReceiver方法去实现的,而只要调用registerReceiver方法那么必定要执行onCreat方法,换句话说,就是必须启动应用程序才能接收到广播!而静态注册的方式可以实现不启动应用就可以接收广播!那么何时需要不启用应用就要接收广播呢?想想我们做服务端开发的时候,是不是在生产环境的服务器上的许多服务需要开机启动?比如:数据库服务、缓存服务、负载均衡的Nginx服务等等,那么回到Android中来看,如果我们需要做一个“开机启动”的APP,那么必然要通过静态注册BroadcastReceiver去实现了!示例代码依然是郭神书中的例子,很简单,首先自定义一个BroadcastReceiver,然后在manifest文件中通过receiver标签去声明这个广播接收器即可,下面是最简单的一个自定义广播接收器:
package com.wl.broadcastdemo.receiver;

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) {
		// TODO Auto-generated method stub
		Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();
	}

}

然后我们需要在manifest文件中声明它以及相关权限:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wl.broadcastdemo"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="21" />

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <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" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name=".receiver.BootCompleteReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>


可以看到第12行声明了监听开机广播的权限,而28-32行则通过<receiver>标签定义了广播接收器,而<intent-filter>标签则定义了我们需要接收什么样的广播,同上面的动态注册的方式是一样的道理。不过这种方式在Android高版本中貌似不管用了,我用的魅族MX4,FlyMe4.2对应的安卓4.4.2就没效果,所以想真正实现APP“开机启动”的这种效果再去搜索一下其它可行的解决方案吧!


标准广播(Normal broadcasts)和有序广播(Ordered broadcasts)


下面继续看官方文档来学习。

如上图所示,Android提供了两个主要的类可以用于接收广播,Normal broadcastsOrdered broadcasts,这就是我们在上面提到的一种分类方式——标准广播有序广播。如上述介绍,发送标准广播可以通过Context.sendBroadcast,而发送有序广播可以通过Context.sendOrderedBroadcast,那么它们之间有何区别呢?如果翻译了上述文档片段的话那么一定很清楚了:
Normal broadcast are completelyasynchronous(标准广播是异步的),All receivers of the broadcast are run in an undefined order, often at the same time(所有的广播接收器会在同一时间接收到广播且没有先后顺序),This is more efficient(这样更高效),but means that receivers cannot use the result or abort APIs included here(但是也意味着无法被截断)。.
有序广播基本是和标准广播相反的,即:同步、一次只有一个接收器可以收到广播、优先级高的接收器会优先收到广播、前面的接收器可以截断广播导致后面的无法再接收。了解了理论之后,下面就写个demo具体看一看它们的使用方法和应用场景吧。首先是Normal boradcasts,创建一个Android Project:
处女男学Android(十五)---Android 广播机制之Broadcast Receiver_第4张图片
首先是Activity的代码:
package com.wl.broadcastdemo;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {
	
	private Button btnSend;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		btnSend = (Button) findViewById(R.id.btn_send);
		btnSend.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				sendBroadcast(new Intent("com.wl.broadcastdemo.MY_BROADCAST"));
			}
		});
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle action bar item clicks here. The action bar will
		// automatically handle clicks on the Home/Up button, so long
		// as you specify a parent activity in AndroidManifest.xml.
		int id = item.getItemId();
		if (id == R.id.action_settings) {
			return true;
		}
		return super.onOptionsItemSelected(item);
	}
}

可以看到非常简单,界面上有一个Button,点击Button会发送一条normal broadcast,我们指定了本条广播的intent为“com.wl.broadcastdemo.MY_BROADCAST” ,这个值我们随意自定义即可。下面是我们自定义的接收器类:
package com.wl.broadcastdemo.receiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class MyReceiver extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		// TODO Auto-generated method stub
		Toast.makeText(context, "MyReceiver", Toast.LENGTH_LONG).show();
	}

}

同样很简单,通过Toast弹出“MyReceiver”。最后看一下Manifest文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wl.broadcastdemo"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="19" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver android:name=".receiver.MyReceiver" >
            <intent-filter>
                <action android:name="com.wl.broadcastdemo.MY_BROADCAST" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

我们通过<receiver>标签的方式声明了一个广播接收器,用来接收intent为“com.wl.broadcastdemo.MY_BROADCAST”的广播。很明显,当我们点击Button时,会发出一条广播,而同时又会被这个receiver收到,也就是“自发自收”的简单效果,我们运行一下项目可以看到运行结果:
处女男学Android(十五)---Android 广播机制之Broadcast Receiver_第5张图片

但是这样的“自发自收”没有体现出normal broadcast的一个特点,即:异步传播。也就是说我们一旦发送了一条normal broadcast,那么其它APP的接收器也可以接收到,下面就通过创建一个新的项目来验证这一点,再创建一个Android Project——BroadcastDemo2:
处女男学Android(十五)---Android 广播机制之Broadcast Receiver_第6张图片
这个项目更加简单,由于仅仅是用来验证是否能接收到广播,所以我们的Activity无需任何代码,下面看一下receiver和manifest文件:
package com.wl.broadcastdemo2.receiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class MyReceiver extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		// TODO Auto-generated method stub
		Toast.makeText(context, "MyReceiver in other app", Toast.LENGTH_LONG).show();
	}

}
可以看到这个MyReceiver和刚才的没有任何变化,唯独修改了Toast的文本用来区分。再看一下manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wl.broadcastdemo2"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="19" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name=".receiver.MyReceiver">
            <intent-filter>
                <action android:name="com.wl.broadcastdemo.MY_BROADCAST"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>

可以看到这里也没有丝毫的变化,注意一下<receiver>的子标签<action>,通过name属性可以发现这个监听器也是监听的intent为“com.wl.broadcastdemo.MY_BROADCAST”的这样的一条广播,也就是说,根据normal broadcast的特性——异步传播,我们两个APP监听的是同一条广播,那么我们两个项目中的MyReceiver的onReceive回调方法都应被执行,也就是说Toast会弹两遍,下面我们首先在模拟器中安装BroadcastDemo2,然后再回到BroadcastDemo中点击send按钮看一下效果:
处女男学Android(十五)---Android 广播机制之Broadcast Receiver_第7张图片

可以通过演示图看到,当我们成功安装了BroadcastDemo2之后,再回到BroadcastDemo中点击Send MyBroadCast按钮,Toast确实会弹出两次,即使BroadcastDemo2没有处于运行状态也可以正常显示Toast信息,这就是normal broadcast。
根据官方的分类我们发现还有一种广播叫OrderedBroadcast,它和标准广播的区别上面也有介绍,需要注意以下三点:
第一,发送有序广播的方式和发送标准广播略有不同:
sendOrderedBroadcast(new Intent("com.wl.broadcastdemo.MY_BROADCAST"), null);

可以看到这个发送有序广播的方法多了一个参数,我们这里给的是NULL,在源码中我们可以看到对这个参数的说明信息:

可以看到这是一个权限相关的字符串,也有是说接收方必须有指定的权限,才能收到发送端发出的这条有序广播,这里我们给NULL即可,暂不深入研究。
第二,有序广播是同步的,谁的优先级高谁先收到。在上面的官方文档中可以看到这样一句话:  The order receivers run in can be controlled with the android:priority attribute of the matching intent-filter; receivers with the same priority will be run in an arbitrary order.简单的说,就是在<receiver>的子标签<intent-filter>标签中通过指定属性android:priority属性来指定优先级的。下面我们略微修改刚才的Demo来验证一下这一点,比如我希望:当点击Send按钮的时候,首先让另一个APP的接收器收到广播,再让按钮所在的当前APP收到广播,除了修改sendBroadcast为sendOrderedBroadcast之外,只需如上所述的加上android:priority即可,下面依次看一下两个项目的配置片段,首先是按钮所在的当前APP的配置,其次是另外一个仅仅接收广播的APP:
        <receiver android:name=".receiver.MyReceiver" >
            <intent-filter android:priority="60">
                <action android:name="com.wl.broadcastdemo.MY_BROADCAST" />
            </intent-filter>
        </receiver>
        <receiver android:name=".receiver.MyReceiver">
            <intent-filter android:priority="100">
                <action android:name="com.wl.broadcastdemo.MY_BROADCAST"/>
            </intent-filter>
        </receiver>

当我们重新运行了两个项目之后再次点击send按钮就能看到我们预期的效果了:
处女男学Android(十五)---Android 广播机制之Broadcast Receiver_第8张图片
结果显而易见,通过指定优先级就可以准确的控制各个APP中的Receiver接收广播的顺序了。
第三,就是有序广播可以被截断,当优先级较高的receiver收到广播时,可以通过截断来使得其它APP的receiver无法继续接收本条广播,我们在API中可以看到这样一个方法:
 
abort就是中止、截断的意思,很明显当我们在程序中调用这个方法,那么就会使得传播到当前APP的广播被截断。当我们在BroadcastDemo2中的MyReceiver的onReceiver方法中的Toast语句下添加这行代码:
package com.wl.broadcastdemo2.receiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class MyReceiver extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		// TODO Auto-generated method stub
		Toast.makeText(context, "MyReceiver in other app", Toast.LENGTH_LONG).show();
		abortBroadcast();
	}

}

那么,BroadcastDemo的MyReceiver就不可能再接收到该条广播了,也就是说Toast只会弹出一次。运行效果如下所示:
处女男学Android(十五)---Android 广播机制之Broadcast Receiver_第9张图片


关于安全(Security)



目前为止我们了解了如何收发广播,以及在APP之间传递广播,那么是否考虑到存在一些安全问题呢?广播传递的是intent,而intent又能携带数据,再想想广播的全局特性,安全问题不言而喻。官方为我们列举了4件需要考虑的事情:
  • The Intent namespace is global. Make sure that Intent action names and other strings are written in a namespace you own, or else you may inadvertently conflict with other applications.
  • When you use registerReceiver(BroadcastReceiver, IntentFilter), any application may send broadcasts to that registered receiver. You can control who can send broadcasts to it through permissions described below.
  • When you publish a receiver in your application's manifest and specify intent-filters for it, any other application can send broadcasts to it regardless of the filters you specify. To prevent others from sending to it, make it unavailable to them with android:exported="false".
  • When you use sendBroadcast(Intent) or related methods, normally any other application can receive these broadcasts. You can control who can receive such broadcasts through permissions described below. Alternatively, starting with ICE_CREAM_SANDWICH, you can also safely restrict the broadcast to a single application with Intent.setPackag
而最后给出的解决方案,也就是blog开头提到的一个类——LocalBroadcastManagerNone of these issues exist when using LocalBroadcastManager, since intents broadcast it never go outside of the current process.)。看来一开始我的理解有些局限,广播的全局特性是针对系统广播来讲,而应用内部也需要相应的广播,LocalBroadcastManager提供的正是APP内的本地广播的安全解决方案。用法也很简单,只不过不再使用Context的相关API,而是通过LocalBroadcastManager的API去注册接收器、解除注册、发送广播等等。下面贴出一个简单的示例代码:
package com.wl.broadcastdemo;

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.widget.Button;
import android.widget.Toast;

public class SecondActivity extends Activity {

	private Button btnSend;

	private IntentFilter intentFilter;
	// 自定义Receiver
	private LocalReceiver localReceiver;
	private LocalBroadcastManager localBroadcastManager;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_second);
		// 实例化LocalBroadcastManager
		localBroadcastManager = LocalBroadcastManager.getInstance(this);
		btnSend = (Button) findViewById(R.id.btn_send_two);
		btnSend.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				Intent intent = new Intent("com.wl.broadcastdemo.LOCAL_BROADCAST");
				// 发送本地广播
				localBroadcastManager.sendBroadcast(intent);
			}
		});
		intentFilter = new IntentFilter();
		intentFilter.addAction("com.wl.broadcastdemo.LOCAL_BROADCAST");
		localReceiver = new LocalReceiver();
		// 注册广播接收器
		localBroadcastManager.registerReceiver(localReceiver, intentFilter);
	}

	class LocalReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {
			// TODO Auto-generated method stub
			Toast.makeText(context, "received local broadcast!", Toast.LENGTH_LONG).show();
		}

	}

	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		// 解除注册
		localBroadcastManager.unregisterReceiver(localReceiver);
	}

}

界面上之后一个Button,由于代码基本和动态注册广播接收器一致所以此处不再做过多说明,最后看一下运行效果:
处女男学Android(十五)---Android 广播机制之Broadcast Receiver_第10张图片


总结



本篇blog记录了一下Broadcast Receiver的学习过程,关于LocalBroadcastManager介绍的内容比较少是因为现在APP内的广播已逐渐被EventBus所替代,所以对这部分内容就不作太深入的研究了。关于Android的学习还会继续,希望能对学习Android的小伙伴有所帮助,The End。

你可能感兴趣的:(android,广播机制,Broadcast,receiver)