Android 四大組件通信核心

前言

系列文章:

Android Activity創(chuàng)建到View的顯示過(guò)程
Android 四大組件通信核心
Android 系統(tǒng)啟動(dòng)到App 界面完全展示終于明白(圖文版)

我們知道Android 四大組件:Activity/Service/Broadcast/ContentProvider 能夠進(jìn)行跨進(jìn)程通信问潭,它們均是借助Binder實(shí)現(xiàn)跨進(jìn)程通信的能力虑啤。四者之間又有千絲萬(wàn)縷的聯(lián)系带族,本篇將從宏觀角度分析四者通信核心以及聯(lián)系與區(qū)別荆陆。
通過(guò)本篇文章屈溉,你將了解到:

1榜掌、四大組件通信基礎(chǔ)
2檐春、Activity 與AMS 交互
3况芒、Service 與AMS 交互
4唠椭、Broadcast 與AMS 交互
5跳纳、ContentProvider 與AMS 交互

1、四大組件通信基礎(chǔ)

四大組件基本功能

先來(lái)看看四大組件的基本功能:


image.png

四者如何進(jìn)行交互的呢贪嫂?

image.png

由圖可知寺庄,只要拿到了Context 對(duì)象就可以啟用四大組件的功能,由此可見(jiàn)Context 在Android 里的地位可見(jiàn)一斑力崇。
關(guān)于 Context 請(qǐng)移步:Android各種Context的前世今生

當(dāng)我們?cè)偕钊隒ontext 里的源碼斗塘,以Context.startService(xx) 為例,會(huì)調(diào)用到ContextImpl.startService(xx)亮靴,進(jìn)而調(diào)用ContextImpl.startServiceCommon(xx)馍盟。
而startServiceCommon(xx)方法在Android 8.0 前后的實(shí)現(xiàn)是不一樣的。
先以Android 8.0之前的實(shí)現(xiàn)為例:

