主頁 >  其他 > Android四大組件之Activity啟動流程原始碼實作詳解(一)

Android四大組件之Activity啟動流程原始碼實作詳解(一)

2020-09-23 22:56:30 其他

??????Activity啟動流程原始碼實作詳解(一)

Android四大組件原始碼實作詳解系列博客目錄:

Android應用行程創建流程大揭秘
Android四大組件之bindService原始碼實作詳解
Android四大組件之Activity啟動流程原始碼實作詳解概要
Android四大組件之Activity啟動流程原始碼實作詳解(一)


前言

??在正式開始Android四大組件之Activity啟動流程原始碼實作詳解之前,如果小伙們還沒有閱讀Activity啟動流程原始碼實作詳解概要,強烈建議先行閱讀該概要,因為該篇博客從整體概要和Activity啟動的前期知識點出發為我們提供了提綱和將要涉及到的知識點的梳理,在本篇博客中我們將重點分析目標行程發送Activity啟動請求和AMS服務對啟動Activity的請求的初步處理,即重點闡述如下階段流程:

  • Souruce端行程發送請求目標Activity啟動階段
    ?1.Source端發起顯示/隱式啟動請求啟動Activity

  • System_server行程通過AMS處理啟動Activity請求
    ?2.決議啟動目標Activity的Intent
    ?3.創建目標Activity對應的ActivityRecord

  • 注意:本篇的介紹是基于Android 7.xx平臺為基礎的,其中涉及的代碼路徑如下:

frameworks/base/services/core/java/com/android/server/am/
  --- ActivityManagerService.java
  --- ProcessRecord.java
  --- ActivityRecord.java
  --- ActivityResult.java
  --- ActivityStack.java
  --- ActivityStackSupervisor.java
  --- ActivityStarter.java
  --- TaskRecord.java 

frameworks/base/services/core/java/com/android/server/pm、
 --- PackageManagerService.java

frameworks/base/core/java/android/app/
  --- IActivityManager.java
  --- ActivityManagerNative.java (內部包含AMP)
  --- ActivityManager.java
  --- AppGlobals.java
  --- Activity.java
  --- ActivityThread.java(內含AT)
  --- LoadedApk.java  
  --- AppGlobals.java
  --- Application.java
  --- Instrumentation.java
  
  --- IApplicationThread.java
  --- ApplicationThreadNative.java (內部包含ATP)
  --- ActivityThread.java (內含ApplicationThread)
  --- ContextImpl.java
  • 并且在后續的原始碼分析程序中為了簡述方便,會將做如下簡述:
    ApplicationThreadProxy簡稱為ATP
    ActivityManagerProxy簡稱為AMP
    ActivityManagerService簡稱為AMS
    ActivityManagerNative簡稱AMN
    ApplicationThreadNative簡稱ATN
    PackageManagerService簡稱為PKMS
    ApplicationThread簡稱為AT
    ActivityStarter簡稱為AS,這里不要和ActivityServices搞混淆了
    ActivityStackSupervisor簡稱為ASS

在正式開始相關的原始碼分析前,還是先奉上呼叫的時序圖以便小伙們先從整體上有個清晰的概括,然后再從細節開擼!

在這里插入圖片描述



一. Source端開始請求執行啟動Activity

??Source端開始請求執行啟動Activity的核心偽代碼如下,接下來我們以下面的偽代碼為脈絡逐步分析,但是不會每個原始碼細節都予以分析(并且如果小伙們只是想大概了解一下基本流程,那么下面的偽代碼就夠了!),而是挑出重點!

//發起端行程發起啟動Activity請求
	Activity.startActivity(...)
	  Activity.startActivityForResult(...)
	    mInstrumentation.execStartActivity(...)
		  AMP.startActivity(...)//通過AMS代理端向AMS發起啟動Activity的Binder IPC請求
		  mInstrumentation.checkStartActivityResult(...)//檢測啟動是否成功
		mMainThread.sendActivityResult(...)

2.1 Activity.startActivity

    Intent intent = new Intent();
    intent.setAction("xxx.xxx.xxx");
    startActivity(intent);

??通常我們在Activity啟動Activity會直接呼叫startActivity方法,此時會執行Activity的startActivity方法

//Activity.java
    @Override
    public void startActivity(Intent intent) {
        this.startActivity(intent, null);
    }

此時可以看到會接著呼叫Activity的多載startActivity方法,我們接著繼續分析其多載方法

//Activity.java
    @Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            startActivityForResult(intent, -1);//進入該分支,注意此時的引數requestCode取值為-1,表明發起端行程無需接收Activity的結果,詳見章節2.2
        }
    }

此時我們的入參options為null,所以會走入else的分支,即此時的startActivityForResult方法的引數requestCode被默認賦予了-1的值,

??分析到這里不知道小伙們注意到一個問題沒有,就是通常我們啟動Activity有兩個方法startActivity和startActivityForResult,這里我們可以看到Activity中呼叫startActivity的內部也是呼叫的startActivityForResult的,
??而我們知道通過startActivityForResult啟動目標Activity可以在Activity中回呼onActivityResult接收目標Acitiy啟動的情況,而呼叫startActivity則不可以呢?其最最主要的原因就是呼叫startActivity啟動目標Acitivity時,其內部呼叫startActivityForResult傳遞的引數requestCode被默認賦予為-1了在后續程序中會根據此值判斷是否需要傳遞回呼叫結果,這也意味著我們在Activity呼叫startActivityForResult的時候傳遞的requestCode值為-1(通過后續分析我們可知,只要小于0)的話,那么onActivityResult是不起作用的,所以當我們呼叫startActivityForResult的時候需要注意這一點,


2.2 Activity.startActivityForResult

