深入Android系统(十一)AMS-4-广播管理

本文深入剖析BroadcastReceiver机制,包括其工作原理、应用场景及广播类型。详细介绍了动态与静态注册过程、粘性广播特性以及如何处理耗时操作。

BroadcastReceiver 管理

Broadcast实质上是提供了一种更灵活的使用Intent的方式。

无论是启动Activity,还是Service,其实都是通过Intent的发送和过滤来调用应用中的功能,但是ActivityServiceContentProvider所完成的功能是固定的

而对于BroadcastReceiver可以做任意的事情,同时广播的发送和接收方式也更加丰富灵活

理解BroadcastReceiver

BroadcastReceiver是一种很简单的组件,甚至在ActivityThread中都没有管理它的数据结构。

BroadcastReceiver的本质就是通过Intent来执行应用中的一个方法,在应用中并不需要一个长期存在的对象

虽然应用中没有专门的数据结构来管理BroadcastReceiver,但是在AMS中还是需要的,因为BroadcastReceiver可以在运行时动态向AMS注册,AMS中需要有数据结构来管理动态接收者

BroadcastReceiver可以通过两种方式接收广播

  • 通过AndroidManifest中的<receiver/>标签注册
  • 通过AMSregisterReceiver()接口注册

关于广播使用上的知识大家可以在官网学习:广播基础知识

我们看下BroadcastReceiver类相关的重要定义:

public abstract class BroadcastReceiver {
   
   
    private PendingResult mPendingResult;
    public abstract void onReceive(Context context, Intent intent);
    public final PendingResult goAsync() {
   
   
        PendingResult res = mPendingResult;
        mPendingResult = null;
        return res;
    }
}

实现一个广播接收器只需要继承BroadcastReceiver,然后实现它的抽象方法onReceive()就可以了。这也是最简单的广播的用法。

不过从BroadcastReceiver的类定义中还有一个mPendingResult变量和goAsync()方法,它们有什么作用呢?
它们主要是针对当接收到广播后需要做一些耗时操作,而且还有可能需要返回处理结果的情况

  • 我们知道BroadcastReceiver对象的onReceive()如果长时间不返回,会引发ANR
  • 因此如果要执行耗时的操作,必须在其他线程中完成

mPendingResult变量的存在就是为了上述需求。而goAsync()方法提供了获取mPendingResult变量的接口。

goAsync()方法的实现可以看到

  • 方法在将mPendingResult对象的指针返回后。同时也会将mPendingResult的值置为null

按照官方说法:

一旦执行了goAsync()方法,在onReceive()结束前,需要将mPendingResult对象的指针传递到新线程中去,在新线程处理完成后必须调用mPendingResult对象的finish()方法来通知AMS

我们看下PendingResultfinish()方法:

    public final void finish() {
   
   
        if (mType == TYPE_COMPONENT) {
   
   
            final IActivityManager mgr = ActivityManager.getService();
            if (QueuedWork.hasPendingWork()) {
   
   
                QueuedWork.queue(new Runnable() {
   
   
                    @Override public void run() {
   
   
                        if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                                "Finishing broadcast after work to component " + mToken);
                        sendFinished(mgr);
                    }
                }, false);
            } else {
   
   
                if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                        "Finishing broadcast to component " + mToken);
                sendFinished(mgr);
            }
        } else if (mOrderedHint && mType != TYPE_UNREGISTERED) {
   
   
            if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                    "Finishing broadcast to " + mToken);
            final IActivityManager mgr = ActivityManager.getService();
            sendFinished(mgr);
        }
    }
    public void sendFinished(IActivityManager am) {
   
   
        synchronized (this) {
   
   
            ...
            try {
   
   
                ...
                if (mOrderedHint) {
   
   
                    am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
                            mAbortBroadcast, mFlags);
                } else {
   
   
                    am.finishReceiver(mToken, 0, null, null, false, mFlags);
                }
            } catch (RemoteException ex) {
   
   
            }
        }
    }

核心操作就是调用AMSfinishReceiver()方法,这才是当前广播接收器的结束的标志。

正常情况下(没有执行过goAsync())当onReceive()调用完成后,AMS检测到mPendingResult不为空,便会自动执行finish()方法,而对于执行过goAsync()的广播接收器,AMS便不会主动执行finish()

这个finish()方法很重要,本章最后也有它的身影

广播的分类

从广播的发送方式来划分,有4类广播。

  • 普通广播:通过Context中的方法sendBroadcast()sendBroadcastAsUser()发送的广播属于普通广播。
    • 普通广播的特点是发送给系统当前的所有注册的接收者
    • 接收者接收到广播的顺序也是不确定的
  • 有序广播:通过Context中的方法sendOrderedBroadcast()sendOrderedBroadcastAsUser()发送的广播属于有序广播。
    • 有序广播的发送顺序是按照接收者的优先级来决定的,系统默认范围是-1000-1000,但实际上没有明确的数值限定,可以到int最大值
    • 接收器的优先级通过匹配的intent-filterandroid:priority属性来控制
    • 具有相同优先级的接收器将按随机顺序运行(这部分书中和官网的描述并不一致,看完源码再说啦)
    • 当接收器逐个顺序执行时,接收器可以向下传递结果,也可以完全中止广播,使其不再传递给其他接收器
  • 粘性广播:通过Context中的方法sendStickyBroadcast()sendOrderedBroadcastAsUser()发送的广播属于粘性广播。
    • 和前两个广播不同,粘性广播能将广播发送给系统中以后注册的接收者,甚至是新安装应用中的接收者
    • 官网解释出于安全因素,在Android 9上,这两个接口被标记为了Deprecated
    • 不过这并不影响它在Framework中的使用,我们看个官网的例子:
        IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        // 对于一个粘性广播来说,注册监听时便可以得到当前电池状态的 Intent
        Intent batteryStatus = context.registerReceiver(null, ifilter);
    
  • 本地广播:通过LocalBroadcastManager.sendBroadcast发送的广播属于本地广播
    • 本地广播会将广播发送给与发送者位于同一应用中的接收者
    • 当不需要跨应用发送广播时,建议使用本地广播
    • 这种实现方法的效率更高(无需进行进程间通信)
    • 而且无需担心其他应用在收发广播时带来的任何安全问题