#ContextImpl.java
    private ComponentName startServiceCommon(Intent service, UserHandle user) {
        try {
            validateServiceIntent(service);
            service.prepareToLeaveProcess(this);
            //調(diào)用的是ActivityManagerNative 里的方法
            ComponentName cn = ActivityManagerNative.getDefault().startService(
                    mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                            getContentResolver()), getOpPackageName(), user.getIdentifier());
            ...
            return cn;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

ActivityManagerNative.getDefault() 從單例里取數(shù)據(jù)茧吊,該方法返回的是IActivityManager 接口贞岭,對(duì)外暴露的方法都聲明在IActivityManager接口里八毯。實(shí)際拿到的對(duì)象是ActivityManagerProxy 實(shí)例,而ActivityManagerProxy 實(shí)現(xiàn)了IActivityManager 接口曹步,當(dāng)調(diào)用ActivityManagerProxy 里的方法時(shí)宪彩,實(shí)際上是通過(guò)BinderProxy.transact(xx)進(jìn)而調(diào)用Binder驅(qū)動(dòng)進(jìn)行跨進(jìn)程通信,而接收該調(diào)用類讲婚,也就是實(shí)現(xiàn)了onTransact(xx)的類是ActivityManagerNative尿孔,而它是抽象類,其子類實(shí)現(xiàn)為:ActivityManagerService筹麸。

再說(shuō)Android 8.0(含)之后的實(shí)現(xiàn)為例:

#ContextImpl.java
    private ComponentName startServiceCommon(Intent service, boolean requireForeground,
                                             UserHandle user) {
        try {
            validateServiceIntent(service);
            service.prepareToLeaveProcess(this);
            //調(diào)用的是ActivityManagerService 里的方法
            ComponentName cn = ActivityManager.getService().startService(
                    mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                            getContentResolver()), requireForeground,
                    getOpPackageName(), user.getIdentifier());
            ...
            return cn;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

同樣的活合,ActivityManager.getService() 也是從單例里獲取數(shù)據(jù),該方法返回的是IActivityManager 接口物赶。實(shí)際拿到的對(duì)象是IActivityManager.Proxy白指,而IActivityManager.Proxy 實(shí)現(xiàn)了IActivityManager接口,當(dāng)調(diào)用IActivityManager.Proxy 里的方法時(shí)酵紫,實(shí)際上是通過(guò)BinderProxy.transact(xx)進(jìn)而調(diào)用Binder驅(qū)動(dòng)進(jìn)行跨進(jìn)程通信告嘲,而接收該調(diào)用類,也就是實(shí)現(xiàn)了onTransact(xx)的類是IActivityManager.Stub奖地,而它是抽象類橄唬,其子類實(shí)現(xiàn)為:ActivityManagerService。

兩者區(qū)別用圖表示如下:


image.png

ActivityManagerService 運(yùn)行在system_server 進(jìn)程里参歹;ServiceManager 運(yùn)行在單獨(dú)的進(jìn)程里仰楚;應(yīng)用程序運(yùn)行在另一個(gè)進(jìn)程里。因此犬庇,上圖涉及到了三個(gè)進(jìn)程僧界。

1、ActivityManagerService 將IBinder引用注冊(cè)到ServiceManager里臭挽,這是第1步捂襟,此過(guò)程是個(gè)IPC。
2欢峰、當(dāng)應(yīng)用進(jìn)程想要獲取ActivityManagerService 提供的功能時(shí)葬荷,需要向ServiceManager進(jìn)行查詢,這是第2步赤赊,此過(guò)程是個(gè)IPC(拿過(guò)一次后闯狱,緩存下來(lái),下次再來(lái)拿就不用再IPC了).
3抛计、應(yīng)用進(jìn)程拿到了IBinder引用后哄孤,尋找轉(zhuǎn)換為對(duì)應(yīng)的接口IActivityManager,進(jìn)而調(diào)用ActivityManagerService 提供的方法吹截,這是第3步瘦陈,此過(guò)程是個(gè)IPC凝危。

可以看出,盡管Android 8.0前后獲取IActivityManager 接口的方式不同晨逝,然而都要經(jīng)歷上面3個(gè)步驟蛾默。只是Android 8.0 之后使用AIDL 的自動(dòng)生成代碼功能替換了ActivityManagerProxy(IActivityManager.Proxy)、ActivityManagerNative(IActivityManager.Stub)捉貌、IActivityManager(AIDL 自動(dòng)生成該類)支鸡,方便了編碼。
更多AIDL 與原生Binder比對(duì)請(qǐng)移步:Android IPC 之AIDL應(yīng)用(上)

四大組件通信橋梁

上面以啟動(dòng)Service為例闡述了應(yīng)用進(jìn)程與ServiceManager趁窃、ActivityManagerService(AMS)的關(guān)系牧挣,實(shí)際最終的目的是為了調(diào)用AMS功能,簡(jiǎn)單看看AMS 提供了哪些功能:

        #IActivityManager.aidl
    {
        //綁定Application
        void attachApplication(in IApplicationThread app, long startSeq);
        //啟動(dòng)服務(wù)
        ComponentName startService(in IApplicationThread caller, in Intent service,
        in String resolvedType, boolean requireForeground, in String callingPackage,
        in String callingFeatureId, int userId);

        //綁定服務(wù)
        int bindService(in IApplicationThread caller, in IBinder token, in Intent service,
        in String resolvedType, in IServiceConnection connection, int flags,
        in String callingPackage, int userId);

        //發(fā)布服務(wù)
        void publishService(in IBinder token, in Intent intent, in IBinder service);

        //啟動(dòng)Activity
        int startActivity(in IApplicationThread caller, in String callingPackage, in Intent intent,
        in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode,
        int flags, in ProfilerInfo profilerInfo, in Bundle options);

        //發(fā)送廣播
        int broadcastIntent(in IApplicationThread caller, in Intent intent,
        in String resolvedType, in IIntentReceiver resultTo, int resultCode,
        in String resultData, in Bundle map, in String[] requiredPermissions,
        int appOp, in Bundle options, boolean serialized, boolean sticky, int userId);

        //獲取contentProvider
        ContentProviderHolder getContentProvider(in IApplicationThread caller, in String callingPackage,
        in String name, int userId, boolean stable);

        //發(fā)布contentProvider
        void publishContentProviders(in IApplicationThread caller,
        in List<ContentProviderHolder> providers);
    }

僅僅列出了與四大組件有關(guān)的部分方法醒陆,AMS還提供了很多其它方法瀑构。
你也許已經(jīng)發(fā)現(xiàn)了,每個(gè)方法的第一個(gè)參數(shù)為:"in IApplicationThread caller"刨摩,這個(gè)參數(shù)非常重要寺晌,我們后面分析。
由以上方法可知澡刹,四大組件都需要與AMS打交道呻征,由AMS控制它們的生命周期,因此AMS也被稱為"大管家":


image.png

接下來(lái)就分別分析四大組件如何通過(guò)AMS 與自身(其它進(jìn)程)通信的像屋。

2怕犁、Activity 與AMS 交互

現(xiàn)在有兩個(gè)Activity边篮,分別為AMSActivity己莺、AMSTargetActivity。
AMSActivity 想要啟動(dòng)AMSTargetActivity戈轿,調(diào)用如下方法:

    public static void start(Context context) {
        //Context 為AMSActivity
        Intent intent = new Intent(context, AMSTargetActivity.class);
        context.startActivity(intent);
    }

該方法在AMSActivity進(jìn)程里的調(diào)用棧如下:

Activity.startActivity-->Activity.startActivityForResult
-->FragmentActivity.startActivityForResult
-->Instrumentation.execStartActivity
-->ActivityTaskManager.getService().startActivity(xx)

而ActivityTaskManager.getService() 前面已經(jīng)分析過(guò)了凌受,就是獲取了AMS對(duì)外的接口: IActivityManager,拿到了引用后調(diào)用AMS startActivity(xx)方法:

#ActivityManagerService.java
   public int startActivity(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
        return mActivityTaskManager.startActivity(caller, callingPackage, intent, resolvedType,
                resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions);
    }

重點(diǎn)關(guān)注兩個(gè)參數(shù):IApplicationThread caller和Intent intent思杯。
Intent 我們很熟悉了胜蛉,指明了要啟動(dòng)哪個(gè)Activity。
IApplicationThread 是個(gè)接口色乾,里面定義了很多方法誊册,列舉部分如:

#IApplicationThread.java
    {
        //回調(diào)靜態(tài)廣播
        void scheduleReceiver(in Intent intent, in ActivityInfo info,
        in CompatibilityInfo compatInfo,
        int resultCode, in String data, in Bundle extras, boolean sync,
        int sendingUser, int processState);
        @UnsupportedAppUsage
        //回調(diào)創(chuàng)建服務(wù)
        void scheduleCreateService(IBinder token, in ServiceInfo info,
        in CompatibilityInfo compatInfo, int processState);
        @UnsupportedAppUsage
        //回調(diào)停止服務(wù)
        void scheduleStopService(IBinder token);
        //回調(diào)綁定Application
        void bindApplication(in String packageName, in ApplicationInfo info,
        in ProviderInfoList providerList, in ComponentName testName,
        in ProfilerInfo profilerInfo, in Bundle testArguments,
        IInstrumentationWatcher testWatcher, IUiAutomationConnection uiAutomationConnection,
        int debugMode, boolean enableBinderTracking, boolean trackAllocation,
        boolean restrictedBackupMode, boolean persistent, in Configuration config,
        in CompatibilityInfo compatInfo, in Map services,
        in Bundle coreSettings, in String buildSerial, in AutofillOptions autofillOptions,
        in ContentCaptureOptions contentCaptureOptions, in long[] disabledCompatChanges);
        //回調(diào)Service onStartCommand
        void scheduleServiceArgs(IBinder token, in ParceledListSlice args);
        //回調(diào)綁定服務(wù)
        void scheduleBindService(IBinder token,
            in Intent intent, boolean rebind, int processState);
        @UnsupportedAppUsage
        //回調(diào)解綁服務(wù)
        void scheduleUnbindService(IBinder token,
            in Intent intent);

        //回調(diào)Activity 生命周期相關(guān)
        void scheduleTransaction(in ClientTransaction transaction);
    }

而ActivityThread.java 里實(shí)現(xiàn)了該接口(Android 8.0之后使用了AIDL 定義,此處以此為例分析)暖璧。

#ActivityThread.java
    private class ApplicationThread extends IApplicationThread.Stub {
        ...
        //實(shí)現(xiàn)了IApplicationThread 接口里定義的方法
    }

IApplicationThread 有啥用呢案怯?我們調(diào)用AMS方法后,有些方法并沒(méi)有返回值或者僅僅只返回int澎办,比如startActivity(xx)嘲碱,那么我們的進(jìn)程如何接收Activity的生命周期的回調(diào)呢金砍,不僅Activity的生命周期回調(diào),還有Service等的回調(diào)麦锯。這個(gè)時(shí)候就得依靠IApplicationThread 接口回調(diào)了恕稠。
因此,當(dāng)調(diào)用AMS方法的時(shí)候扶欣,傳入IApplicationThread實(shí)例鹅巍,當(dāng)AMS完成某個(gè)動(dòng)作后通過(guò)IApplicationThread 回調(diào)給應(yīng)用進(jìn)程。
實(shí)際上料祠,AMS里針對(duì)每個(gè)應(yīng)用進(jìn)程只保存了一個(gè)IApplicationThread實(shí)例昆著,而第一次傳遞給AMS是在進(jìn)程啟動(dòng)的時(shí)候:

#ActivityThread.java
    private void attach(boolean system, long startSeq) {
        ...
        if (!system) {
            //獲取AMS引用
            final IActivityManager mgr = ActivityManager.getService();
            try {
                //傳入IApplicationThread 實(shí)例給AMS
                mgr.attachApplication(mAppThread, startSeq);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
        ...
    }

此時(shí)AMS將IApplicationThread 實(shí)例保存下來(lái),后續(xù)有應(yīng)用進(jìn)程調(diào)用AMS方法時(shí)术陶,也會(huì)傳入IApplicationThread 實(shí)例凑懂,AMS通過(guò)查找是否存在實(shí)例,存在就直接拿出來(lái)用梧宫。


image.png

當(dāng)調(diào)用AMS startActivity()后接谨,AMS 將檢測(cè)目標(biāo)Activity所在進(jìn)程是否存活,若沒(méi)有則啟動(dòng)進(jìn)程塘匣,若有則將Activity移動(dòng)到棧頂脓豪,而后處理之前被移出棧頂?shù)腁ctivity,做完了這些操作之后需要通知涉及到的各個(gè)應(yīng)用進(jìn)程忌卤,而這個(gè)通知是通過(guò)IApplicationThread 回調(diào)的扫夜。
假設(shè) AMSActivity、AMSTargetActivity 處在同一個(gè)進(jìn)程里驰徊,此時(shí)AMS會(huì)回調(diào)IApplicationThread里的scheduleTransaction()方法:

    #ApplicationThread
    public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
        ActivityThread.this.scheduleTransaction(transaction);
    }

    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case EXECUTE_TRANSACTION:
                final ClientTransaction transaction = (ClientTransaction) msg.obj;
                //最終調(diào)用到ActivityThread里的handleLaunchActivity()等方法
                //進(jìn)而回調(diào)目標(biāo)Activity 的onCreate/onPause/onResume等方法
                mTransactionExecutor.execute(transaction);
                break;
        }

    #ClientTransactionHandler.java
    void scheduleTransaction(ClientTransaction transaction) {
        transaction.preExecute(this);
        sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
    }

可以看出笤闯,從AMS回調(diào)后,通過(guò)Handler發(fā)送到主線程執(zhí)行了棍厂,在Handler處理Message時(shí)颗味,會(huì)調(diào)用到Activity的onCreate()/onResume()/等方法。
這也是為什么我們經(jīng)常說(shuō)的Activity的重寫(xiě)方法都在主線程執(zhí)行的原因了牺弹。

image.png

由上可知浦马,當(dāng)應(yīng)用進(jìn)程發(fā)起startActivity動(dòng)作后,AMS 管理了目標(biāo)Activity的生命周期张漂,我們僅僅只需要在應(yīng)用進(jìn)程里重寫(xiě)目標(biāo)Activity對(duì)應(yīng)方法晶默,并在里面處理相應(yīng)的邏輯,即可實(shí)現(xiàn)一次Activity跳轉(zhuǎn)的功能航攒。

3磺陡、Service 與AMS 交互

start 啟動(dòng)Service

    Intent intent = new Intent(AMSActivity.this, AMSTargetService.class);
    startService(intent);

調(diào)用棧如下:

ContextWrapper.startService-->
ContextImpl.startServiceCommon
-->ActivityManager.getService().startService(xx)

依然是先拿到AMS 接口,進(jìn)而調(diào)用startService(xx):

#ContextImpl.java
public ComponentName startService(IApplicationThread caller, Intent service,
            String resolvedType, boolean requireForeground, String callingPackage,
            String callingFeatureId, int userId)

假設(shè)AMSActivity 與AMSTargetService 在同一進(jìn)程。
AMS 收到startService請(qǐng)求后仅政,尋找目標(biāo)Service垢油,若是Service還沒(méi)有創(chuàng)建,最終則通過(guò)回調(diào) IApplicationThread 方法scheduleCreateService(xx)圆丹、scheduleServiceArgs(xx)滩愁,這些方法處理如下:

#ActivityThread.java
    public final void scheduleCreateService(IBinder token,
                                            ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
        ...
        //切換到主線程執(zhí)行
        sendMessage(H.CREATE_SERVICE, s);
    }

    
    public final void scheduleServiceArgs(IBinder token, ParceledListSlice args) {
        List<ServiceStartArgs> list = args.getList();

        for (int i = 0; i < list.size(); i++) {
            ...
            //切換到主線程執(zhí)行
            sendMessage(H.SERVICE_ARGS, s);
        }
    }

    public void handleMessage(Message msg) {
        switch (msg.what) {
            case CREATE_SERVICE:
                //1、該方法里創(chuàng)建Service 實(shí)例
                //2辫封、回調(diào)Service onCreate 方法
                handleCreateService((CreateServiceData)msg.obj);
                break;

            case SERVICE_ARGS:
                //回調(diào)onStartCommand方法
                handleServiceArgs((ServiceArgsData)msg.obj);
                break;
        }
    }

與Activity 類似硝枉,Service 生命周期回調(diào)最終切換到主線程執(zhí)行。
這也就是我們常說(shuō)的為什么Service里不能執(zhí)行耗時(shí)任務(wù)倦微,否則容易發(fā)生ANR妻味,因?yàn)榛卣{(diào)方法在主線程執(zhí)行的。

bind 啟動(dòng)Service

假設(shè)AMSActivity 與AMSTargetService 不在同一進(jìn)程欣福,以AMSActivity 所在進(jìn)程為客戶端责球,AMSTargetService所在進(jìn)程 為服務(wù)端,有如下圖:


image.png

前提是客戶端拓劝、服務(wù)端已經(jīng)綁定到AMS里了(傳遞IApplicationThread)

1雏逾、客戶端發(fā)起綁定請(qǐng)求。
2郑临、AMS 尋找目標(biāo)Service栖博,通過(guò)IApplicationThread 回調(diào)scheduleCreateService 創(chuàng)建服務(wù)端Service,并通過(guò)scheduleBindService 綁定服務(wù)厢洞。
3仇让、服務(wù)端創(chuàng)建、綁定成功并傳遞IBinder給AMS躺翻。
4丧叽、AMS收到IBinder引用通過(guò)IServiceConnection 回調(diào)connected方法告訴客戶端服務(wù)綁定成功,也就是回調(diào)客戶端綁定時(shí)注冊(cè)的ServiceConnection.onServiceConnected 方法获枝。

4蠢正、Broadcast 與AMS 交互

靜態(tài)注冊(cè)

廣播有兩種注冊(cè)方式:靜態(tài)與動(dòng)態(tài)骇笔。
先分析靜態(tài)注冊(cè):

在AndroidManifest.xml 里聲明廣播省店,并指定接收的Action和處理該Action的類。

如下:

        <receiver android:name=".ams.AMSTargetBroadcast">
            <intent-filter>
                <action android:name="my_action"></action>
            </intent-filter>
        </receiver>

在AMSActivity里 發(fā)送廣播笨触,發(fā)送廣播調(diào)用棧如下:

ContextWrapper.sendBroadcast-->ContextImpl.sendBroadcast
-->ActivityManager.getService().broadcastIntent(xx)

依然是先拿到AMS 接口懦傍,進(jìn)而調(diào)用broadcastIntent(xx):

#ContextImpl.java
int broadcastIntent(in IApplicationThread caller, in Intent intent,
        in String resolvedType, in IIntentReceiver resultTo, int resultCode,
        in String resultData, in Bundle map, in String[] requiredPermissions,
        int appOp, in Bundle options, boolean serialized, boolean sticky, int userId);

假設(shè)AMSActivity 與 AMSTargetBroadcast 在同一進(jìn)程。
AMS 收到broadcastIntent 請(qǐng)求后芦劣,轉(zhuǎn)發(fā)給BroadcastQueue 處理粗俱,若廣播接收器是靜態(tài)注冊(cè),則通過(guò) 回調(diào)IApplicationThread scheduleReceiver 方法虚吟。后面的處理過(guò)程與Activity/Service類似:
AMSActivity 所在進(jìn)程收到scheduleReceiver 回調(diào)后寸认,切換到主線程签财,然后反射構(gòu)造AMSTargetBroadcast實(shí)例,并調(diào)用onReceive(xx)方法偏塞,而onReceive(xx) 方法里正是我們重寫(xiě)的接收廣播后的處理邏輯唱蒸。
因此,onReceive(xx) 方法也是在主線程執(zhí)行的灸叼,不能執(zhí)行耗時(shí)操作神汹,否則容易發(fā)生ANR

動(dòng)態(tài)注冊(cè)

動(dòng)態(tài)注冊(cè)如下:

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(AMSTargetBroadcast.MY_ACTION);
        registerReceiver(new AMSTargetBroadcast(), intentFilter);

registerReceiver(xx)最終調(diào)用到:

#ContextImpl.java
    private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
                                            IntentFilter filter, String broadcastPermission,
                                            Handler scheduler, Context context, int flags) {
        //AIDL 實(shí)現(xiàn)
        IIntentReceiver rd = null;
        if (receiver != null) {
            if (mPackageInfo != null && context != null) {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                //構(gòu)造接收器回調(diào)
                rd = mPackageInfo.getReceiverDispatcher(
                        receiver, context, scheduler,
                        mMainThread.getInstrumentation(), true);
            } else {
                ...
            }
        }
        try {
            //向AMS 注冊(cè)
            final Intent intent = ActivityManager.getService().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
                    broadcastPermission, userId, flags);
            ...
            return intent;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

在向AMS注冊(cè)前,構(gòu)造了IIntentReceiver 對(duì)象古今,該接口是AIDL聲明的屁魏,也就是說(shuō)向AMS注冊(cè)了個(gè)回調(diào)接口,當(dāng)AMS 接收到發(fā)送廣播的請(qǐng)求后捉腥,發(fā)現(xiàn)是動(dòng)態(tài)注冊(cè)的氓拼,于是通過(guò)回調(diào)IIntentReceiver 接口的performReceive(xx)方法,進(jìn)而調(diào)用BroadcastReceiver里的onReceive(xx)方法抵碟,貌似沒(méi)有看到切換到主線程執(zhí)行呢披诗?看看IIntentReceiver performReceive(xx)的處理:

#LoadedApk.java
    public void performReceive(Intent intent, int resultCode, String data,
                               Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
        ...
        //mActivityThread.post 切換到主線程執(zhí)行
        if (intent == null || !mActivityThread.post(args.getRunnable())) {
            ...
        }
    }

    public final Runnable getRunnable() {
        return () -> {
            final BroadcastReceiver receiver = mReceiver;
            ...
            try {
                ...
                //注冊(cè)時(shí)傳入的BroadcastReceiver
                receiver.onReceive(mContext, intent);
            } catch (Exception e) {
                ...
            }
        };
    }

很明顯,此處是切換到主線程執(zhí)行了立磁。
可以看出呈队,不管是靜態(tài)注冊(cè),抑或是動(dòng)態(tài)注冊(cè)唱歧,最終都是在主線程回調(diào)onReceive(xx)方法宪摧。
Broadcast 與AMS 交互圖如下:


image.png

由上還可以總結(jié)出:

1、每發(fā)送一次廣播颅崩,都需要走兩次IPC(請(qǐng)求AMS/AMS回調(diào))几于,因此若是廣播只在同一進(jìn)程里發(fā)送/接收,沒(méi)必要使用廣播沿后,推薦使用本地廣播:LocalBroadcastManager沿彭。
2、若廣播是靜態(tài)注冊(cè)的尖滚,AMS每次回調(diào)時(shí)都會(huì)反射重新創(chuàng)建BroadcastReceiver 實(shí)例喉刘,因此在廣播發(fā)送/接收很頻繁的情況下不建議使用靜態(tài)注冊(cè),推薦使用動(dòng)態(tài)注冊(cè)漆弄。

5睦裳、ContentProvider 與AMS 交互

ContentProvider 顧名思義:內(nèi)容提供者。
以典型的手機(jī)通訊錄為例撼唾,通訊錄如何提供給其它進(jìn)程使用其數(shù)據(jù)呢廉邑?根據(jù)前面的經(jīng)驗(yàn),通訊錄需要暴露一個(gè)對(duì)外的接口,外部程序想使用通訊錄那得拿到這個(gè)暴露出來(lái)的接口蛛蒙。
接下來(lái)看看如何實(shí)現(xiàn)自定義的ContentProvider:

聲明ContentProvider

#AndroidManifest.xml
        <provider
            android:authorities="com.fish.AMSTargetProvider"
            android:name=".ams.AMSTargetProvider">
        </provider>
public class AMSTargetProvider extends ContentProvider {
    public static String AUTHORITY = "com.fish.AMSTargetProvider";
    private static int MATCH_CODE = 1000;
    private static UriMatcher uriMatcher;
    ...
    //重寫(xiě)增刪改查方法糙箍,處理具體的邏輯
}

接口有了,處理邏輯也有了牵祟,那么需要對(duì)外展示自己的能力倍靡。當(dāng)然這個(gè)過(guò)程不需要我們完成,系統(tǒng)自動(dòng)處理了课舍。

發(fā)布ContentProvider 到AMS

還記得之前我們說(shuō)的應(yīng)用進(jìn)程啟動(dòng)后塌西,會(huì)向AMS綁定(注冊(cè))IApplicationThread,綁定成功后會(huì)通過(guò)IApplicationThread 的bindApplication(xx)回調(diào)應(yīng)用進(jìn)程筝尾,進(jìn)而切換到主線程執(zhí)行handleBindApplication(xx)捡需,在該方法里會(huì)反射創(chuàng)建Application實(shí)例,然后處理AndroidManifest.xml里聲明的Provider筹淫。
調(diào)用棧如下:

ActivityThread.handleBindApplication-->ActivityThread.installContentProviders
-->ActivityManager.getService().publishContentProviders(xx)

在ActivityThread.installContentProviders 里會(huì)實(shí)例化ContentProvider站辉,并將其引用保存到Map里,最后調(diào)用AMS publishContentProviders(xx) 傳遞給AMS损姜。

#AMS
 public final void publishContentProviders(IApplicationThread caller,
            List<ContentProviderHolder> providers)

ContentProviderHolder 實(shí)現(xiàn)了Parcelable 接口饰剥,因此其可以跨進(jìn)程傳遞,其內(nèi)部持有IContentProvider 成員變量摧阅。此處你可能有疑惑汰蓉,IContentProvider 并不能跨進(jìn)程傳遞!實(shí)際上IContentProvider 只是個(gè)接口棒卷,它的具體實(shí)現(xiàn)類是:ContentProvider.Transport顾孽,其聲明如下:

#ContentProvider.java
class Transport extends ContentProviderNative {
    ...
}

#ContentProviderNative.java
abstract public class ContentProviderNative extends Binder implements IContentProvider {
    ...
}

因此ContentProvider.Transport 可以跨進(jìn)程傳遞。

獲取ContentProvider

AMS 里已經(jīng)存留了ContentProvider相關(guān)信息比规,當(dāng)有進(jìn)程需要使用ContentProvider時(shí)若厚,以插入數(shù)據(jù)為例,使用方法如下:

    {
        ContentValues contentValues = new ContentValues();
        getContentResolver().insert(uri, contentValues);
    }

調(diào)用insert(xx)方法時(shí)蜒什,調(diào)用棧如下:

ContentResolver.insert-->ContentResolver.acquireProvider-->ApplicationContentResolver.acquireProvider
-->ActivityThread.acquireProvider-->ActivityThread.acquireExistingProvider
-->ActivityManager.getService().getContentProvider(xx)

其中测秸,ActivityThread.acquireExistingProvider 會(huì)先查詢本地是否緩存有ContentProvider,若有則直接返回(對(duì)應(yīng)AMSTargetProvider 實(shí)例與調(diào)用者同一個(gè)進(jìn)程內(nèi))灾常,此時(shí)直接返回ContentProvider實(shí)例霎冯,無(wú)需再查詢AMS了。

若是不同的進(jìn)程岗憋,則需要通過(guò)AMS 查詢ContentProvider:

public final ContentProviderHolder getContentProvider(
            IApplicationThread caller, String callingPackage, String name, int userId,
            boolean stable) 

若是AMS之前緩存了ContentProvider肃晚,則直接返回,否則查看目標(biāo)進(jìn)程是否存活仔戈,不存活拉起來(lái),再拿ContentProvider。
有個(gè)點(diǎn)需要注意的是:

拿到遠(yuǎn)程的ContentProvider也會(huì)緩存的监徘,只是在ContentResolver里的insert()/delete()/query() 方法的最后都會(huì)調(diào)用releaseProvider(xx)釋放緩存晋修。因此對(duì)于遠(yuǎn)程的ContentProvider,每次都是通過(guò)AMS重新獲取的凰盔。

用圖表示ContentProvider與AMS的交互:


image.png

ContentProvider 數(shù)據(jù)變更

當(dāng)ContentProvider數(shù)據(jù)變更時(shí)墓卦,需要通知給監(jiān)聽(tīng)者,而其它進(jìn)程想要監(jiān)聽(tīng)變化户敬,則需要注冊(cè)觀察者落剪。
注冊(cè)觀察者如下:

        Handler handler = new Handler() {
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
            }
        };
        Uri uri = Uri.parse("content://" + AMSTargetProvider.AUTHORITY + "/ams");
        getContentResolver().registerContentObserver(uri, false, new ContentObserver(handler) {
            @Override
            public void onChange(boolean selfChange) {
                super.onChange(selfChange);
            }
        });

