BroadcastReceiver

注冊方式

1.動態(tài)注冊

IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
registerReceiver(new ScreenBroadcastReceiver(), filter);

另外簸喂,注冊廣播可以通過IntentFilter的 setPriority設(shè)置廣播的優(yōu)先級悲敷。
如:

IntentFilter intentFilter = new IntentFilter(ACTION);

intentFilter.setPriority(1000);
registerReceiver(firstReceiver, intentFilter);

2.靜態(tài)注冊

<receiver android:name=".ScreenBroadcastReceiver">
    <intent-filter>
        <action android:name="android.intent.action.SCREEN_OFF" />
        <action android:name="android.intent.action.USER_PRESENT" />
    </intent-filter>
</receiver>

也可以通過 android:priority來設(shè)置廣播的優(yōu)先級

<receiver android:name=".receiver.FirstReceivcer">
    <intent-filter android:priority="1000">
        <action android:name="com.jack.app.myapplication.action.ORDER_BROADCAST"></action>
    </intent-filter>
</receiver>

使用

public class ScreenBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        action = intent.getAction();
        if (Intent.ACTION_SCREEN_OFF.equals(action)) {              // 鎖屏
            BaseActivity.ISSCREENOFF = true;
        } else if (Intent.ACTION_USER_PRESENT.equals(action)) { // 解鎖
            BaseActivity.ISSCREENOFF = false;
        }
    }
}

無序廣播與有序廣播

  • 普通廣播:異步,發(fā)出時可被所有接受者收到屿良,不可以取消
  • 有序廣播:根據(jù)優(yōu)先級依次傳播,直到有接受者將其終止或所有接受者都不終止它痘昌。可以通過abortBroadcast取消它的繼續(xù)傳播(責(zé)任鏈設(shè)計模式)

有序廣播接收炬转,傳值示例

注冊略辆苔,請看上面的兩者注冊方式,F(xiàn)irstReceivcer 的優(yōu)先級是1000扼劈,SecondReceiver的優(yōu)先級是100.

public class FirstReceivcer extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        int limit = intent.getIntExtra("limit",0);
        if (limit == 1000){
            String msg = intent.getStringExtra("msg");
            Toast.makeText(context,msg,Toast.LENGTH_SHORT).show();
            abortBroadcast();
        } else{
            Bundle b = new Bundle();
            b.putString("new","message from firstReceiver");
            setResultExtras(b);
        }
    }
}

public class SecondReceivcer extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        int limit = intent.getIntExtra("limit", 0);
        if (limit == 100) {
            String msg = intent.getStringExtra("msg");

            /**
             * 取得上一個Receiver增加的信息
             */
            Bundle resultExtras = getResultExtras(true);
            String aNew = resultExtras.getString("new");

            Toast.makeText(context, "get message from first receiver:" + aNew, Toast.LENGTH_SHORT).show();
            abortBroadcast();
        } else {
            Bundle b = new Bundle();
            b.putString("new", "message from secondReceiver");
            setResultExtras(b);
        }
    }
}

在BroadcastReceiver的intent傳遞過程中

  1. 發(fā)送有序廣播 sendOrderedBroadcast
Intent i = new Intent();
i.setAction(ACTION);
if (id == R.id.action_menu0){
    i.putExtra("limit", 100);
    i.putExtra("msg","有序廣播");
    sendOrderedBroadcast(i, null);
}else if (id == R.id.action_menu1){
    i.putExtra("limit", 100);
    i.putExtra("msg","普通廣播");
    sendBroadcast(i);
}
  1. 可以通過abortBroadcast取消廣播的繼續(xù)傳遞
  2. 在有序廣播中驻啤,可以通過
Bundle b = new Bundle();
b.putString("new","message from firstReceiver");
setResultExtras(b);

來設(shè)置加入對廣播傳遞信息的修改〖龀常可以通過getResultExtras取得上一個Receiver增加的信息街佑。

/**
 * 取得上一個Receiver增加的信息
 */
Bundle resultExtras = getResultExtras(true);
String aNew = resultExtras.getString("new");

BroadcastReceiver 工作過程

靜態(tài)注冊的廣播在應(yīng)用安裝的時候由系統(tǒng)自動完成注冊,具體是由 PackageManagerService 來完成整個注冊過程捍靠。這里只分析廣播的動態(tài)注冊的流程。