//Activity.java
	Activity mParent;
    public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
			/*
			* mToken: 資料型別為IBinder,這個Token貫穿AMS,Activity,WMS,并且可以跨行程傳遞
			* mAppThread: 又是一個IBinder型別,此處它是Binder物體ApplicationThread
			*/
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);//詳見2.3
            if (ar != null) {
                mMainThread.sendActivityResult(//發送執行結果
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            if (requestCode >= 0) {//注意此處
                mStartedActivity = true;
            }

            cancelInputsAndStartExitTransition(options);
        } else {//子視窗中會執行此分支
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
                mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
    }

??注意這里有一個判斷邏輯mParent是否為null,這里的mParent 根據檔案注釋和實測應該是用來判斷是否是在子視窗的,類似于在Dialog中啟動Activity中的那種,在當前情況下我們可知為null所以會走入ifi分支,

這里我們需要重點分析一下Instrumentation這個類,Instrumentation是android系統中啟動Activity的一個實際操作類,即實際執行者,在有些書中也被成為Activity的大管家,并且這里我們所說的Source端開始請求執行啟動Activity實際上就是Instrumentation進行實際操作執行的,那么為什么說是在應用行程端的啟動呢?實際上Activity的啟動分為應用行程端的啟動和system_server服務行程端的啟動,Activity的啟動不是獨立的,而是多個應用行程相互配合最終完成了Activity在系統中的啟動的,而在應用行程端的啟動實際的操作類就是Intrumentation來執行的,我們來簡單看看其提供了那些基本方法:

newActivity(…)
newApplication(…)
callApplicationOnCreate(…)
callActivityOnCreate(…)
callActivityOnNewIntent(…)
callActivityOnXXX(…)
execStartActivity(…)

從上面可以看到它的方法都是Application和Activity的創建以及生命周期的相關方法,

同時這里有一個知識點,我需要提前補充一下就是對于每一個Android App行程來說,它的總入口都是ActivityThread的main. 每一個應用的行程都有且僅有一個ActivityThread物件,而每一個ActivityThread物件有且僅有一個Instrumentation成員變數,即整個App行程中ActivityThread和Instrumentation實體物件是唯一的,


2.3 Instrumentation.execStartActivity

//Instrumentation.java
    public ActivityResult execStartActivity(
            Context who, 
            IBinder contextThread, 
            IBinder token, 
            Activity target,
            Intent intent, 
            int requestCode, 
            Bundle options)

??在正式開始分析該方法前,我得對該方法的引數予以隆重的介紹:

  • who
    引數型別為Context實體,標明發起端背景關系的資訊

  • contextThread
    引數型別為IBinder實體,該物件繼承于ApplicationThreadNative(Binder服務端),這個ApplicationThread物件很重要,因為正是通過它串聯其了AMS對發起端行程的ActivityThread的互動(如果把ApplicationThread當作服務端,那么此時AMS相關于ApplicationThread而言就是客戶端),其兩者之間的關系建立詳見下述的示意圖,即AMS持有ApplicationThread的代理端,而應用端行程持有AMS的代理端AMP,二者相互持有各自Binder服務端的代理端進而完成了二者之間的RPC呼叫,在目標Activiy啟動后發起端Activity的onPause的執行是由其代理端ATP在AMS中來通過Binder IPC透傳過來,然后發起端Activity執行onPause流程在這里插入圖片描述

  • token
    引數型別也為IBinder實體,指向發起端Activity的ActivityRecord物件中的Token,其Binder物體在AMS中,這里暫且不表

  • target
    引數型別為Activity實體,標明發起端Activity,如果發起端不為Activity此時為null

  • intent
    引數型別為Intent實體,用來表明要啟動的Activity資訊,此時的intent可能是顯示Intent,也可能是隱式Intent,我們此處的是一個隱式Intent,顯式intent通常用在包內啟動組件,如果是啟動其他APP的組件,則通常用隱式intent,顯式intent里面包含了一個ComponentName,ComponentName由包名 + 類名組成,可以唯一標識一個組件,系統通過ComponentName就可以找到要啟動的組件,隱式intent通常通過Action來過濾出要啟動的組件

  • requestCode
    引數型別為int,啟動Activity的請求碼,此請求碼表明發起端是否需要接收目標Activity啟動的結果

  • options
    引數型別為Bundle ,可以理解我啟動目標Activity的附件引數,譬如附件傳輸的一些額外資訊

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        IApplicationThread whoThread = (IApplicationThread) contextThread;
		..
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);

			/* public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
						String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
			   			ProfilerInfo profilerInfo, Bundle options) throws RemoteException;
			   caller:當前應用的ApplicationThread物件mAppThread
			   callingPackage: 呼叫當前ContextImpl.getBasePackageName(),獲取當前Activity所在包名
			   intent: 這便是啟動Activity時,傳遞過來的引數
			*/

            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);//詳見章節2.4
            checkStartActivityResult(result, intent);//檢查Activity啟動是否成功
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }

2.4 AMN.getDefault

??繼續回到章節2.3,我們可以看到最后其呼叫了AMN.getDefault().startActivity,這里牽涉到一個重要的方法AMN.getDefault(),其實它在我們的博客中 Android Binder框架實作之Java層獲取Binder服務原始碼分析已經有詳細的介紹和分析了,但是為了博客的連貫性還是簡單過下(主要是為了不太熟悉的小伙伴們),

//ActivityManagerNative.java
    static public IActivityManager getDefault() {
        return gDefault.get();
    }

這里的gDefault是Singleton物件實體,而Singleton我們可以看到是一個模板類物件,并且提供了一個單例方法,其定義如下:

//Singleton.java
public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {//采用單例模式
                mInstance = create();
            }
            return mInstance;
        }
    }
}

我們將IActivityManager帶入Singleton,得到如下的create方法程序:

	//ActivityManagerNative.java
    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
        	//此處等價于IBinder b = new BinderProxy(new BpBinder(handle));
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
			//此處等價于IActivityManager am = new ActivityManagerProxy(new BinderProxy(new BpBinder(handle)))
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };

	//注意此處我們的入參是BinderProxy型別,所以會走代理端
    static public IActivityManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        IActivityManager in =
            (IActivityManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }
		//即會走到此處
        return new ActivityManagerProxy(obj);
    }

這里即最終經過層層轉換得到了AMS服務的代理端ActivityManagerProxy,進而借助它完成對AMS服務的RPC請求,

2.4.1 AMN.getDefault()小結

AMN.getDefault()的呼叫流程基本分析結束了,我們對其小結一下:

  • AMN.getDefault()最侄訓取了AMS的遠程Binder代理端AMP
  • AMS的Binder通信程序中提供了一個IActivityManager服務業務層介面,AMP類與AMS類都實作了IActivityManager介面方法,區別不同給的是AMS端顯示了真正的具體服務,而AMP端是封裝了相關的通信傳輸邏輯,AMP作為Binder通信的服務代理端,而AMS作為Binder通信的服務端物體,根據Android Binder框架實作之Java層Binder服務跨行程呼叫原始碼分析,AMP.bindService()最終呼叫AMS.startActivity(),整個流程圖如下:

在這里插入圖片描述
關于上述整個Binder IPC呼叫流程,可以使用如下偽代碼來簡述

AMP.startActivity(...)---> 
BinderProxy.transact(...) --->
BpBinder.transact(...)--->
binder驅動傳輸--->
JavaBBinder.onTransact(...)--->
AMN.onTransact(..)--->
AMN.startActivity(...)

