Android广播注册过程

简介

广播作为Android系统四大组件之一,起得作用是相当大,安卓系统进程之间通信是相当频繁,而且解耦工作是重中之重,那么作为这样的设计初衷,所以诞生了广播。我们今天就来一步步看看安卓中广播是怎么样进行工作的。

使用

  1. 自定义广播接受者
public class MyReceiver extends BroadcastReceiver {  
      
    private static final String TAG = "MyReceiver";  
      
    @Override  
    public void onReceive(Context context, Intent intent) {  
        String msg = intent.getStringExtra("msg");  
        Log.i(TAG, msg);  
    }  
}  
  1. 注册广播
    • 静态注册
    <receiver android:name=".MyReceiver">  
        <intent-filter>  
            <action android:name="android.intent.action.MY_BROADCAST"/>  
            <category android:name="android.intent.category.DEFAULT" />  
        </intent-filter>  
    </receiver>  
    
    • 动态注册
    MyReceiver receiver = new MyReceiver();  
    IntentFilter filter = new IntentFilter();  
    filter.addAction("android.intent.action.MY_BROADCAST");  
    registerReceiver(receiver, filter);  
    
  2. 发送广播
public void send(View view) {  
    Intent intent = new Intent("android.intent.action.MY_BROADCAST");  
    intent.putExtra("msg", "hello receiver.");  
    sendBroadcast(intent);  
}
  1. 解除广播
@Override  
protected void onDestroy() {  
    super.onDestroy();  
    unregisterReceiver(receiver);  
}  

补充:

有序广播的定义案例:


<receiver android:name=".FirstReceiver">  
    <intent-filter android:priority="1000">  
        <action android:name="android.intent.action.MY_BROADCAST"/>  
        <category android:name="android.intent.category.DEFAULT" />  
    </intent-filter>  
</receiver>  
<receiver android:name=".SecondReceiver">  
    <intent-filter android:priority="999">  
        <action android:name="android.intent.action.MY_BROADCAST"/>  
        <category android:name="android.intent.category.DEFAULT" />  
    </intent-filter>  
</receiver>  
<receiver android:name=".ThirdReceiver">  
    <intent-filter android:priority="998">  
        <action android:name="android.intent.action.MY_BROADCAST"/>  
        <category android:name="android.intent.category.DEFAULT" />  
    </intent-filter>  
</receiver>

源码分析

注册广播源码分析

我们通过ContextImpl对象最后会进入如下代码:

private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
        IntentFilter filter, String broadcastPermission,
        Handler scheduler, Context context) {
    IIntentReceiver rd = null;
    if (receiver != null) {
        //mPackageInfo的类型为LoadedApk
        if (mPackageInfo != null && context != null) {
            //类型是Handler,没有设置的时候用主线程和处理
            if (scheduler == null) {
                scheduler = mMainThread.getHandler();
            }
            rd = mPackageInfo.getReceiverDispatcher(
                receiver, context, scheduler,
                mMainThread.getInstrumentation(), true);
        } else {
            if (scheduler == null) {
                scheduler = mMainThread.getHandler();
            }
            rd = new LoadedApk.ReceiverDispatcher(
                    receiver, context, scheduler, null, true).getIIntentReceiver();
        }
    }
    try {
        final Intent intent = ActivityManagerNative.getDefault().registerReceiver(
                mMainThread.getApplicationThread(), mBasePackageName,
                rd, filter, broadcastPermission, userId);
        if (intent != null) {
            intent.setExtrasClassLoader(getClassLoader());
            intent.prepareToEnterProcess();
        }
        return intent;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

mPackageInfo.getReceiverDispatcher()这个方法主要在LoadedApk的private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers根据BroadcastReceiver r拿到LoadedApk.ReceiverDispatcher如果没有,则new ReceiverDispatcher将信息封装到ReceiverDispatcher中然后保存到数组中,当new ReceiverDispatcher会创建(mIIntentReceiver = new InnerReceiver(this, !registered);这个是一个binder)

反正上面代码执行完成就要有LoadedApk.ReceiverDispatcher的创建,并且要调用AMS的registerReceiver()
同样在new ReceiverDispatcher的时候就会创建IIntentReceiver对象,这个对象是继承Binder的。

在使用广播的时候,我们就会遇到一种情况,就是广播导致的anr,由于上面handler是主线程的handler,所在在oReceiver的时候,一旦超时就会引发anr问题,我们可能会想和service处理办法一样,由于service的onCreat也是在主线程中调用的,我们处理会启动一个子线程,在子线程中完成我们需要的工作。同理我们可不可以在onReceive方法中启动子线程呢?

答案是不可以!如果我们用了子线程可能会产生这样一种情况,onReceive的生命周期很短可能我们在子线程中的工作没有做完,AMS就销毁进程了,这个时候就达不到我们需要的结果。

所以广播也给我们准备了一种办法,就是PendingResult,它是定义在BroadcastReceiver中的:

public abstract class BroadcastReceiver {
    private PendingResult mPendingResult;

具体的做法是:

  1. 先调用BroadcastReceiver的goAsync函数得到一个PendingResult对象,
  2. 然后将该对象放到工作线程中去释放。

这样onReceive函数就可以立即返回而不至于阻塞主线程。
同时,Android系统将保证BroadcastReceiver对应进程的生命周期,
直到工作线程处理完广播消息后,调用PendingResult的finish函数为止。

private class MyBroadcastReceiver extends BroadcastReceiver {
    ..................
    public void onReceive(final Context context, final Intent intent) {
        //得到PendingResult
        final PendingResult result = goAsync();  
        //放到异步线程中执行
        AsyncHandler.post(new Runnable() {  
            @Override  
            public void run() {  
                handleIntent(context, intent);//可进行一些耗时操作  
                result.finish();  
            }  
        });  
    } 
}
final class AsyncHandler {  
    private static final HandlerThread sHandlerThread = new HandlerThread("AsyncHandler");  
    private static final Handler sHandler;  
    static {  
        sHandlerThread.start();  
        sHandler = new Handler(sHandlerThread.getLooper());  
    }  
    public static void post(Runnable r) {  
        sHandler.post(r);  
    }  
    private AsyncHandler() {}  
}  

细节对应到源码

public final PendingResult goAsync() {
    PendingResult res = mPendingResult;
    mPendingResult = null;
    return res;
}

在onReceive函数中执行异步操作,主要目的是避免一些操作阻塞了主线程,
但整个操作仍然需要保证在10s内返回结果,尤其是处理有序广播和静态广播时。
毕竟AMS必须要收到返回结果后,才能向下一个BroadcastReceiver发送广播。

源码过程

ContextImpl.java

    private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
            IntentFilter filter, String broadcastPermission,
            Handler scheduler, Context context) {
        IIntentReceiver rd = null;
        if (receiver != null) {
            if (mPackageInfo != null && context != null) {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = mPackageInfo.getReceiverDispatcher(
                    receiver, context, scheduler,
                    mMainThread.getInstrumentation(), true);
            } else {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = new LoadedApk.ReceiverDispatcher(
                        receiver, context, scheduler, null, true).getIIntentReceiver();
            }
        }
        try {
            final Intent intent = ActivityManagerNative.getDefault().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName,
                    rd, filter, broadcastPermission, userId);
            if (intent != null) {
                intent.setExtrasClassLoader(getClassLoader());
                intent.prepareToEnterProcess();
            }
            return intent;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}

首先传递进来的核心参数:

  • BroadcastReceiver receiver
  • Context context
  • IntentFilter filter

此方法第一步:

  • scheduler = mMainThread.getHandler();//获取主线程
  • 获取IIntentReceiver,是将receiver封装的对象
rd = mPackageInfo.getReceiverDispatcher(
                    receiver, context, scheduler,
                    mMainThread.getInstrumentation(), true);
  • 最后通过AMS的registerReceiver将信息注册到AMS中

我们分析一下这个IIntentReceiver rd到底是什么?

public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
        Context context, Handler handler,
        Instrumentation instrumentation, boolean registered) {
    synchronized (mReceivers) {
        LoadedApk.ReceiverDispatcher rd = null;
        ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
        if (registered) {
            map = mReceivers.get(context);
            if (map != null) {
                rd = map.get(r);
            }
        }
        if (rd == null) {
            rd = new ReceiverDispatcher(r, context, handler,
                    instrumentation, registered);
            if (registered) {
                if (map == null) {
                    map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
                    mReceivers.put(context, map);
                }
                map.put(r, rd);
            }
        } else {
            rd.validate(context, handler);
        }
        rd.mForgotten = false;
        return rd.getIIntentReceiver();
    }
}

这里面的几个核心数据结构:

  • mReceivers
      private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers;
    
  • map
      ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map;
    
  • rd
      rd = new ReceiverDispatcher(r, context, handler, instrumentation, registered);
    

由上面代码可以看出来是:

  • 将BroadcastReceiver(r)封装到ReceiverDispatcher(rd)
  • 将r和rd对应起来到一个map
  • context和map对应起来

最后形成的效果是:

ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>>

方法最后返回final IIntentReceiver.Stub mIIntentReceiver;这个是在ReceiverDispatcher的构造中进行创建的,传入的参数是ReceiverDispatcher这个对象本身,也就是说receiver,context等等所有的引用都在这里面。同时它是一个Binder对象,说明可以夸进程传输数据。

上面的信息说明,在getReceiverDispatcher这个方法中将receiver,context等等都保存起来,并且统一在LoadedApk列表中进行保存。于此同时返回一个Binder对象给调用者(注册广播的对象)

小节:

到这里我们明白ContextImpl对象的registerReceiverInternal方法的目的是将信息保存到LoadedApk中,并且生成Binder对象这个对象中存有所有recevire和context等重要引用,最后AMS就可以通过这个Binder对象的引用和当前注册广播的进程进行通信。

AMS.registerReceiver()

public Intent registerReceiver(IApplicationThread caller, String callerPackage,
        IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {

part-1

    ...............
    //保存系统内已有的sticky广播
    ArrayList<Intent> stickyIntents = null;
    ...............
    synchronized(this) {
        if (caller != null) {
            //根据IApplicationThread得到对应的进程信息
            callerApp = getRecordForAppLocked(caller);
            if (callerApp == null) {
                //抛出异常,即不允许未登记的进程注册BroadcastReceiver
                ...............
            }
            if (callerApp.info.uid != Process.SYSTEM_UID &&
                    !callerApp.pkgList.containsKey(callerPackage) &&
                    !"android".equals(callerPackage)) {
                //抛出异常,即注册进程必须携带Package名称
                ..................
            }
        } else {
            .............
        }
        ......................
        //为了得到IntentFilter中定义的Action信息,先取出其Iterator
        Iterator<String> actions = filter.actionsIterator();
        if (actions == null) {
            ArrayList<String> noAction = new ArrayList<String>(1);
            noAction.add(null);
            actions = noAction.iterator();
        }
        //可能有多个action,所以这里得到action的迭代器actions
        
        int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
        while (actions.hasNext()) {
            //依次比较BroadcastReceiver关注的Action与Stick广播是否一致
            String action = actions.next();
            for (int id : userIds) {
                ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(id);
                if (stickies != null) {
                    //Sticky广播中,有BroadcastReceiver关注的
                    //可能有多个Intent,对应的Action相似,在此先做一个初步筛选
                    ArrayList<Intent> intents = stickies.get(action);
                    if (intents != null) {
                        //如果stickyIntents==null,则创建对应列表
                        if (stickyIntents == null) {
                            stickyIntents = new ArrayList<Intent>();
                        }
                        //将这些广播保存到stickyIntents中
                        stickyIntents.addAll(intents);
                    }
                }
            }
        }
    }

    ArrayList<Intent> allSticky = null;
    //stickyIntents中保存的是action匹配的
    if (stickyIntents != null) {
        //用于解析intent的type
        final ContentResolver resolver = mContext.getContentResolver();
        // Look for any matching sticky broadcasts...
        for (int i = 0, N = stickyIntents.size(); i < N; i++) {
            Intent intent = stickyIntents.get(i);
            //此时进一步判断Intent与BroadcastReceiver的IntentFilter是否匹配
            if (filter.match(resolver, intent, true, TAG) >= 0) {
                if (allSticky == null) {
                    allSticky = new ArrayList<Intent>();
                }
                allSticky.add(intent);
            }
        }
    }

第一部分主要工作是:

  • 得到action的迭代器
  • 得到UserAll,当前user的所有粘连广播列表添加到sticktIntents中
  • stickyIntents中进行匹配将所有对应粘连广播的intent添加到allSticky中。

part-2

    //得到粘连挂广播中的第一个广播
    Intent sticky = allSticky != null ? allSticky.get(0) : null;
    ................
    synchronized (this) {
        ..............
        //一个Receiver可能监听多个广播,多个广播对应的BroadcastFilter组成ReceiverList
        ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
        if (rl == null) {
            //首次注册,rl为null,进入此分支
            //新建BroadcastReceiver对应的ReceiverList
            rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                    userId, receiver);
            if (rl.app != null) {
                //添加到注册进程里
                rl.app.receivers.add(rl);
            } else {
                try {
                    //rl监听receiver所在进程的死亡消息
                    receiver.asBinder().linkToDeath(rl, 0);
                } catch (RemoteException e) {
                    return sticky;
                }
                rl.linkedToDeath = true;
            }
            //保存到AMS上
            mRegisteredReceivers.put(receiver.asBinder(), rl);
        } ..........
        ............

这里牵扯到一个数据结构:

  • mRegisteredReceivers
final HashMap<IBinder, ReceiverList> mRegisteredReceivers;

这个主要保存的是注册的广播的receiver和ReceiverList,再看看ReceiverList这个数据结构

class ReceiverList extends ArrayList<BroadcastFilter>
主要是存BroadcastFilter

怎么理解呢?就是一个广播接受者可以对应多个广播过滤器,也就是BroadcastFilter,所以就形成了HashMap<IBinder, ReceiverList>这种结构

这种结构也就是说,一个广播可以注册多次,每次filter不同,则会将filter添加到对应的广播接收器中。

第二部分做的工作主要有:
将filter保存到对应的BroadcastFilter中,然后将BroadcastFilter保存到对应的ReceiverList中,也就是说一个recevier对应多个Filter。

part-3

        //创建当前IntentFilter对应的BroadcastFilter
        //AMS收到广播后,将根据BroadcastFilter决定是否将广播递交给对应的BroadcastReceiver
        //一个BroadcastReceiver可以对应多个IntentFilter
        //这些IntentFilter对应的BroadcastFilter共用一个ReceiverList
        BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
                permission, callingUid, userId);
        rl.add(bf);
        ................
        mReceiverResolver.addFilter(bf);

        // Enqueue broadcasts for all existing stickies that match
        // this filter.
        //allSticky不为空,说明有粘性广播需要发送给刚注册的BroadcastReceiver
        if (allSticky != null) {
            ArrayList receivers = new ArrayList();
            //receivers记录bf
            receivers.add(bf);

            final int stickyCount = allSticky.size();
            for (int i = 0; i < stickyCount; i++) {
                Intent intent = allSticky.get(i);
                //根据intent的flag (FLAG_RECEIVER_FOREGROUND)决定是使用gBroadcastQueue还是BgBroadcastQueue
                BroadcastQueue queue = broadcastQueueForIntent(intent);

                //创建广播对应的BroadcastRecord
                //BroadcastRecord中包含了Intent,即知道要发送什么广播;
                //同时其中含有receivers,内部包含bf,即知道需要将广播发送给哪个BroadcastReceiver
                BroadcastRecord r = new BroadcastRecord(queue, intent, null,
                        null, -1, -1, null, null, AppOpsManager.OP_NONE, null, receivers,
                        null, 0, null, null, false, true, true, -1);

                //加入到BroadcastQueue中,存入的是并发广播对应的队列
                queue.enqueueParallelBroadcastLocked(r);

                //将发送BROADCAST_INTENT_MSG,触发AMS发送广播
                queue.scheduleBroadcastsLocked();
            }
        }
    }
}

将通过intent匹配的粘连广播进行发送。

小节上面1.2.3部分

  1. 将系统中粘连广播根据intent进行匹配并得到保存在粘连广播列表
  2. 将传递进来的receiver和对应的intentfilterList对应起来保存到对应列表
  3. 发送匹配到的粘连广播

这里可以看出粘连广播的特性,就是如果注册是广播是粘连广播那么就在注册的时候就会给所有接受这个粘连广播的接收器发送广播。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容