Android深刻四大組件(六)Android8.0 根Activity啓動過程(前篇)

相關文章
Android深刻四大組件系列
Android系統啓動系列
Android應用程序進程系列
Android深刻解析AMS系列html

前言

在幾個月前我寫了Android深刻四大組件(一)應用程序啓動過程(前篇)Android深刻四大組件(一)應用程序啓動過程(後篇)這兩篇文章,它們都是基於Android 7.0,當我開始閱讀Android 8.0源碼時發現應用程序(根Activity)啓動過程照Android 7.0有了一些變化,所以又寫下了本篇文章,本篇文章照此前的文章不只流程發生變化,並且增長了一些分析,算是升級版本。因爲篇幅較長,Android8.0 根Activity啓動過程仍舊分爲前篇和後篇來進行講解。java

1.概述

Activity的啓動過程分爲兩種,一種是根Activity的啓動過程,另外一種是普通Activity的啓動過程,根Activity指的是應用程序啓動的第一個Activity,所以根Activity的啓動過程通常狀況下也能夠理解爲應用程序的啓動過程。普通Activity指的是除了應用程序啓動的第一個Activity以外的其餘的Activity。這裏介紹的是根Activity的啓動過程,它和普通Activity的啓動過程是有重疊部分的,只不過根Activity的啓動過程通常狀況下指的就是應用程序的啓動過程,更具備指導性意義。想要了解普通Activity的啓動過程的的同窗能夠參考根Activity的啓動過程去自行閱讀源碼。android

根Activity的啓動過程比較複雜,所以這裏分爲三個部分來說,分別是Launcher請求AMS過程、 AMS到ApplicationThread的調用過程和ActivityThread啓動Activity,本篇文章會介紹前兩個部分。bash

2.Launcher請求AMS過程

Launcher啓動後會將已安裝應用程序的快捷圖標顯示到桌面上,這些應用程序的快捷圖標就是啓動根Activity的入口,當咱們點擊某個應用程序的快捷圖標時就會經過Launcher請求AMS來啓動該應用程序。時序圖以下圖所示。
app

當咱們點擊應用程序的快捷圖標時,就會調用Launcher的startActivitySafely方法,以下所示。
packages/apps/Launcher3/src/com/android/launcher3/Launcher.javaide

public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
        ...
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//1
        if (v != null) {
            intent.setSourceBounds(getViewBounds(v));
        }
        try {
            if (Utilities.ATLEAST_MARSHMALLOW
                    && (item instanceof ShortcutInfo)
                    && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
                     || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
                    && !((ShortcutInfo) item).isPromise()) {
                startShortcutIntentSafely(intent, optsBundle, item);
            } else if (user == null || user.equals(Process.myUserHandle())) {
                startActivity(intent, optsBundle);//2
            } else {
                LauncherAppsCompat.getInstance(this).startActivityForProfile(
                        intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
            }
            return true;
        } catch (ActivityNotFoundException|SecurityException e) {
            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
            Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
        }
        return false;
    }複製代碼

在註釋1處設置Flag爲Intent.FLAG_ACTIVITY_NEW_TASK①,這樣根Activity會在新的任務棧中啓動。在註釋2處會調用startActivity方法,這個startActivity方法的實如今Activity中,以下所示。
frameworks/base/core/java/android/app/Activity.java工具

@Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            startActivityForResult(intent, -1);
        }
    }複製代碼

startActivity方法中會調用startActivityForResult方法,它的第二個參數爲-1,表示Launcher不須要知道Activity啓動的結果,startActivityForResult方法的代碼以下所示。
frameworks/base/core/java/android/app/Activity.java學習

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {//1
            options = transferSpringboardActivityOptions(options);
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
           ...
        } else {
          ...
        }
    }複製代碼

註釋1處的mParent是Activity類型的,表示當前Activity的父類。由於目前根Activity尚未建立出來,所以,mParent == null成立。接着調用Instrumentation的execStartActivity方法,Instrumentation主要用來監控應用程序和系統的交互,execStartActivity方法的代碼以下所示。
frameworks/base/core/java/android/app/Instrumentation.javaui

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        ...
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }複製代碼

首先會調用ActivityManager的getService方法來獲取AMS的代理對象,接着調用它的startActivity方法。這裏與Android 7.0代碼的邏輯有些不一樣,Android 7.0是經過ActivityManagerNative的getDefault來獲取AMS的代理對象,如今這個邏輯封裝到了ActivityManager中而不是ActivityManagerNative中。首先咱們先來查看ActivityManager的getService方法作了什麼:this