2.5 AMP.startActivity(…)

	//ActivityManagerNative.java
    public int startActivity(IApplicationThread caller, 
    						 String callingPackage, 
    						 Intent intent,
            				 String resolvedType, 
            				 IBinder resultTo, 
            				 String resultWho, 
            				 int requestCode,
                             int startFlags, 
                             ProfilerInfo profilerInfo, 
                             Bundle options) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        //寫入AMS Binder服務描述資訊即android.app.IActivityManager
        data.writeInterfaceToken(IActivityManager.descriptor);
        //寫入IApplicationThread 匿名Binder服務物體(這個在attachApplication時寫入過)
        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
        data.writeString(callingPackage);
        intent.writeToParcel(data, 0);
        data.writeString(resolvedType);
        data.writeStrongBinder(resultTo);
        data.writeString(resultWho);
        data.writeInt(requestCode);
        data.writeInt(startFlags);
        if (profilerInfo != null) {
            data.writeInt(1);
            profilerInfo.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
        } else {
            data.writeInt(0);
        }
        if (options != null) {
            data.writeInt(1);
            options.writeToParcel(data, 0);
        } else {
            data.writeInt(0);
        }
		//BinderProxy
		//mRemote指向BinderProxy,而BinderProxy持有C++端的BpBinder,進而借助Binder驅動和AMS通信
        mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
        reply.readException();
        int result = reply.readInt();
        reply.recycle();
        data.recycle();
        return result;
    }

這里如果對Binder框架熟悉的小伙們應該對上述的呼叫程序是見怪不怪了,但是有幾個點我們需要注意:

  • 此處startActivity()的共有10個引數, 下面說說每個引數傳遞AMP.startActivity()每一項的對應值
引數型別 引數名稱引數含義以及取值
IApplicationThread caller 當前應用的ActivityThread物件ApplicationThread實體mAppThread
String callingPackage 呼叫當前ContextImpl.getBasePackageName(),獲取當前Activity所在包名
Intent intent 啟動目的端Activity傳遞過來的引數,其中攜帶目的端Acitivity隱式或者顯示啟動需要的引數
String resolvedType 呼叫intent.resolveTypeIfNeeded而獲取
IBinder resultTo 引數型別也為IBinder實體,指向發起端Activity的ActivityRecord物件中的Token,其Binder物體在AMS中
String resultWho 來自于當前發起端Activity.mEmbeddedID,可能為null
int requestCode 啟動目的端Activity的請求碼,此時的取值為-1
int startFlags 此時取值為0,代指Activity的啟動模式
ProfilerInfo profilerInfo 此時取值null,這個引數暫時沒有搞懂是干啥的
Bundle options 啟動目的端Activity附加引數,此時取值為null
  • startActivity中呼叫了二次Parcel類的方法writeStrongBinder(),這里我們需要注意writeStrongBinder()這二次寫入的是Binder物體代理端還是代理端,是實名Binder還是匿名Binder,
  • 這里的mRemote指向BinderProxy,而BinderProxy持有C++端的BpBinder,而BpBinder作為遠程Binder物體的通信代理端,最后借助Binder驅動和AMS通信,最后呼叫到ActivityManagerNative的onTransact()方法中


三. System_server行程接收啟動Activity的請求

??通過上面的層層沖關,打怪我們跳出了發起端行程,來到了system_server行程,讓我們接著分析看看system_server是怎么處理startActivity的RPC請求的,

3.1 AMN.onTransact

	//ActivityManagerNative.java
    @Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
        switch (code) {
        case START_ACTIVITY_TRANSACTION:
        {
            data.enforceInterface(IActivityManager.descriptor);
            IBinder b = data.readStrongBinder();
			//轉換成ApplicationThread Binder物體代理端ApplicationThreadProxy
            IApplicationThread app = ApplicationThreadNative.asInterface(b);
            String callingPackage = data.readString();
            Intent intent = Intent.CREATOR.createFromParcel(data);
            String resolvedType = data.readString();
            IBinder resultTo = data.readStrongBinder();
            String resultWho = data.readString();
            int requestCode = data.readInt();
            int startFlags = data.readInt();
            ProfilerInfo profilerInfo = data.readInt() != 0
                    ? ProfilerInfo.CREATOR.createFromParcel(data) : null;
            Bundle options = data.readInt() != 0
                    ? Bundle.CREATOR.createFromParcel(data) : null;
			//呼叫AMN的子類AMS,詳見章節3.2
            int result = startActivity(app, callingPackage, intent, resolvedType,
                    resultTo, resultWho, requestCode, startFlags, profilerInfo, options);
            reply.writeNoException();
            reply.writeInt(result);
            return true;
        }

在正式開始上述的原始碼分析前,我們先來闡述一個重要的知識點,即在這個呼叫程序中涉及到兩個行程,不妨令startActivity的發起行程記為行程Process_A,AMS Service所屬行程記為行程Process_B;那么行程Process_A通過Binder機制(采用IActivityManager介面)向行程Process_B發起請求服務,行程Process_B則通過Binder機制(采用IApplicationThread介面)向行程Process_A發起請求服務,也就是說行程Process_A與行程Process_B能相互間主動發起請求,進而完成行程通信,但是這里有一點需要注意IApplicationThread的Binder物體端并沒有注冊到servicemanager行程中,它是一個依賴于實名Binder的匿名Binder,

這里涉及IApplicationThread很重要,它串聯起了AMS對App行程的生命周期及其其它的控制,那么下面直接把其相關的類圖展示如下:

在這里插入圖片描述

這里的IApplicationThread與IActivityManager的Binder通信原理一樣,ATP作為Binder通信的客戶端,ATN作為Binder通信的服務端,其中ApplicationThread繼承ATN類,覆寫其中的部分方法,

接著繼續分析onTransact方法,其根據AMP傳遞過來的code值進入START_ACTIVITY_TRANSACTION分支,然后解讀取通過Binder驅動傳遞過來的資料,決議完成之后呼叫AMN的方法startActivity繼續未完成之作業(這里的startActivity在AMS服務中具體實作),這里從驅動中獲取到資料然后決議這里就不重點關注了,


3.2 AMS.startActivity

    @Override
    //AMS.java
    public final int startActivity(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
        return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
                resultWho, requestCode, startFlags, profilerInfo, bOptions,
                UserHandle.getCallingUserId());
    }
    final ActivityStackSupervisor mStackSupervisor;

    final ActivityStarter mActivityStarter;
    final ActiveServices mServices;
    @Override
    public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
        enforceNotIsolatedCaller("startActivity");
        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                userId, false, ALLOW_FULL_ONLY, "startActivity", null);
                //這里的mActivityStarter是ActivityStarter的實體物件
        return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
                resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
                profilerInfo, null, null, bOptions, false, userId, null, null);//詳見章節3.3
    }

這里需要重點強調如下幾點:

  • AMS是Android提供負責系統中四大組件的啟動、切換、調度及應用行程的管理和調度等作業的Java層Binder服務,其身上的擔子很重,為了減輕負擔和代碼邏輯的精簡其會將重任委托給幾個非常重要的類來執行ActivityStarter,ActivityStackSupervisor,ActiveServices,AMS的類圖關系如下所示,并且關于每個類的作用詳見博客Activity啟動流程原始碼實作詳解概要章節2.5這里就不重復了,
    在這里插入圖片描述

  • 這里的mActivityStarter是ActivityStarter的實體物件,其是在AMS的構造方法中被創建的,如下所示可以看到其持有對AMS和ASS的參考,