注冊過程

  1. 通過ContextWrapper
@Override
public Intent registerReceiver(
    BroadcastReceiver receiver, IntentFilter filter) {
    return mBase.registerReceiver(receiver, filter);
}

mBase 是一個ContextImpl對象森逮。

  1. ContextImpl的registerReceiver方法
    registerReceiver 又會去調(diào)用自己的 registerReceiverInternal 榨婆,ReceiverDispatcher是 LoadedApk的靜態(tài)內(nèi)部類。
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
        IntentFilter filter, String broadcastPermission,
        Handler scheduler, Context context) {
    /**
     * IIntentReceiver 一個 Binder 對象褒侧,因為注冊過程是一個跨進(jìn)程通信的過程
     */
    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 {
        return ActivityManagerNative.getDefault().registerReceiver(
                mMainThread.getApplicationThread(), mBasePackageName,
                rd, filter, broadcastPermission, userId);
    } catch (RemoteException e) {
        return null;
    }
}

ActivityManagerNative.getDefault()就是AMS良风。
這里關(guān)注下 ReceiverDispatcher 的 getReceiverDispatcher 實(shí)現(xiàn),其先創(chuàng)建了 ReceiverDispatcher對象rd闷供,再通過
rd.getIIntentReceiver();
返回 IIntentReceiver 對象烟央,如下:

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();
    }
}
  1. 由于注冊廣播真正的過程是在AMS中,我們看看AMS的 registerReceiver歪脏。
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {
            ...
    rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                userId, receiver);
    mRegisteredReceivers.put(receiver.asBinder(), rl);
    
    BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
            permission, callingUid, userId);
    rl.add(bf);
    if (!bf.debugCheck()) {
        Slog.w(TAG, "==> For Dynamic broadcast");
    }
    mReceiverResolver.addFilter(bf);
    ...
            
}

最終會把遠(yuǎn)程的 InnerReceiver 對象以及 IntentFilter 對象存儲起來疑俭,整個廣播的注冊就完成了。

廣播的發(fā)送和接收過程

這里只分析普通廣播的發(fā)送過程婿失。廣播的發(fā)送和接受钞艇,其本質(zhì)是一個過程的兩個階段啄寡。我們從廣播的發(fā)送說起:
Context 的 sendBroadcast 是一個抽象方法。和注冊的過程一樣哩照,ContextWrapper 的sendBroadcast同樣將事情交給了 ContextImpl來處理挺物。

1. ContextImpl 的 sendBroadcast 過程如下:

@Override
public void sendBroadcast(Intent intent) {
    warnIfCallingFromSystemProcess();
    String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
    try {
        intent.prepareToLeaveProcess();
        ActivityManagerNative.getDefault().broadcastIntent(
                mMainThread.getApplicationThread(), intent, resolvedType, null,
                Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
                getUserId());
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
}

ContextImpl 幾乎什么事都沒有干,直接向 AMS 發(fā)起了一個異步請求用于發(fā)送廣播飘弧。

2. AMS 的 broadcastIntent 源碼如下:

public final int broadcastIntent(IApplicationThread caller,
        Intent intent, String resolvedType, IIntentReceiver resultTo,
        int resultCode, String resultData, Bundle resultExtras,
        String[] requiredPermissions, int appOp, Bundle options,
        boolean serialized, boolean sticky, int userId) {
    enforceNotIsolatedCaller("broadcastIntent");
    synchronized(this) {
        intent = verifyBroadcastLocked(intent);

        final ProcessRecord callerApp = getRecordForAppLocked(caller);
        final int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();
        final long origId = Binder.clearCallingIdentity();
        int res = broadcastIntentLocked(callerApp,
                callerApp != null ? callerApp.info.packageName : null,
                intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
                requiredPermissions, appOp, null, serialized, sticky,
                callingPid, callingUid, userId);
        Binder.restoreCallingIdentity(origId);
        return res;
    }
}

broadcastIntent 調(diào)用了 broadcastIntentLocked 识藤。broadcastIntentLocked 代碼比較多,看起來比較復(fù)雜次伶。

// By default broadcasts do not go to stopped apps.
intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);

