Android 四大组件之 Broadcast

来源:互联网 发布:java web代码混淆原理 编辑:程序博客网 时间:2024/06/05 23:36

发送一个广播的方式

sendBroadcast() 普通的广播

普通广播对于多个接收者来说是完全异步的,通常每个接收者都无需等待即可以接收到广播,接收者相互之间不会有影响。对于这种广播,接收者无法终止广播,即无法阻止其他接收者的接收动作。

先执行优先级由高到低,相同的优先级先执行代码注册的广播接收器

sendOrderedBroadcast() 有序的广播

按优先级高到低顺序接收,优先级高的广播接收器可以中断广播来实现拦截

拦截方法:abortBroadcast(),比如短信就是一个有序广播

android:priority,这个属性的范围在-1000到1000.

sendStickyBroadcast() 有粘性的广播

发送粘性广播需要相应的权限:

<uses-permission android:name="android.permission.BROADCAST_STICKY"/>

系统会保留最后一个Intent,当有匹配的广播接收器启动,就会收到广播

注:广播接收器的执行时间在10秒内,否则会ANR(Application Not Response)应用程序无响应

长时间任务可以启动Activity : Intent.FLAG_ACTIVITY_NEW_TASK

注册广播

创建自己的BroadcastReceiver对象,我们需要继承android.content.BroadcastReceiver,并实现其onReceive方法,然后注册广播。

静态注册

静态注册是在AndroidManifest.xml文件中配置的

<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="android.intent.action.MY_BROADCAST"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>

动态注册

动态注册需要在代码中动态的指定广播地址并注册,通常我们是在 Activity 或Service 调用 registerReceiver注册一个广播。

MyReceiver receiver = new MyReceiver();
 
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.MY_BROADCAST");
 
registerReceiver(receiver, filter);

两种方式都需要IntentFIlter。

注意,registerReceiver是android.content.ContextWrapper类中的方法,Activity和Service都继承了ContextWrapper,所以可以直接调用。在实际应用中,我们在Activity或Service中注册了一个BroadcastReceiver,当这个Activity或Service被销毁时如果没有解除注册,系统会报一个异常,提示我们是否忘记解除注册了。所以,记得在特定的地方执行解除注册操作:

@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}

执行这样行代码就可以解决问题了。注意,这种注册方式与静态注册相反,不是常驻型的,也就是说广播会跟随程序的生命周期。

权限

存取的权限可以通过在发送方的Intent或者接收方的Intent中强制指定。

在发送一个broadcast时强制指定权限,就必须提供一个非空的peemission参数给sendBroadcast(Intent, String)或者是sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handel, int, String, Bundle).只有那些拥有这些权限(通过在ANdroidManifest.xml文件中相应的声明标签)的receiver能够接收这些broadcast。

在接收一个broadcast时强制指定权限,就必须在注册receiver时提供一个非空的permission参数--无论是在调用registerReceiver(BroadcastReceiver, IntentFilter, String, android.os.Handler)或者是通过再AndroidManifest.xml文件中通过静态标签来声明。只有那些拥有这些权限(通过在相应的AndroidManifest.xml文件中查询标签来获知)的发送方将能够给这个receiver发送Intent。

对于安全和权限的详细内容请查看Security and Permission文档。

Receiver的生命周期

一个BroadcastReceiver的对象仅仅在调用onReceiver(COntext, Intent)的时间中有效。一旦你的代码从这个函数中返回,那么系统就认为这个对象应该结束了,不能再被激活。

你在onReceive(Context, Intent)中的实现有着非常重要的影响:任何对于异步操作的请求都是不允许的,因为你可能需要从这个函数中返回去处理异步的操作,但是在那种情况下,BroadcastReceiver将不会再被激活,因此系统就会再异步操作之前杀死这个进程。

特别是,你不应该再一个BroadcastReceiver中显示一个对话框或者绑定一个服务。对于前者(显示一个对话框),你应该用NotificationManagerAPI来替代,对于后者(绑定一个服务),你可以使用Context.startService()发送一个命令给那个服务来实现绑定效果。

进程的生命周期

一个正在执行BroadcastReceiver(也就是,正在执行onReceive(COntext, Intent)方法)的进程被认为是一个前台的进程,将会一直运行,除非系统处于内存极度低的情况下。

一旦从OnReceive()方法中返回,这个BroadcastReceiver将不会再被激活,此时它的主进程就和任何其他运行于此应用程序中的组件拥有相同的优先级。这一点非常重要,如果进程仅仅只是拥有BroadReceiver(一个普遍的情况是用户从不或者是最近没有和它进行交互),因此一旦它从onReceive()方法中返回时,系统就会认为进程是空的并且主动的杀死它,以便这些资源可以被其他重要的进程利用。

这意味着对于耗时的操作,可以采用将Service和BroadcastReceiver结合使用以确保执行这个操作的进程在整个执行过程中都保持激活状态。

本地广播

为了能够简单地解决广播的安全性问题,Android引入了一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播

public class MainActivity extends Activity {
 
private IntentFilter intentFilter;
 
private LocalReceiver localReceiver;
 
private LocalBroadcastManager localBroadcastManager;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
localBroadcastManager = LocalBroadcastManager.getInstance(this); // 获取实例
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.broadcasttest. LOCAL_BROADCAST");
localBroadcastManager.sendBroadcast(intent); // 发送本地广播
}
});
intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter); // 注册本地广播监听器
}
@Override
protected void onDestroy() {
super.onDestroy();
localBroadcastManager.unregisterReceiver(localReceiver);
}
 