registerContentObserver 調(diào)用棧如下:

ContentResolver.registerContentObserver
-->ContentResolver.registerContentObserver
-->getContentService().registerContentObserver(xx)

ContentObserver 本身沒(méi)有跨進(jìn)程的能力,因此需要將它包裝起來(lái):

    #ContentObserver.java
    public IContentObserver getContentObserver() {
        synchronized (mLock) {
            if (mTransport == null) {
                mTransport = new Transport(this);
            }
            return mTransport;
        }
    }

    private static final class Transport extends IContentObserver.Stub {
        //持有ContentObserver
        private ContentObserver mContentObserver;
        ...
    }

封裝好觀察者之后尿庐,需要將它傳遞出去忠怖。
重點(diǎn)在getContentService()里:

 #ContentResolver.java
    public static IContentService getContentService() {
        if (sContentService != null) {
            return sContentService;
        }
        //從ServiceManager 獲取IBinder 引用
        IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME);
        sContentService = IContentService.Stub.asInterface(b);
        return sContentService;
    }

而注冊(cè)到ServiceManager里的Service如下:

#ContentService.java
    public final class ContentService extends IContentService.Stub {
        ...
    }

因此調(diào)用ContentService.registerContentObserver(xx),將ContentObserver.Transport 傳遞給ContentService抄瑟。