//AMS.java
final ActivityStackSupervisor mStackSupervisor;
final ActivityStarter mActivityStarter;
final ActiveServices mServices;
public ActivityManagerService(Context systemContext) {
	...
	mServices = new ActiveServices(this);
	mStackSupervisor = new ActivityStackSupervisor(this);
    mActivityStarter = new ActivityStarter(this, mStackSupervisor);
	...
}

3.3 AS.startActivityMayWait

//ActivityStarter.java
    final int startActivityMayWait(IApplicationThread caller, 
    								int callingUid,
            						String callingPackage, 
            						Intent intent, 
            						String resolvedType,
            						IVoiceInteractionSession voiceSession, 
            						IVoiceInteractor voiceInteractor,
            						IBinder resultTo, 
            						String resultWho, 
            						int requestCode, 
            						int startFlags,
            						ProfilerInfo profilerInfo, 
            						IActivityManager.WaitResult outResult, 
            						Configuration config,
            						Bundle bOptions, 
            						boolean ignoreTargetSecurity, 
            						int userId,
            						IActivityContainer iContainer, 
            						TaskRecord inTask) {

??在正式開始分析該方法前,我們先對該方法的引數和入參整理一下,該方法的引數不少啊,總共有19個至多(不知道數對了沒有啊),其引數型別和取值如下:

引數型別 引數名稱引數含義及取值
IApplicationThread caller 和發起端行程匿名Binder物件ActivityThread進行通信的代理端ATP
int callingUid 取值為-1,看引數命名應該是表示發起端的行程的uid
String callingPackage 發起端行程所在包名
Intent intent 啟動目的端Activity傳遞過來的引數,其中攜帶目的端Acitivity隱式或者顯示啟動需要的引數
String resolvedType 發起端行程通過呼叫intent.resolveTypeIfNeeded而獲取
IVoiceInteractionSession voiceSession 意義不明,取值為null
IVoiceInteractor voiceInteractor 意義不明,取值為null
IBinder resultTo 引數型別也為IBinder實體,指向發起端Activity的ActivityRecord物件中的Token,其Binder物體在AMS中
String resultWho 來自于當前發起端Activity.mEmbeddedID,可能為null
int requestCode 啟動目的端Activity的請求碼,此時的取值為-1
int startFlags 此時取值為0,代指Activity的啟動模式
ProfilerInfo profilerInfo 此時取值null,這個引數暫時沒有搞懂是干啥的
WaitResult outResult 此時取值為null,這個引數暫時沒有搞懂是干啥的
Configuration config 應該是表示啟動Activity的Configuration配置資訊(不太確定),此時取值為null
Bundle options 啟動目的端Activity附加引數,此時取值為null
boolean ignoreTargetSecurity 是否忽略檢查發起端行程的安全,此時取值為false
int userId 通過mUserController.handleIncomingUser,當呼叫者userId跟當前處于同一個userId,則直接回傳該userId;當不相等時則根據呼叫者userId來決定是否需要將callingUserId轉換為mCurrentUserId
IActivityContainer iContainer 此時取值為null
TaskRecord inTask 此時取值為null

??入參分析完了,我們接著分析原始碼:

//ActivityStarter.java
    final int startActivityMayWait(IApplicationThread caller, int callingUid,
            String callingPackage, Intent intent, String resolvedType,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int startFlags,
            ProfilerInfo profilerInfo, IActivityManager.WaitResult outResult, Configuration config,
            Bundle bOptions, boolean ignoreTargetSecurity, int userId,
            IActivityContainer iContainer, TaskRecord inTask) {
		...
        final Intent ephemeralIntent = new Intent(intent);
        //以傳遞進來的intent為引數重新創建新的Intent物件,即便intent被修改也不受影響
        intent = new Intent(intent);
        //收集Intent所指向的Activity資訊, 當存在多個可供選擇的Activity,則直接向用戶彈出resolveActivity 
        ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);//重點分析該方法,詳見章節3.4
        if (rInfo == null) {//不會進入該分支
        	...
        }
		//根據獲取的rInfo資訊重新組裝intent和設定啟動的引數資訊
        ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);//詳見3.5
        ...

        final ActivityStack stack;
        if (container == null || container.mStack.isOnHomeDisplay()) {//傳入的引數container為null
            stack = mSupervisor.mFocusedStack;//進入該分支
        } else {
            stack = container.mStack;
        }
		//此時傳入的config為null
        stack.mConfigWillChange = config != null && mService.mConfiguration.diff(config) != 0;
        ...
        if (aInfo != null &&
                (aInfo.applicationInfo.privateFlags
                        & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
            //hwavy-weight行程,貌似木有見過這個東東,暫時不管
        	...
        }
        ...
         final ActivityRecord[] outRecord = new ActivityRecord[1];

		//繼續跟進
         int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
                 aInfo, rInfo, voiceSession, voiceInteractor,
                 resultTo, resultWho, requestCode, callingPid,
                 callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
                 options, ignoreTargetSecurity, componentSpecified, outRecord, container,
                 inTask);//詳見章節3.6

         Binder.restoreCallingIdentity(origId);
		...
		return res;        
}

該方法的主要功能如下:

  • 借助ASS的resolveIntent和resolveActivity方法,通過傳遞進來的引數(主要是intent)查找合適的目標Actitiy,并將保存到ActivityInfo中
  • 繼續呼叫startActivityLocked方法,繼續未完成啟動作業

此時整個上述整個流程的偽代碼如下:

	AMS.startActivity(...)
	 ActivityStarter.startActivityMayWait(...)
	   ResolveInfo rInfo = ASS.resolveIntent(...)//收集Intent所指向的Activity資訊, 當存在多個可供選擇的Activity,則直接向用戶彈出resolveActivity
	     IPackageManager.Stub.resolveIntent(...)//通過PKMS物體查詢
	   ActivityInfo aInfo = ASS.resolveActivity(...)根據獲取的rInfo資訊重新組裝intent和設定啟動的引數資訊
	   ActivityStarter.startActivityLocked(...)

3.4 通過intent查詢獲取目的端Activity資訊

??在正式開始該流程分析前,我們先來看看ResolveInfo,它是一個容器類,里面包含了ActivityInfo,ServiceInfo,ProviderInfo等成員來表示四大組件的資訊,activity和broadcast資訊都是用ActivityInfo來表示的,這三個成員只有一個不為空,這里我們啟動的是activity,所以ActivityInfo是不為空的,ActivityInfo包含了各種各樣的activity資訊,都是宣告在AndroidManifest.xml檔案中的,比較重要的包括launchMode、theme、screenOrientation等,其中還包含了ApplicationInfo,提供packageName、targetSdkVersion等重要資訊,關于這兩者之間的關系,可以使用如下類圖表示:
在這里插入圖片描述

3.4.1 ASS.resolveIntent