從 Android3.1開始廣播就具有這種特性-默認(rèn)情況下廣播不會發(fā)送給已經(jīng)停止的應(yīng)用痴昧,這樣做是為了防止廣播無意間或者在不必要的時候調(diào)起已經(jīng)停止運(yùn)行的應(yīng)用。在Android3.1中為Intent添加了兩個標(biāo)記位学少,分別為

1.FLAG_EXCLUDE_STOPPED_PACKAGES
表示不包含已經(jīng)停止的應(yīng)用剪个,這個時候廣播不會發(fā)送給已經(jīng)停止的應(yīng)用
2.FLAG_INCLUDE_STOPPED_PACKAGES
表示包含已經(jīng)停止的應(yīng)用,這個時候廣播會發(fā)送給已經(jīng)停止的應(yīng)用

如果確實(shí)需要調(diào)起未啟動的應(yīng)用版确,那么只需要為廣播的 Intent 添加 FLAG_INCLUDE_STOPPED_PACKAGES 標(biāo)記即可扣囊。當(dāng) FLAG_EXCLUDE_STOPPED_PACKAGES 和 FLAG_INCLUDE_STOPPED_PACKAGES 兩種標(biāo)記并存時,以 FLAG_INCLUDE_STOPPED_PACKAGES 為準(zhǔn)绒疗。

在 broadcastIntentLocked 內(nèi)部侵歇,會根據(jù) intent-filter 查找出匹配的廣播接受者并經(jīng)過一系列條件過濾。最終會把滿足條件的廣播接受者添加到 BroadcastQueue 中吓蘑。

int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
if (!ordered && NR > 0) {
    // If we are not serializing this broadcast, then send the
    // registered receivers separately so they don't wait for the
    // components to be launched.
    final BroadcastQueue queue = broadcastQueueForIntent(intent);
    BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
            callerPackage, callingPid, callingUid, resolvedType, requiredPermission,
            appOp, registeredReceivers, resultTo, resultCode, resultData, map,
            ordered, sticky, false, userId);
    if (DEBUG_BROADCAST) Slog.v(
            TAG, "Enqueueing parallel broadcast " + r);
    final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
    if (!replaced) {
        queue.enqueueParallelBroadcastLocked(r);
        queue.scheduleBroadcastsLocked();
    }
    registeredReceivers = null;
    NR = 0;
}

3. 下面看下 BroadcastQueue 中廣播的發(fā)送過程的實(shí)現(xiàn)

public void scheduleBroadcastsLocked() {
    if (DEBUG_BROADCAST) Slog.v(TAG, "Schedule broadcasts ["
            + mQueueName + "]: current="
            + mBroadcastsScheduled);

    if (mBroadcastsScheduled) {
        return;
    }
    mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
    mBroadcastsScheduled = true;
}

scheduleBroadcastsLocked并沒有立即執(zhí)行發(fā)送廣播惕虑,而是發(fā)送了一個BROADCAST_INTENT_MSG類型的消息,BroadcastQueue 收到消息后會調(diào)用 BroadcastQueue 的 processNextBroadcast 方法磨镶,processNextBroadcast 方法對普通廣播的處理如下:

// First, deliver any non-serialized broadcasts right away.
while (mParallelBroadcasts.size() > 0) {
    r = mParallelBroadcasts.remove(0);
    r.dispatchTime = SystemClock.uptimeMillis();
    r.dispatchClockTime = System.currentTimeMillis();
    final int N = r.receivers.size();
    if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast ["
            + mQueueName + "] " + r);
    for (int i=0; i<N; i++) {
        Object target = r.receivers.get(i);
        if (DEBUG_BROADCAST)  Slog.v(TAG,
                "Delivering non-ordered on [" + mQueueName + "] to registered "
                + target + ": " + r);
        deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false);
    }
    addBroadcastToHistoryLocked(r);
    if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast ["
            + mQueueName + "] " + r);
}

無序廣播存儲在 mParallelBroadcasts 中溃蔫,系統(tǒng)會遍歷 mParallelBroadcasts 并將其中的廣播發(fā)送給他們所有的接受者。具體通過 deliverToRegisteredReceiverLocked 實(shí)現(xiàn)琳猫,deliverToRegisteredReceiverLocked 負(fù)責(zé)講一個廣播發(fā)送給一個特定的接受者伟叛,它內(nèi)部調(diào)用了 performReceiveLocked 來完成具體的發(fā)送過程。

 performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
    new Intent(r.intent), r.resultCode, r.resultData,
    r.resultExtras, r.ordered, r.initialSticky, r.userId);