广播的数据结构

AMS中,所有注册的广播接收者都放在成员变量mRegisteredReceivers中,定义如下:

final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();

mRegisteredReceivers是一个HashMap,其中

  • key为接收者的IBinder对象

  • value为接收者的对象ReceiverList

    • 因为一个接收者中可能包含多个IntentFilter,所以ReceiverList是一个数组
    final class ReceiverList extends ArrayList<BroadcastFilter>
    
    • BroadcastFilter则是继承了IntentFilter,定义如下:
    final class BroadcastFilter extends IntentFilter {
         
         
        final ReceiverList receiverList; // 所属 receiver 的引用
        final String packageName;        // 所在应用的包名
        final String requiredPermission; // 发送广播时需要声明的权限字符串
        final int owningUid;             // 所在应用的 uid
        final int owningUserId;          // 所在应用的 userid
        ...
    }
    

ReceiverList的关键定义如下:

final class ReceiverList extends ArrayList<BroadcastFilter>
        implements IBinder.DeathRecipient {
   
   
    final ActivityManagerService owner;
    public final IIntentReceiver receiver; // 用户进程中定义的 IntentReceiver
    public final ProcessRecord app;        // 所属用户进程的 ProcessRecord 对象
    public final int pid;                  // 所属用户进程的pid
    public final int uid;                  // 所属用户进程的uid
    public final int userId;               // 用户id
    BroadcastRecord curBroadcast = null;   //
    boolean linkedToDeath = false;         // 是否注册了 binder 死亡监听
}

发送广播时,AMS中收到的广播消息首先会保存在mBroadcastQueues对象中,然后再发送给用户进程中的接收者。mBroadcastQueues是一个只有两个元素的数组,定义如下:

final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[2];

此外,AMS中还定义了两个变量:

    BroadcastQueue mFgBroadcastQueue;
    BroadcastQueue mBgBroadcastQueue;

AMS在构造方法中将这两个变量和mBroadcastQueues集合进行了关联:

    public ActivityManagerService(Context systemContext) {
   
   
        ...
        mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
                "foreground", BROADCAST_FG_TIMEOUT, false);
        mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
                "background", BROADCAST_BG_TIMEOUT, true);
        mBroadcastQueues[0] = mFgBroadcastQueue;
        mBroadcastQueues[1] = mBgBroadcastQueue;
        ...
    }
  • mFgBroadcastQueue用来保存带有FLAG_RECEIVER_FOREGROUND标志的广播,它要求接收者进程以foreground的优先级运行,这样执行的更快。
  • 如果不特别指定,一般的广播都会保存在mBgBroadcastQueue

我们看下BroadcastQueue类的主要结构:

public final class BroadcastQueue {
   
   
    final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
    final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();
}
  • mParallelBroadcasts用来保存所有的普通广播
  • mOrderedBroadcasts用来保存所有的有序广播

粘性广播在哪里呢?
系统中所有的粘性广播都保存在AMS的成员变量mStickyBroadcasts中,定义如下:

    final SparseArray<ArrayMap<String, ArrayList<Intent>>> mStickyBroadcasts =
            new SparseArray<ArrayMap<String, ArrayList<Intent>>>();
  • mStickyBroadcasts是一个SparseArray类型的数组,使用用户Id作为索引,保存的是ArrayMap对象
  • ArrayMap对象储存的是某个用户发送的所有的粘性广播,每条记录以Intentaction字符串作为索引,保存的内容是一个ArrayList对象
  • ArrayList对象其中储存了包含该actionIntent

广播的注册过程

动态注册是通过接口registerReceiver()完成的。应用注册receiver时,并不是直接调用AMS的接口来完成的,而是通过Context类中的方法,因为AMS的接口需要提供应用的ApplicationThread类的IBinder对象来作为参数,应用中得到这个对象比较麻烦。

Context中的registerReceiver()最终调用的是ContextImplregisterReceiverInternal()方法,代码如下:

class ContextImpl{
   
   
    private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
            IntentFilter filter, String broadcastPermission,
            Handler scheduler, Context context, int flags) {
   
   
        ...
        try {
   
   
            final Intent intent = ActivityManager.getService().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
                    broadcastPermission, userId, flags);
            if (intent != null) {
   
   
                intent.setExtrasClassLoader(getClassLoader());
                intent.prepareToEnterProcess();
            }
            return intent;
        } catch (RemoteException e) {
   
   
            throw e.rethrowFromSystemServer();
        }
    }
}

registerReceiverInternal()方法最终调用的是AMSregisterReceiver()方法,代码如下:

    public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter, String permission, int userId,
            int flags) {
   
   
        // 确保不是 isolated 进程
        enforceNotIsolatedCaller("registerReceiver");
        ArrayList<Intent> stickyIntents = null;
        ProcessRecord callerApp = null;
        final boolean visibleToInstantApps
                = (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
        int callingUid
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值