當(dāng)ContentProvider數(shù)據(jù)發(fā)生變更的時(shí)候調(diào)用如下代碼:

#ContentResolver.java
    private void notifyChange() {
        Uri notifyUri = Uri.parse("content://" + AUTHORITY + "/ams");
        getContext().getContentResolver().notifyChange(notifyUri, null);
    }

//最終調(diào)用到此
    public void notifyChange(@android.annotation.NonNull Uri uri, ContentObserver observer, boolean syncToNetwork,
                             @UserIdInt int userHandle) {
        try {
            //ContentService 里的方法
            getContentService().notifyChange(
                    uri, observer == null ? null : observer.getContentObserver(),
                    observer != null && observer.deliverSelfNotifications(),
                    syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0,
                    userHandle, mTargetSdkVersion, mContext.getPackageName());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

實(shí)際還是調(diào)用了到ContentService里凡泣,在里面找到之前注冊(cè)的ContentObserver. Transport,然后回調(diào)Transport.onChange皮假,最終會(huì)調(diào)用到剛開(kāi)始注冊(cè)進(jìn)去的ContentObserver.onChange()方法鞋拟。
需要注意的是:onChange() 可以選擇在主/子 線程回調(diào),只需要注冊(cè)的時(shí)候傳入Handler即可惹资。

數(shù)據(jù)變更通知并沒(méi)有和AMS 打交道贺纲,而是和ContentService有關(guān)系,用圖表示如下:

image.png

至此褪测,四大組件與AMS 通信的核心已經(jīng)分析完畢哮笆,本篇沒(méi)有分析AMS 里對(duì)四大組件的處理細(xì)節(jié),只是從IPC的角度分析通信的過(guò)程汰扭,只要理解了通信過(guò)程與通信框架稠肘,很容易就找到對(duì)應(yīng)的處理細(xì)節(jié)所在之處。

測(cè)試代碼包含了startService/bindService萝毛、動(dòng)態(tài)/靜態(tài) 注冊(cè)廣播项阴、ContentProvider 跨進(jìn)程編寫(xiě)等示例:


tt0.top-606323.gif

本文基于Android 10.0/8.0/7.0
完整代碼演示 若是有幫助,給github 點(diǎn)個(gè)贊唄~

您若喜歡笆包,請(qǐng)點(diǎn)贊环揽、關(guān)注,您的鼓勵(lì)是我前進(jìn)的動(dòng)力

持續(xù)更新中庵佣,和我一起步步為營(yíng)系統(tǒng)歉胶、深入學(xué)習(xí)Android/Java

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市巴粪,隨后出現(xiàn)的幾起案子通今,更是在濱河造成了極大的恐慌粥谬,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辫塌,死亡現(xiàn)場(chǎng)離奇詭異漏策,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)臼氨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門掺喻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人储矩,你說(shuō)我怎么就攤上這事感耙。” “怎么了持隧?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵即硼,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我舆蝴,道長(zhǎng)谦絮,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任洁仗,我火速辦了婚禮层皱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赠潦。我一直安慰自己叫胖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布她奥。 她就那樣靜靜地躺著瓮增,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哩俭。 梳的紋絲不亂的頭發(fā)上绷跑,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音凡资,去河邊找鬼砸捏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛隙赁,可吹牛的內(nèi)容都是我干的垦藏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼伞访,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼掂骏!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起厚掷,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤弟灼,失蹤者是張志新(化名)和其女友劉穎级解,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體袜爪,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蠕趁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年薛闪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辛馆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡豁延,死狀恐怖昙篙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诱咏,我是刑警寧澤苔可,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站袋狞,受9級(jí)特大地震影響焚辅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜苟鸯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一同蜻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧早处,春花似錦湾蔓、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至咸包,卻和暖如春桃序,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背烂瘫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工媒熊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人忱反。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓泛释,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親温算。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怜校,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

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