你遗漏的Android广播知识点

你遗漏的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存在,即使出现内存泄漏,从而不会出现奔溃现象!

posted @ 2019-04-18 09:40  帅气好男人_jack  阅读(14)  评论(0编辑  收藏  举报  来源