//[ActivityStackSupervisor.java]
    ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId) {
        return resolveIntent(intent, resolvedType, userId, 0);
    }

    ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags) {
        try {
        	//呼叫到同行程PKMS服務的resolveIntent
            return AppGlobals.getPackageManager().resolveIntent(intent, resolvedType,
                    PackageManager.MATCH_DEFAULT_ONLY | flags
                    | ActivityManagerService.STOCK_PM_FLAGS, userId);//詳見章節3.4.2
        } catch (RemoteException e) {
        }
        return null;
    }

??由于該流程牽涉到PKMS服務的呼叫,在開始原始碼前我們先看看PKMS的類圖關系如下:
在這里插入圖片描述

//[AppGlobals.java]
    public static IPackageManager getPackageManager() {
        return ActivityThread.getPackageManager();
    }

//[ActivityThread.java]
	static volatile IPackageManager sPackageManager;
    public static IPackageManager getPackageManager() {
        if (sPackageManager != null) {
            return sPackageManager;
        }
        //等同于IBinder b = new JavaBBinder()//此時AMS和PKMS在同一個行程,所以獲取的是PKMS的Binder服務的物體端,不需要通過Binder IPC跨行程呼叫
        IBinder b = ServiceManager.getService("package");
        //等同于sPackageManager  = new IPackageManager.Stub(JavaBBinder)
        sPackageManager = IPackageManager.Stub.asInterface(b);
        return sPackageManager;
    }

??AppGlobals.getPackageManager()經過函式層層呼叫,獲取的是IPackageManager.Stub,注意獲取的是PKMS的物體端,所以無需經過Binder IPC呼叫,此處一定要特別注意!最侄訓像是用普通的類一樣直接呼叫到PKMS物件.故此時呼叫方法為PMS.resolveIntent(),至于其中涉及的具體轉換可以詳見博客Android Binder框架實作之Java層Binder服務跨行程呼叫原始碼分析

3.4.2 PKMS.resolveIntent

??通過上述的分析,我們看到AMS決議Intent主要是通過PKMS來決議的,因為我們四大組件都是必須宣告在AndroidManifest.xml檔案中的(廣播接收器允許動態注冊),Android這么做的原因主要是為了屏蔽行程間通訊細節,應用之間通過組件就可以互動,系統會在必要的時候拉起對方行程,但是這里會存在一個問題,即在應用沒起來之前,只有PMS知道應用都有哪些組件,所以AMS必須借助PKMS來完成相關intent的查詢,應用四大組件的資訊在應用安裝的時候,就已經被PMS決議保存起來了,如果沒有宣告在AndroidManifest.xml檔案中,那么AMS就無法獲取目標組件的資訊,對于顯式intent,會拋出錯誤;對于隱式intent,也會啟動失敗,關于PKSMS服務對App安裝的處理不是本文的重點,這里不與過多分析,

//[PackageManagerService.java]
  @Override
  public ResolveInfo resolveIntent(Intent intent, String resolvedType,
          int flags, int userId) {
            enforceCrossUserPermission(Binder.getCallingUid(), userId,false /*requireFullPermission*/, false /*checkShell*/, "resolve intent");

			//queryIntentActivitiesInternal方法回傳的結果就是符合intent的ActivityInfo串列
            final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType,
                    flags, userId);//詳見章節3.4.3

	
			//根據priority,preferred選擇最佳的Activity,當有多個合適的Actitiy的時候,會彈出選擇框提供給用戶進行選擇
            final ResolveInfo bestChoice =
                    chooseBestActivity(intent, resolvedType, flags, query, userId);

            return bestChoice;
  }

3.4.3 PKMS.queryIntentActivitiesInternal

//[PackageManagerService.java]
	/*
	1.查看當前Intent是否是顯式Intent,是則取出其中的class物件和AndroidManifest的進行匹配,匹配成功回傳,
	2.如果沒有指定包名則全系統的查找匹配intent
	3.如果指定包名,則從當前的包名尋找匹配規則相符合的intent的Activity
	*/
    private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
            String resolvedType, int flags, int userId) {
        if (!sUserManager.exists(userId)) return Collections.emptyList();
        flags = updateFlagsForResolve(flags, userId, intent);
        enforceCrossUserPermission(Binder.getCallingUid(), userId,
                false /* requireFullPermission */, false /* checkShell */,
                "query intent activities");
		//獲取Intent的ComponentName
		ComponentName comp = intent.getComponent();
        if (comp == null) {
            if (intent.getSelector() != null) {
                intent = intent.getSelector();
                comp = intent.getComponent();
            }
        }

		//不為空,則是通過顯示Intent啟動,直接獲取到ActivityInfo回傳
        if (comp != null) {
            final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
			//獲取Activity資訊
            final ActivityInfo ai = getActivityInfo(comp, flags, userId);
            if (ai != null) {
                final ResolveInfo ri = new ResolveInfo();
                ri.activityInfo = ai;
                list.add(ri);
            }
            return list;
        }
        //comp為空,則是隱式啟動
        synchronized (mPackages) {
            final String pkgName = intent.getPackage();
            if (pkgName == null) {//如果包名為空,則會通過ActivityIntentResolver等進行模糊匹配,比如根據Action、Category等
            	...
                return result;
            }
			// 通過包名獲取到Package物件
            final PackageParser.Package pkg = mPackages.get(pkgName);
            if (pkg != null) {
                return filterIfNotSystemUser(
                        mActivities.queryIntentForPackage(
                                intent, resolvedType, flags, pkg.activities, userId),
                        userId);
            }
            return new ArrayList<ResolveInfo>();
        }
    }     

??上面的流程就是通過intent獲取目的Activity的ResolveInfo核心代碼,這里不過多展開了(因為牽涉到PKMS對App的安裝決議流程),它的大致程序如下:

  • 首先獲取Intent的Component物件,如果不為空,說明指定了Componet,那么就直接通過Componet找到ActivityInfo串列,并且這個串列size為1,所以這個ActivityInfo就是指定需要跳轉的組件,
  • 如果沒有指定Component,那就是隱式Intent呼叫,接著獲取Intent傳遞的需要跳轉的包名,
  • 如果包名為空,則會通過ActivityIntentResolver等進行模糊匹配,比如根據Action、Category等,
  • 如果包名不為空,則直接根據包名來獲取到對應的ActivityInfo物件,而mActivities就是PMS存盤的activity資訊表,

3.4.4 通過intent查詢獲取目的端Activity資訊小結

??至此通過intent查詢獲取目的端Activity資訊就告一段落了,此時我們借助PKMS服務決議intent獲取到了合適的ResolveInfo(啟動包含了ActivityInfo資訊),那么接下來就可以繼續余下的作業了,我們還是用相關的偽代碼來總結一下上述的流程:

   ResolveInfo rInfo = ASS.resolveIntent(...)//收集Intent所指向的Activity資訊, 當存在多個可供選擇的Activity,則直接向用戶彈出resolveActivity
     IPackageManager.Stub.resolveIntent(...)//通過PKMS物體查詢
	   PKMS.resolveIntent(...)
		 PKMS.queryIntentActivitiesInternal(...)
		 PKMS.chooseBestActivity(...)

3.5 ASS.resolveActivity

讓我們整理下思路,回到章節3.3繼續分析resolveActivity方法

//[ActivityStackSupervisor.java]
	/*根據獲取的rInfo資訊重新組裝intent和設定啟動的引數資訊
	 startFlags = 0; 
	 profilerInfo = null; 
	 rInfo 指向通過PKMS決議intent資訊獲取到的ResolveInfo實體物件,其中包含ActivityInfo的資訊
	*/
    ActivityInfo resolveActivity(Intent intent, ResolveInfo rInfo, int startFlags,
            ProfilerInfo profilerInfo) {
        final ActivityInfo aInfo = rInfo != null ? rInfo.activityInfo : null;
        if (aInfo != null) {
            intent.setComponent(new ComponentName(
                    aInfo.applicationInfo.packageName, aInfo.name));
            if (!aInfo.processName.equals("system")) {//對于非system行程,根據flags來設定相應的debug資訊
                if ((startFlags & ActivityManager.START_FLAG_DEBUG) != 0) {//用于除錯debug app
                    mService.setDebugApp(aInfo.processName, true, false);
                }

                if ((startFlags & ActivityManager.START_FLAG_NATIVE_DEBUGGING) != 0) {//除錯native
                    mService.setNativeDebuggingAppLocked(aInfo.applicationInfo, aInfo.processName);
                }

                if ((startFlags & ActivityManager.START_FLAG_TRACK_ALLOCATION) != 0) {//用于除錯allocation tracking
                    mService.setTrackAllocationApp(aInfo.applicationInfo, aInfo.processName);
                }

                if (profilerInfo != null) {
                    mService.setProfileApp(aInfo.applicationInfo, aInfo.processName, profilerInfo);
                }
            }
        }
        return aInfo;
    }

resolveActivity方法的處理邏輯比較簡單,主要是根據前面獲取的rInfo資訊重新組裝intent和設定Activity啟動的引數資訊,ActivityManager類提供了如下3個flags用于除錯,如下:

  • START_FLAG_DEBUG:用于除錯debug app
  • START_FLAG_NATIVE_DEBUGGING:用于除錯native
  • START_FLAG_TRACK_ALLOCATION: 用于除錯allocation tracking

3.6 AS.startActivityLocked

??讓我們整理下思路,回到章節3.3繼續分析startActivityLocked方法,在繼續分析之前,我們還是整理整理system_server行程從接收到啟動Activity至此startActivityLocked的呼叫流程!

	AMS.startActivity(...)
	 ActivityStarter.startActivityMayWait(...)
	   ResolveInfo rInfo = ASS.resolveIntent(...)//收集Intent所指向的Activity資訊, 當存在多個可供選擇的Activity,則直接向用戶彈出resolveActivity
	     IPackageManager.Stub.resolveIntent(...)//通過PKMS物體查詢
		   PKMS.resolveIntent(...)
			 PKMS.queryIntentActivitiesInternal(...)
			 PKMS.chooseBestActivity(...)
	   ActivityInfo aInfo = ASS.resolveActivity(...)根據獲取的rInfo資訊重新組裝intent和設定啟動的引數資訊
	   ActivityStarter.startActivityLocked(...)

??不知道小伙們,我這個節奏是否還跟得上!如果小伙們感覺有點吃力了,不要退縮堅持就好了,因為當初的我也是這么過來的!大伙可以先上上廁所,喝杯茶,我們接著繼續!

//[ActivityStarter.java]
    final int startActivityLocked(IApplicationThread caller, //caller是請求啟動當前Activity的發起方,IApplicationThread類是AMS呼叫ActivityThread的IBinder介面,在啟動者的ActivityThread中定義
			Intent intent, //啟動當前Activity的intent
			Intent ephemeralIntent,//啟動當前Activity的intent復本
            String resolvedType, //啟動當前Activity的resolvedType
            ActivityInfo aInfo, //當前啟動的Activity的ActivityInfo(PackageManger決議獲得,具體獲取見前面的操作步驟
            ResolveInfo rInfo,//目標Activity的ResolveInfo資訊
            IVoiceInteractionSession voiceSession, //暫時忽略
            IVoiceInteractor voiceInteractor,//暫時忽略
            IBinder resultTo, // 呼叫方Activity的ActivityRecord,每個Activity在啟動之后,AMS均會將這個Activity的ActivityRecord的IBinder再傳遞給Activity,作為其在AMS中的標識,
            String resultWho, 
            int requestCode, 
            int callingPid, int callingUid,//用于權限檢查,檢查請求放是否有權限啟動這個Activity
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
            ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
            TaskRecord inTask) {
        int err = ActivityManager.START_SUCCESS;

		//用來存盤呼叫者的行程記錄物件
        ProcessRecord callerApp = null;
        if (caller != null) {
            callerApp = mService.getRecordForAppLocked(caller);//獲取呼叫者的行程資訊
            if (callerApp != null) {
                callingPid = callerApp.pid;
                callingUid = callerApp.info.uid;
            } else {
				...
                err = ActivityManager.START_PERMISSION_DENIED;
            }
        }

        final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;



		//記錄發起端Activity資訊記錄物件
        ActivityRecord sourceRecord = null;//表示請求啟動當前Activity的Activity
        ActivityRecord resultRecord = null;//表示當前activity啟動之后需要得到回傳結果的activity
		
		/**若當前啟動方式是以startActivityForResult啟動的,
		*則保存在呼叫者Activity在sourceRecord中,
		*如果啟動時候設定了requestCode并且大于是0,那么將接收
		*startActivityForResult回傳值的Activity保存在resultRecord中,
		*在此種情況下,呼叫者和接識訓傳者的Activity是相同的
		*是不是還是很抽象,我們來舉個例子:
		*假如Activity A通過startActivityForResult啟動B,并且requestCode>0
		*那么此時sourceRecord和resultRecord都為A
		*/
		if (resultTo != null) {
			//獲取呼叫者所在的Activity
            sourceRecord = mSupervisor.isInAnyStackLocked(resultTo);
            if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
                    "Will send result to " + resultTo + " " + sourceRecord);
            if (sourceRecord != null) { //一般情況下,sourceRecord的activity使用startActivityForResult()啟動當前activity且requestCode>=0,那么resultRecord = sourceRecord
                if (requestCode >= 0 && !sourceRecord.finishing) {
                    resultRecord = sourceRecord;
                }
            }
        }

        final int launchFlags = intent.getFlags();

		// Activity執行結果的回傳由源Activity轉換到新Activity, 不需要回傳結果則不會進入該分支
		// 特殊情況,若sourceRecord啟動當前activity時設定了標記Intent.FLAG_ACTIVITY_FORWARD_RESULT,且requestCode<0, 那么當前activity的resultRecord等于sourceRecord.resultTo,也就是sourceRecord的resultRecord
		
		/**
		 *FLAG_ACTIVITY_FORWARD_RESULT這個FLAG作用,舉個例子:
		 * 當一個應用中存在有ActivityA,B,C,A以startActivityForResult方式啟動了B,
		 * 此時B設定了FLAG_ACTIVITY_FORWARD_RESULT啟動了C,注意此時B啟動C
		 * 時requestCode不設定或者設定值要小于0,不然會報錯,
		 * 那么原來在A中接識訓傳值從B回傳的,但這時A接收的回傳值變成從C回傳,
		 */

		if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) {
            // Transfer the result target from the source activity to the new
            // one being started, including any failures.
            // 將sourceRecord的resultRecord轉移給當前新啟動的activity
            if (requestCode >= 0) {
                ActivityOptions.abort(options);
                return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
            }
            resultRecord = sourceRecord.resultTo;
            if (resultRecord != null && !resultRecord.isInStackLocked()) {
                resultRecord = null;
            }
            resultWho = sourceRecord.resultWho;
            requestCode = sourceRecord.requestCode;
            sourceRecord.resultTo = null;
            if (resultRecord != null) {
                resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode);
            }
            if (sourceRecord.launchedFromUid == callingUid) {
				...
                callingPackage = sourceRecord.launchedFromPackage;
            }
        }

        if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
            //從Intent中無法找到相應的Component
            err = ActivityManager.START_INTENT_NOT_RESOLVED;
        }

        if (err == ActivityManager.START_SUCCESS && aInfo == null) {
            //從Intent中無法找到相應的ActivityInfo
            err = ActivityManager.START_CLASS_NOT_FOUND;
        }



		執行后resultStack = null
        final ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;

        if (err != START_SUCCESS) {
            if (resultRecord != null) {
                resultStack.sendActivityResultLocked(
                        -1, resultRecord, resultWho, requestCode, RESULT_CANCELED, null);
            }
            ActivityOptions.abort(options);
            return err;
        }

		//權限檢測
        boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
                requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp,
                resultRecord, resultStack, options);
        abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
                callingPid, resolvedType, aInfo.applicationInfo);

        if (mService.mController != null) {
            try {
                Intent watchIntent = intent.cloneFilter();
                abort |= !mService.mController.activityStarting(watchIntent,
                        aInfo.applicationInfo.packageName);
            } catch (RemoteException e) {
                mService.mController = null;
            }
        }

		...
        if (abort) {//權限檢查不滿足,才進入該分支則直接回傳
            if (resultRecord != null) {
                resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
                        RESULT_CANCELED, null);
            }

            ActivityOptions.abort(options);
            return START_SUCCESS;
        }

		...


		//創建Activity記錄物件
        ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
                intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
                requestCode, componentSpecified, voiceSession != null, mSupervisor, container,
                options, sourceRecord);//詳見章節3.7
        if (outActivity != null) {
            outActivity[0] = r;// 將創建的ActivityRecord回傳
        }

        if (r.appTimeTracker == null && sourceRecord != null) {
            // If the caller didn't specify an explicit time tracker, we want to continue
            // tracking under any it has.
            r.appTimeTracker = sourceRecord.appTimeTracker;
        }

		// 將mFocusedStack賦予當前stack,這里的mFocusedStack表示當前活動的
        final ActivityStack stack = mSupervisor.mFocusedStack;
        if (voiceSession == null && (stack.mResumedActivity == null
                || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
            // 前臺stack還沒有resume狀態的Activity時, 則檢查app切換是否允許    
            if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
                    realCallingPid, realCallingUid, "Activity start")) {
                PendingActivityLaunch pal =  new PendingActivityLaunch(r,
                        sourceRecord, startFlags, stack, callerApp);
				// 當不允許切換,則把要啟動的Activity添加到mPendingActivityLaunches物件, 并且直接回傳
                mPendingActivityLaunches.add(pal);
                ActivityOptions.abort(options);
                return ActivityManager.START_SWITCHES_CANCELED;
            }
        }

        if (mService.mDidAppSwitch) {
            //從上次禁止app切換以來,這是第二次允許app切換,因此將允許切換時間設定為0,則表示可以任意切換app
            mService.mAppSwitchesAllowedTime = 0;
        } else {
            mService.mDidAppSwitch = true;
        }

		//處理 pendind Activity的啟動, 這些Activity是由于app switch禁用從而被hold的等待啟動activity
        doPendingActivityLaunchesLocked(false);

        try {
			//這個方法很長,很長,很長
            err = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
                    true, options, inTask);
        } finally {
            mService.mWindowManager.continueSurfaceLayout();
        }
        postStartActivityUncheckedProcessing(r, err, stack.mStackId, mSourceRecord, mTargetStack);
        return err;
    }

startActivityLocked方法看著代碼一大堆,是有蠻唬人的,其實主要邏輯就是:

  • 進一步對發起端的行程做一些權限檢查,然后接著確定sourceRecord和resultRecord的取值
  • 接著通過上述確認的引數構建ActivityRecord,這個是重點會在后面章節分析
  • 接著繼續呼叫startActivityUnchecked方法開始后續的Activity啟動,這個方法牽涉的邏輯很長,很長

3.7 ActivityRecord

??ActivityRecord是AMS中保存activity資訊的資料結構,AMS就是通過著這樣一個結構來管理Activity的,注意和ActivityInfo的區別!我個人認為二者最大的區別在與ActivityInfo是死的,它是對AndroidManifest.xml的完全決議的呈現,而ActivityRecord是根據發起端行程創建目標Activiyt傳入的引數等情況確定的,

ActivityRecord類的定義如下:

//ActivityRecord 
final class ActivityRecord {                                      
	/*表示AMS在創建ActivityRecord時候指定*/
	final ActivityManagerService service; 
	
	/*這個是和WindomMangerService通信的,該值會賦值給Activity中*/
	final IApplicationToken.Stub appToken; 
	
	/*這個描述是AndroidManifest.xml中定義activity相關資訊*/
	final ActivityInfo info; 
	
	/*這個描述是AndroidManifest.xml中定義application相關資訊*/
	final ApplicationInfo appInfo; 
	
	...
	
	/*應用的包名*/
	final String packageName; 
	
	final String processName; 
	/**Activity親和性,這上簡單的描述就是該Activity應屬于是哪個Task*/
	final String taskAffinity; 
	
	...
	/**以下三個表示該Activity型別*/               
	static final int APPLICATION_ACTIVITY_TYPE= 0;
	static final int HOME_ACTIVITY_TYPE = 1;
	static final int RECENTS_ACTIVITY_TYPE = 2;
	int mActivityType;
	...
	/**Activity屬于哪個Task*/
	TaskRecord task;       
	/**通過startActivityForResult啟動時,接識訓傳值的Activity*/
	ActivityRecord resultTo; 
	/**通過startActivityForResult啟動時設定的requestCode*/
	final int requestCode;  
	...
	
