前言
转载请标明出处:http://blog.csdn.net/wlwlwlwl015/article/details/44280423
这篇文章写了一半在草稿箱放了半年多
,
之前由于换工作的原因搁置了Android的学习,但相比服务端开发,我还是对Android更有感觉一些,所以决定以后会继续坚持做Android,废话不多说,继续我的安卓学习之路,半年多没接触Android发现移动端的开发技术确实是日新月异,和传统的服务端开发技术相比更新速度太快了,听说Broadcast已渐渐的被EventBus这个东东替代,但是针对系统广播来讲应该还是无法被替代的,不管是否过时,学了再说吧。学习Broadcast Recivier参考的主要是官方文档和郭神的《第一行代码》之中的部分内容,本篇blog就记录一下我的学习过程以及自己的一些总结体会吧!
广播简介
为了方便系统级别的消息通知,Android也引入了一整套完整的API用于发送广播or接收广播,而用于接收广播的“接收器”也正是大名鼎鼎的Android四大组件之一的BroadcastReceiver,首先看一下官方文档中对它的介绍:
简单看一下,BroadCastReceiver可以接收到由sendBroadcast()发出的意图(广播),如果不需要跨应用去发送广播,可以考虑使用LocalBroadcastManager去替代它,LocalBroadcastManager可以带给你更为高效的实现,并且让你避免考虑当其它应用接收或发送你的广播时的一些安全问题。你也可以通过Context.registerReceiver()方法去动态的注册一个BroadcastReceiver实例,亦或是通过在AndroidMannifest.xml中定义一个<receiver>标签来静态的声明一个BroadcastReceiver。
根据这段话了解到注册一个广播接收器有两种方式:
- 动态注册,通过Context.registerReceiver()
- 静态注册,通过<receiver>标签
动态注册BroadcastReceiver
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
<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>
标准广播(Normal broadcasts)和有序广播(Ordered broadcasts)
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);
}
}
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收到,也就是“自发自收”的简单效果,我们运行一下项目可以看到运行结果:
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按钮看一下效果:
sendOrderedBroadcast(new Intent("com.wl.broadcastdemo.MY_BROADCAST"), null);
<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按钮就能看到我们预期的效果了:
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();
}
}
关于安全(Security)
- 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
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,由于代码基本和动态注册广播接收器一致所以此处不再做过多说明,最后看一下运行效果: