你遗漏的Android广播知识点
原理简介
广播是Android系统提供的一种可以在进程或者线程之间的通信,分为广播接受者和发送者;一般来说接受者先注册之间receiver,系统会通过binder机制将其注册到系统的AMS上,当发送者发送广播时,将Intent发送给AMS,AMS会查看已注册的广播,根据其intentFilter和优先级来发送,接受者收到消息后回调它的onReceive方法
注册
广播注册分为静态注册和动态注册,静态注册在AndroidManifest.xml,动态注册一般用在Activity的onResume和onPause里面;
静态注册
静态注册在AndroidManifest里面声明自己receiver,如下:
<receiver
android:name="com.xxx.receiver"
android:exported="true"
android:permission="1"
android:process=":bdservice_v1">
<intent-filter>
<action android:name="com.xxxx.action.PUSH_SERVICE" />
</intent-filter>
</receiver>
name: 广播名字
export: 是否可以接受到其他App的广播
process: 为其创建一个新的进程,进程名
permission: 具有相同权限的才能接受到
intentFilter: 过滤广播,接收特定的广播
一般来说静态注册的广播,当App退出以后也能收到系统发来的广播,但是现在Android系统对App的存活状态进行了严格的查看,而且对广播的intent增加了Flag:FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES,默认是不会发送给STOPPED状态的App
动态注册
动态注册一般用在onResume时注册register,onPause里面取消注册unregister,退出当前这个页面就不会在收到广播
广播分类
- 普通广播
- 有序广播
- 系统广播
- 粘性广播(5.1已废弃)
- 应用内广播(局部广播)
普通广播
Context.sendBroadcast(Intent)发送的普通广播,这类广播是无序的,不确定收到时的先后顺序
有序广播
sendOrderedBroadcast(Intent, permission) 这类广播接受者是有序的,接收是会根据注册的优先级,依次接收,并且接受者收到广播后可以修改广播内容setResult,还可决定是否继续向下发送abort,类似一个责任链模式;接收者的顺序按照protity(在intentFilter内定义)大小决定先后顺序接收,动态注册由于静态注册
系统广播
系统广播是由系统发送,具有特定的Intent意图,常见的有屏幕亮息、电池电量低、电话接收等,由于是系统发送的,不能为其添加Flag,所以App退出了不一定能收到系统广播
应用内广播
//发送
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
manager.sendBroadcast(Intent);
//接收
LocalBroadcastManager.getInstance(this).registerReceiver(BroadcastReceiver, IntentFilter);
//结束时
LocalBroadcastManager.getInstance(this).unregisterReceiver(BroadcastReceiver);
只能当前应用内接收,且必须使用LocalBroadcastManager进行发送接收,其安全性能较好注意局部广播不是binder机制,是handler机制
粘性广播(已废弃)
sendStickyBroadcast() 发送广播时,系统会暂存广播,App退出后收不到没关系,下次启动时会收到
使用建议
广播通常是用于接收App之外的数据消息,如果有这个需求建议使用;如果只是接收当前App内部的消息,建议自己App内部逻辑回调处理,不然使用广播,消息还要去系统走一圈,效率不好,如果处于安全性的考虑,可以使用局部广播
扩展阅读
我们都知道Activity的内部类Handler会引起Activity的内存泄漏,其原因在于Handler内部类持有外部类Activity的引用,而Handler发送消息的Message持有Handler引用,而Message又被MessageQueue添加到队列,而MessageQueue又被Looper持有,而Looper呢?自然被ActivityThread持有,从而导致Activity到Root节点的引用链可达,而GC不认为Activity是垃圾,所以也就不会被回收,而一直保留着!
解决办法就是: 将Handler类型设置为static,切断最开始的引用持有!那同理,内部类BroadcastReceiver,我们使用static,在onResume我们注册register广播,但是不unregister这样可以解决内存泄漏吗?
动态广播引起的内存泄漏
如果按照上面的广播场景,就算设置static仍然还是会引起内存泄漏了,为什么?这就必须要分析register和Activity之间的引用链持有关系!
简单分析下源码!我们都知道registerBroadcastReceiver是Activity自身方法,其层层调用会走到Context的逻辑,而Context是一个ContextImpl类型,在其内部逻辑如下:
注意getOuterContext()参数方法,很关键,而且scheduler传递过来是null
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
return registerReceiverInternal(receiver, getUserId(),
filter, broadcastPermission, scheduler, getOuterContext(), 0);
}
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
IntentFilter filter, String broadcastPermission,
Handler scheduler, Context context, int flags) {
IIntentReceiver rd = null;
if (receiver != null) {
if (mPackageInfo != null && context != null) {
.......
} else {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
将注册的receiver再次封装为ReceiverDispatcher,注意传递进去的context参数,返回
rd,为IIntentReceiver
rd = new LoadedApk.ReceiverDispatcher(
receiver, context, scheduler, null, true).getIIntentReceiver();
}
}
try {
将rd注册到AMS,也就是ActivityManagerService,rd的生命周期会长长
final Intent intent = ActivityManager.getService().registerReceiver(
mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
broadcastPermission, userId, flags);
....
return intent;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
从这里得知,IIntentReceiver会被注册到AMS,AMS那边就不继续分析了,但是IIntentReceiver被AMS持有,说明IIntentReceiver生命周期很长长,我们进入ReceiverDispatcher看看:
registered=true
ReceiverDispatcher(BroadcastReceiver receiver, Context context,
Handler activityThread, Instrumentation instrumentation,
boolean registered) {
if (activityThread == null) {
throw new NullPointerException("Handler must not be null");
}
这里mIIntentReceiver就是传递到AMS的,而它的构造方法传this,也就是ReceiverDispatcher,说明ReceiverDispatcher也会很长的生命周期
mIIntentReceiver = new InnerReceiver(this, !registered);
mReceiver = receiver;
mContext = context;
mActivityThread = activityThread;
mInstrumentation = instrumentation;
mRegistered = registered;
mLocation = new IntentReceiverLeaked(null);
mLocation.fillInStackTrace();
}
strong=true
InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {
mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
强引用持有
mStrongRef = strong ? rd : null;
}
从上可知,ReceiverDispatcher会被InnerReceiver强引用持有,所以ReceiverDispatcher生命周期会长,所以ReceiverDispatcher内部持有的参数生命周期都会很长,其内部的mContext是什么呢?
会不会是就是receiver的外部类Activity呢?
答案是的!
看上面代码,你会发现ReceiverDispatcher传入的context来源于ContextImpl的getOuterContext,它是其成员变量mOuterContext,这个由setOuterContext方法设置进去的!
这里我们要分清楚ContextImpl在App进程里面只有一个,会被ActivityThread和每个Activity持有,而其内部的mOuterContext是外部Context,他会在ActivityThead的performLaunchActivity方法中设置进去:
启动的Activity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
创建Activity
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
......
} catch (Exception e) {
.....
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
......
if (activity != null) {
......
appContext是ContextImpl类型,其设置Activity作为其外部context
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
.....
}
这样就确定其mOuterContext就是Activity了,总结下其引用持有关系!如下图:
广播回调onReceive执行
如上图,IIntentReceiver是一个aidl的接口,会涉及binder通信,当有广播发送到AMS时,会binder调用IIntentReceiver接口,执行performReceive方法,然后在执行ReceiverDispatcher的performReceive方法,如下代码:
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
final Args args = new Args(intent, resultCode, data, extras, ordered,
sticky, sendingUser);
....
把args的runnable添加到主Looper的定时执行
if (intent == null || !mActivityThread.post(args.getRunnable())) {
if (mRegistered && ordered) {
IActivityManager mgr = ActivityManager.getService();
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
"Finishing sync broadcast to " + mReceiver);
通知AMS执行完成
args.sendFinished(mgr);
}
}
}
public final Runnable getRunnable() {
return () -> {
ClassLoader cl = mReceiver.getClass().getClassLoader();
intent.setExtrasClassLoader(cl);
intent.prepareToEnterProcess();
setExtrasClassLoader(cl);
receiver.setPendingResult(this);
调用onreceive方法
receiver.onReceive(mContext, intent);
....
}
如上代码,AMS回调aidl接口,最后把我们的执行onReceiver方法封装到Runnable传递给主线程的Looper执行,这也确定了onReceive方法是在主线程执行的,所以也要注意不要太耗时
最后,看了这么多,发现在引用链ReceiverDispatcher可以不用持有Activity的引用,但是Android为何会如此设计呢?
可能在于onReceive方法通常要与我们的Activity的交互,这样强制让Activity存在,即使出现内存泄漏,从而不会出现奔溃现象!