class LocalReceiver extends BroadcastReceiver {
 
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
}
 
}
 
}

本地广播的几点优势

  1. 可以明确地知道正在发送的广播不会离开我们的程序,因此不需要担心机密数据泄漏的问题。
  2. 其他的程序无法将广播发送到我们程序的内部,因此不需要担心会有安全漏洞的隐患。
  3. 发送本地广播比起发送系统全局广播将会更加高效。

栗子

开机启动服务

我们经常会有这样的应用场合,比如消息推送服务,需要实现开机启动的功能。要实现这个功能,我们就可以订阅系统“启动完成”这条广播,接收到这条广播后我们就可以启动自己的服务了。我们来看一下BootCompleteReceiver和MsgPushService的具体实现:

package com.scott.receiver;
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
 
public class BootCompleteReceiver extends BroadcastReceiver {
 
private static final String TAG = "BootCompleteReceiver";
 
@Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, MsgPushService.class);
context.startService(service);
Log.i(TAG, "Boot Complete. Starting MsgPushService...");
}
 
}
package com.scott.receiver;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
 
public class MsgPushService extends Service {
 
private static final String TAG = "MsgPushService";
 
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate called.");
}
 
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand called.");
return super.onStartCommand(intent, flags, startId);
}
 
@Override
public IBinder onBind(Intent arg0) {
return null;
}
}

然后我们需要在AndroidManifest.xml中配置相关信息:

<!-- 开机广播接受者 -->
<receiver android:name=".BootCompleteReceiver">
<intent-filter>
<!-- 注册开机广播地址-->
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- 消息推送服务 -->
<service android:name=".MsgPushService"/>

我们看到BootCompleteReceiver注册了“android.intent.action.BOOT_COMPLETED”这个开机广播地址,从安全角度考虑,系统要求必须声明接收开机启动广播的权限,于是我们再声明使用下面的权限:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

经过上面的几个步骤之后,我们就完成了开机启动的功能,将应用运行在模拟器上,然后重启模拟器,如果我们查看已运行的服务就会发现,MsgPushService已经运行起来了

网络状态变化

在某些场合,比如用户浏览网络信息时,网络突然断开,我们要及时地提醒用户网络已断开。要实现这个功能,我们可以接收网络状态改变这样一条广播,当由连接状态变为断开状态时,系统就会发送一条广播,我们接收到之后,再通过网络的状态做出相应的操作。下面就来实现一下这个功能:

package com.scott.receiver;
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import android.widget.Toast;
 
public class NetworkStateReceiver extends BroadcastReceiver {
 
private static final String TAG = "NetworkStateReceiver";
 
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "network state changed.");
if (!isNetworkAvailable(context)) {
Toast.makeText(context, "network disconnected!", 0).show();
}
}
 
/**
* 网络是否可用
*
* @param context
* @return
*/
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo[] info = mgr.getAllNetworkInfo();
if (info != null) {
for (int i = 0; i < info.length; i++) {
if (info[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
return false;
}
 
}

再注册一下这个接收者的信息:

<receiver android:name=".NetworkStateReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>

因为在isNetworkAvailable方法中我们使用到了网络状态相关的API,所以需要声明相关的权限才行,下面就是对应的权限声明:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

我们可以测试一下,比如关闭WiFi,看看有什么效果。

电量变化

如果我们阅读软件,可能是全屏阅读,这个时候用户就看不到剩余的电量,我们就可以为他们提供电量的信息。要想做到这一点,我们需要接收一条电量变化的广播,然后获取百分比信息,这听上去挺简单的,我们就来实现以下:

package com.scott.receiver;
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
import android.util.Log;
 
public class BatteryChangedReceiver extends BroadcastReceiver {
 
private static final String TAG = "BatteryChangedReceiver";
 
@Override
public void onReceive(Context context, Intent intent) {
int currLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); //当前电量
int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1); //总电量
int percent = currLevel * 100 / total;
Log.i(TAG, "battery: " + percent + "%");
}
 
}

然后再注册一下广播接地址信息就可以了:

<receiver android:name=".BatteryChangedReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>

当然,有些时候我们是要立即获取电量的,而不是等电量变化的广播,比如当阅读软件打开时立即显示出电池电量。我们可以按以下方式获取:

Intent batteryIntent = getApplicationContext().registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
int currLevel = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
int total = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
int percent = currLevel * 100 / total;
Log.i("battery", "battery: " + percent + "%");

强制下线

public class ForceOfflineReceiver extends BroadcastReceiver {
 
@Override
public void onReceive(final Context context, Intent intent) {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
dialogBuilder.setTitle("Warning");
dialogBuilder.setMessage("You are forced to be offline. Please try to login again.");
dialogBuilder.setCancelable(false);
dialogBuilder.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCollector.finishAll(); // 销毁所有活动
Intent intent = new Intent(context, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent); // 重新启动LoginActivity
}
});
AlertDialog alertDialog = dialogBuilder.create();
// 需要设置AlertDialog的类型,保证在广播接收器中可以正常弹出
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
alertDialog.show();
}
 
}

需要权限 android.permission.SYSTEM_ALERT_WINDOW


http://blog.csdn.net/xianming01/article/details/7381070

http://blog.csdn.net/liuhe688/article/details/6955668

0 0