frameworks/base/core/java/android/app/ActivityManager.java

public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }

    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);//1
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);//2
                    return am;
                }
            };複製代碼

getService方法調用了IActivityManagerSingleton的get方法,咱們接着往下看,IActivityManagerSingleton 是一個Singleton類。
註釋1處獲得名爲」activity」的Service引用,也就是IBinder類型的AMS的引用。接着在註釋2處將它轉換成IActivityManager類型的對象,這段代碼採用的是AIDL,IActivityManager.java類是由AIDL工具在編譯時自動生成的,IActivityManager.aidl的文件路徑爲:frameworks/base/core/java/android/app/IActivityManager.aidl 。要實現進程間通訊,服務端也就是AMS只須要繼承IActivityManager.Stub類並實現相應的方法就能夠了。
注意Android 8.0 以前並無採用AIDL,而是採用了相似AIDL的形式,用AMS的代理對象ActivityManagerProxy來與AMS進行進程間通訊,Android 8.0 去除了ActivityManagerNative的內部類ActivityManagerProxy,代替它的則是IActivityManager,它是AMS在本地的代理。不理解AIDL能夠查看Android IPC機制(三)在Android Studio中使用AIDL實現跨進程方法調用這篇文章。
回到Instrumentation類的execStartActivity方法中,從上面得知execStartActivity方法最終調用的是AMS的startActivity方法。

3.AMS到ApplicationThread的調用過程

Launcher請求AMS後,代碼邏輯已經走到了AMS中,接着是AMS到ApplicationThread的調用流程,時序圖如圖4-2所示。

AMS的startActivity方法以下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

@Override
    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());
    }複製代碼

AMS的startActivity方法中return了startActivityAsUser方法,能夠發現startActivityAsUser方法比startActivity方法多了一個參數UserHandle.getCallingUserId(),這個方法會得到調用者的UserId,AMS會根據這個UserId來肯定調用者的權限。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

@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");//1
        //檢查調用者權限
        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                userId, false, ALLOW_FULL_ONLY, "startActivity", null);//2
        return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
                resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
                profilerInfo, null, null, bOptions, false, userId, null, null,
                "startActivityAsUser");
    }複製代碼

註釋1處判斷調用者進程是否被隔離,若是被隔離則拋出SecurityException異常,註釋2處用於檢查調用者是否有權限,若是沒有權限也會拋出SecurityException異常。最後調用了ActivityStarter的startActivityLocked方法,startActivityLocked方法的參數要比startActivityAsUser多幾個,須要注意的是倒數第二個參數類型爲TaskRecord,表明啓動的Activity所在的棧。最後一個參數"startActivityAsUser"表明啓動的理由。 代碼以下所示。

frameworks/base/services/core/java/com/android/server/am/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, WaitResult outResult,
            Configuration globalConfig, Bundle bOptions, boolean ignoreTargetSecurity, int userId,
            IActivityContainer iContainer, TaskRecord inTask, String reason) {
         ...
        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, reason);
         ...
         return res;
     }
 }複製代碼

ActivityStarter是Android 7.0新加入的類,它是加載Activity的控制類,會收集全部的邏輯來決定如何將Intent和Flags轉換爲Activity,並將Activity和Task以及Stack相關聯。ActivityStarter的startActivityMayWait方法調用了startActivityLocked方法,以下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java

int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
            ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
            TaskRecord inTask, String reason) {
        //判斷啓動的理由不爲空
        if (TextUtils.isEmpty(reason)) {//1
            throw new IllegalArgumentException("Need to specify a reason.");
        }
        mLastStartReason = reason;
        mLastStartActivityTimeMs = System.currentTimeMillis();
        mLastStartActivityRecord[0] = null;
        mLastStartActivityResult = startActivity(caller, intent, ephemeralIntent, resolvedType,
                aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode,
                callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
                options, ignoreTargetSecurity, componentSpecified, mLastStartActivityRecord,
                container, inTask);
        if (outActivity != null) {
            outActivity[0] = mLastStartActivityRecord[0];
        }
        return mLastStartActivityResult;
    }複製代碼

註釋1處判斷啓動的理由不爲空,若是爲空則拋出IllegalArgumentException異常。緊接着又調用了startActivity方法,以下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java