performReceiveLocked 實(shí)現(xiàn)如下

private static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
        Intent intent, int resultCode, String data, Bundle extras,
        boolean ordered, boolean sticky, int sendingUser) throws RemoteException {
    // Send the intent to the receiver asynchronously using one-way binder calls.
    if (app != null) {
        if (app.thread != null) {
            // If we have an app thread, do the call through that so it is
            // correctly ordered with other one-way calls.
            /**
             * app.thread 仍然指 ApplicatonThread
             */
            app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
                    data, extras, ordered, sticky, sendingUser, app.repProcState);
        } else {
            // Application has died. Receiver doesn't exist.
            throw new RemoteException("app.thread must not be null");
        }
    } else {
        receiver.performReceive(intent, resultCode, data, extras, ordered,
                sticky, sendingUser);
    }
}

4. 在 ApplicationThread 的 scheduleRegisteredReceiver 中脐嫂,通過 InnerReceiver 來實(shí)現(xiàn)廣播的接收统刮。

public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
        int resultCode, String dataStr, Bundle extras, boolean ordered,
        boolean sticky, int sendingUser, int processState) throws RemoteException {
    updateProcessState(processState, false);
    receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
            sticky, sendingUser);
}

通過 InnerReceiver 的performReceive方法會調(diào)用 LoadedApk.ReceiverDispatcher 的performReceive。方法如下:

public void performReceive(Intent intent, int resultCode, String data,
            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
        if (ActivityThread.DEBUG_BROADCAST) {
            int seq = intent.getIntExtra("seq", -1);
            Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq
                    + " to " + mReceiver);
        }
        Args args = new Args(intent, resultCode, data, extras, ordered,
                sticky, sendingUser);
        if (!mActivityThread.post(args)) {
            if (mRegistered && ordered) {
                IActivityManager mgr = ActivityManagerNative.getDefault();
                if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                        "Finishing sync broadcast to " + mReceiver);
                args.sendFinished(mgr);
            }
        }
    }

}

在上面的代碼中账千,會創(chuàng)建一個 Args 對象侥蒙,并通過 mActivityThread 的post方法執(zhí)行Args中的邏輯,而 Args實(shí)現(xiàn)了 Runnable 接口匀奏。mActivityThread 是一個Handler鞭衩,它其實(shí)就是 ActivityThread 中的 mH,mH 的類型是 ActivityThread的內(nèi)部類H.在Args的run方法中有如下代碼:

final BroadcastReceiver receiver = mReceiver;
receiver.setPendingResult(this);
receiver.onReceive(mContext, intent);

很顯然,這個時候 BroadcastReceiver 的 onReceiver 執(zhí)行了。Ok醋旦!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恒水,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子饲齐,更是在濱河造成了極大的恐慌钉凌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捂人,死亡現(xiàn)場離奇詭異御雕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)滥搭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門酸纲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瑟匆,你說我怎么就攤上這事闽坡。” “怎么了愁溜?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵疾嗅,是天一觀的道長。 經(jīng)常有香客問我冕象,道長代承,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任渐扮,我火速辦了婚禮论悴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘墓律。我一直安慰自己膀估,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布耻讽。 她就那樣靜靜地躺著察纯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪齐饮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天笤昨,我揣著相機(jī)與錄音祖驱,去河邊找鬼。 笑死瞒窒,一個胖子當(dāng)著我的面吹牛捺僻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼匕坯,長吁一口氣:“原來是場噩夢啊……” “哼束昵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起葛峻,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤锹雏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后术奖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體礁遵,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年采记,在試婚紗的時候發(fā)現(xiàn)自己被綠了佣耐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡唧龄,死狀恐怖兼砖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情既棺,我是刑警寧澤讽挟,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站援制,受9級特大地震影響戏挡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晨仑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一褐墅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧洪己,春花似錦妥凳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拱镐,卻和暖如春艘款,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沃琅。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工哗咆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人益眉。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓晌柬,卻偏偏與公主長得像姥份,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子年碘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容