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. 發送匹配到的粘連廣播

這里可以看出粘連廣播的特性,就是如果注冊是廣播是粘連廣播那么就在注冊的時候就會給所有接受這個粘連廣播的接收器發送廣播。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容