private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            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;
        final Bundle verificationBundle
                = options != null ? options.popAppVerificationBundle() : null;
        ProcessRecord callerApp = null;
        if (caller != null) {//1
            //獲取Launcher進程
            callerApp = mService.getRecordForAppLocked(caller);//2
            if (callerApp != null) {
              //獲取Launcher進程的pid和uid並賦值
                callingPid = callerApp.pid;
                callingUid = callerApp.info.uid;
            } else {
                Slog.w(TAG, "Unable to find app for caller " + caller
                        + " (pid=" + callingPid + ") when starting: "
                        + intent.toString());
                err = ActivityManager.START_PERMISSION_DENIED;
            }
        }
        ...
        //建立即將要啓動的Activity的描述類ActivityRecord
        ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
                callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),
                resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,
                mSupervisor, container, options, sourceRecord); //2  
        if (outActivity != null) {
            outActivity[0] = r;//3
        }
        ...
            doPendingActivityLaunchesLocked(false);
            return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true,
                options, inTask, outActivity);//4
    }複製代碼

ActivityStarter的startActivity方法邏輯比較多,這裏列出部分咱們須要關心的代碼。註釋1處判斷IApplicationThread類型的caller是否爲null,這個caller是方法調用一路傳過來的,指向的是Launche進程的ApplicationThread對象,在註釋2處調用AMS的getRecordForAppLocked方法獲得的是表明Launcher進程的callerApp對象,它是ProcessRecord類型的,ProcessRecord用於描述一個應用程序進程。一樣的,ActivityRecord用於描述一個Activity,用來記錄一個Activity的全部信息。在註釋2處建立ActivityRecord,這個ActivityRecord用於描述即將要啓動的Activity,並在註釋3處將建立的ActivityRecord賦值給ActivityRecord[]類型的outActivity,這個outActivity會做爲註釋4處的startActivity方法的參數傳遞下去。
frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java

private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
            ActivityRecord[] outActivity) {
        int result = START_CANCELED;
        try {
            mService.mWindowManager.deferSurfaceLayout();
            result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
                    startFlags, doResume, options, inTask, outActivity);
        }
        ...
        return result;
    }複製代碼

startActivity方法緊接着調用了startActivityUnchecked方法:
frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
            ActivityRecord[] outActivity) {
...
 if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {//1
            newTask = true;
            //建立新的TaskRecord
            result = setTaskFromReuseOrCreateNewTask(
                    taskToAffiliate, preferredLaunchStackId, topStack);//2
        } else if (mSourceRecord != null) {
            result = setTaskFromSourceRecord();
        } else if (mInTask != null) {
            result = setTaskFromInTask();
        } else {
            setTaskToCurrentTopOrCreateNewTask();
        }
       ...
 if (mDoResume) {
            final ActivityRecord topTaskActivity =
                    mStartActivity.getTask().topRunningActivityLocked();
            if (!mTargetStack.isFocusable()
                    || (topTaskActivity != null && topTaskActivity.mTaskOverlay
                    && mStartActivity != topTaskActivity)) {
               ...
            } else {
                if (mTargetStack.isFocusable() && !mSupervisor.isFocusedStack(mTargetStack)) {
                    mTargetStack.moveToFront("startActivityUnchecked");
                }
                mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,
                        mOptions);//3
            }
        } else {
            mTargetStack.addRecentActivityLocked(mStartActivity);
        }
        ...

}複製代碼

startActivityUnchecked方法主要處理棧管理相關的邏輯。在標註①處咱們得知,啓動根Activity時會將Intent的Flag設置爲FLAG_ACTIVITY_NEW_TASK,這樣註釋1處的條件判斷就會知足,接着執行註釋2處的setTaskFromReuseOrCreateNewTask方法,其內部會建立一個新的TaskRecord,TaskRecord用來描述一個Activity任務棧,也就是說setTaskFromReuseOrCreateNewTask方法內部會建立一個新的Activity任務棧。Activity任務棧實際上是一個假想的模型,並不真實的存在,關於Activity任務棧能夠閱讀Android解析ActivityManagerService(二)ActivityTask和Activity棧管理這篇文章。在註釋3處會調用ActivityStackSupervisor的resumeFocusedStackTopActivityLocked方法,以下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