	/*Activity所屬的行程*/
	ProcessRecord app;      
	
	/*表示Activity的當前狀態, INITIALIZING,RESUMED,PAUSING,PAUSED,STOPPING,STOPPED,FINISHING,DESTROYING,DESTROYED
	有這幾種狀態*/
	ActivityState state;   
	/*標記是否為Task的root Activity,比如每個應用有一個mainActivity,一般	
	*情況下該main Activity即為對應Task的rootActivity*/
	boolean frontOfTask;    
	
	/*標記該ActivityRecord是否已經stoped*/
	boolean stopped;        
	
	/*標記ActivityRecord是否已經finishing,這在activity finish時置為true*/
	boolean finishing;      
	
	boolean deferRelaunchUntilPaused;   
	                                  
	boolean visible;       
	boolean sleeping;       
	boolean nowVisible;    
	/*ActivityStack管理類*/
	final ActivityStackSupervisormStackSupervisor;

    ActivityRecord(ActivityManagerService _service, ProcessRecord _caller,
            int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType,
            ActivityInfo aInfo, Configuration _configuration,
            ActivityRecord _resultTo, String _resultWho, int _reqCode,
            boolean _componentSpecified, boolean _rootVoiceInteraction,
            ActivityStackSupervisor supervisor,
            ActivityContainer container, ActivityOptions options, ActivityRecord sourceRecord) {
    	...
    	appToken = new Token(this, service);//注意此處的Token是一個匿名Binder,既然是Binder就可以跨行程傳輸了
    	state = ActivityState.INITIALIZING;
    	...
    }

我還得在2.3章節中對execStartActivity中傳入的引數token嗎,它的Binder物體就是在啟動程序中經由此處創建的(當然它的Binder物體不是這里的Token,這里只是表達告訴各位小伙們這個token會隨著Activity的創建會在AMS,WMS,Activity之間進行傳遞的)YOU

??有了以上知識的鋪墊,那么此處的構造方法就很好理解了在AMS中為目標Activity構建了一個ActivityRecord資料結構,并且在其構造方法中實體化了一個Token,這個Token很重要它是Activity的唯一標識,并且會隨著目標Activity的啟動在AMS和WMS以及目標行程中傳輸!至此目標Ativity相關資訊已經在AMS中建立了!
??這里補充一個小的知識點,我們可以借助dumpsys命令查看AMS中相關的ActivityRecord的資料結構資訊,如下!特別是當我們想查看當前與用戶互動的前臺Activity時Android通過adb shell命令查看當前與用戶互動的前臺Activity

130|XXX:/ # dumpsys activity | grep ActivityRecord
        Hist #0: ActivityRecord{4c7a084 u0 com.android.launcher3/.Launcher t73}
        Run #0: ActivityRecord{4c7a084 u0 com.android.launcher3/.Launcher t73}
        Hist #0: ActivityRecord{68a76e3 u0 com.android.calculator2/.Calculator t77}
        Hist #0: ActivityRecord{697cb49 u0 com.cyanogenmod.filemanager/.activities.NavigationActivity t76}
        Hist #0: ActivityRecord{4c3e469 u0 org.codeaurora.gallery/com.android.gallery3d.app.GalleryActivity t75}
        Hist #0: ActivityRecord{8a53d39 u0 com.pax.printtest/com.example.api.aidl.AidlActivity t74}
        Run #3: ActivityRecord{68a76e3 u0 com.android.calculator2/.Calculator t77}
        Run #2: ActivityRecord{697cb49 u0 com.cyanogenmod.filemanager/.activities.NavigationActivity t76}
        Run #1: ActivityRecord{4c3e469 u0 org.codeaurora.gallery/com.android.gallery3d.app.GalleryActivity t75}
        Run #0: ActivityRecord{8a53d39 u0 com.pax.printtest/com.example.api.aidl.AidlActivity t74}
  mFocusedActivity: ActivityRecord{4c7a084 u0 com.android.launcher3/.Launcher t73}


小結

閱讀原始碼是很操蛋的事情,同樣的如果將太多的邏輯放在一篇中我想小伙們也會閱讀起來也會操蛋,失去耐心的的,所以本篇章就先到這里了,分析至此我們應該有了如下的知識圖譜:

  • 發起端行程是怎么通過Instrumentation管理類,并且借助AMP完成啟動Activity請求的發送
  • system_server行程中的AMS初步處理啟動Activiyt的請求,并借助PKMS服務決議intent獲取目標Activity的ActivityInfo資訊,然后通過上述決議得到的資料為目標Activiyt構建ActivityRecord資料結構

關于上述整個的程序可以使用下述的偽代碼來簡單描述:

發起端行程發起啟動Activity請求
	Activity.startActivity(...)
	  Activity.startActivityForResult(...)
	    mInstrumentation.execStartActivity(...)
		  AMP.startActivity(...)//通過AMS代理端向AMS發起啟動Activity的Binder IPC請求
		  mInstrumentation.checkStartActivityResult(...)//檢測啟動是否成功
		mMainThread.sendActivityResult(...)



system_server行程端
AMS決議Intent	  
	AMS.startActivity(...)
	 ActivityStarter.startActivityMayWait(...)
	   ResolveInfo rInfo = ASS.resolveIntent(...)//收集Intent所指向的Activity資訊, 當存在多個可供選擇的Activity,則直接向用戶彈出resolveActivity
	     IPackageManager.Stub.resolveIntent(...)//通過PKMS物體查詢
		   PKMS.resolveIntent(...)
			 PKMS.queryIntentActivitiesInternal(...)
			 PKMS.chooseBestActivity(...)
	   ActivityInfo aInfo = ASS.resolveActivity(...)根據獲取的rInfo資訊重新組裝intent和設定啟動的引數資訊
	   ActivityStarter.startActivityLocked(...)
		  ActivityRecord r = new ActivityRecord(callerApp,intent,aInfo,mSupervisor,...)
			appToken = new Token(this, _intent);
			  設定state為INITIALIZING
		  ActivityStarter.startActivityUnchecked(...)  
	   
創建目的端Activity的ActivityRecord
	ActivityStarter.startActivityLocked(...)
	  ActivityRecord r = new ActivityRecord(callerApp,intent,aInfo,mSupervisor,...)
	    appToken = new Token(this, _intent);
		  設定state為INITIALIZING
		

好了,今天的博客就到這里了!閱讀原始碼小伙們一定要做好心理準備,千萬不能半途而廢!在接下來的篇章中我們將繼續分析system_server行程對Activity啟動的請求處理,希望小伙們能繼續關注,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/114939.html

標籤:其他

上一篇:Android WebView使用常見問題以及解決方案(高級)

下一篇:Cocos2d-lua下的Mac/ios啟動流程

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more