boolean resumeFocusedStackTopActivityLocked(
        ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {
    if (targetStack != null && isFocusedStack(targetStack)) {
        return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
    }
    //獲取要啓動的Activity所在棧的棧頂的不是處於中止狀態的ActivityRecord
    final ActivityRecord r = mFocusedStack.topRunningActivityLocked();//1
    if (r == null || r.state != RESUMED) {//2
        mFocusedStack.resumeTopActivityUncheckedLocked(null, null);//3
    } else if (r.state == RESUMED) {
        mFocusedStack.executeAppTransition(targetOptions);
    }
    return false;
}複製代碼

註釋1處調用ActivityStack的topRunningActivityLocked方法獲取要啓動的Activity所在棧的棧頂的不是處於中止狀態的ActivityRecord。註釋2處若是ActivityRecord不爲null,或者要啓動的Activity的狀態不是RESUMED狀態,就會調用註釋3處的ActivityStack的resumeTopActivityUncheckedLocked方法,對於即將要啓動的Activity,註釋2的條件判斷是確定知足,所以咱們來查看ActivityStack的resumeTopActivityUncheckedLocked方法,以下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityStack.java

boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
        if (mStackSupervisor.inResumeTopActivity) {
            return false;
        }
        boolean result = false;
        try {
            mStackSupervisor.inResumeTopActivity = true;
            result = resumeTopActivityInnerLocked(prev, options);//1
        } finally {
            mStackSupervisor.inResumeTopActivity = false;
        }
        mStackSupervisor.checkReadyForSleepLocked();
        return result;
    }複製代碼

緊接着查看註釋1處ActivityStack的resumeTopActivityInnerLocked方法:
frameworks/base/services/core/java/com/android/server/am/ActivityStack.java

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
      ...
           mStackSupervisor.startSpecificActivityLocked(next, true, true);
       }
        if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
       return true;
}複製代碼

resumeTopActivityInnerLocked方法代碼很是多,咱們只須要關注調用了ActivityStackSupervisor的startSpecificActivityLocked方法,代碼以下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

void startSpecificActivityLocked(ActivityRecord r,
            boolean andResume, boolean checkConfig) {
        //獲取即將要啓動的Activity的所在的應用程序進程
        ProcessRecord app = mService.getProcessRecordLocked(r.processName,
                r.info.applicationInfo.uid, true);//1
        r.getStack().setLaunchTime(r);

        if (app != null && app.thread != null) {//2
            try {
                if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
                        || !"android".equals(r.info.packageName)) {
                    app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
                            mService.mProcessStats);
                }
                realStartActivityLocked(r, app, andResume, checkConfig);//3
                return;
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception when starting activity "
                        + r.intent.getComponent().flattenToShortString(), e);
            }
        }
        mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
                "activity", r.intent.getComponent(), false, false, true);
    }複製代碼

註釋1處獲取即將要啓動的Activity的所在的應用程序進程,註釋2處判斷要啓動的Activity的所在應用程序進程已經運行的話,就會調用註釋3處的realStartActivityLocked方法,須要注意的是,這個方法的第二個參數是表明要啓動的Activity的所在的應用程序進程的ProcessRecord。
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
          boolean andResume, boolean checkConfig) throws RemoteException {
   ...
          app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                  System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
                  new Configuration(task.mOverrideConfig), r.compat, r.launchedFromPackage,
                  task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
                  newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
  ...      
      return true;
  }複製代碼

這裏的 app.thread指的是IApplicationThread,它的實現是ActivityThread的內部類ApplicationThread,其中ApplicationThread繼承了IApplicationThread.Stub。app指的是傳入的要啓動的Activity的所在的應用程序進程,所以,註釋1處的代碼指的就是要在目標應用程序進程啓動Activity。當前代碼邏輯運行在AMS所在的進程(SyetemServer進程),經過ApplicationThread來與應用程序進程進行Binder通訊,換句話說,ApplicationThread是AMS所在進程(SyetemServer進程)和應用程序進程的通訊橋樑,以下圖所示。

結語

本文咱們學習了根Activity的啓動過程的前兩個部分,分別是Launcher請求AMS過程、 AMS到ApplicationThread的調用過程,完成第二個部分後代碼邏輯就運行在了應用程序進程中,後篇會接着介紹ActivityThread啓動Activity的過程以及根Activity啓動過程當中涉及的進程。

相關文章
相關標籤/搜索