android N進程啓動流程(一)(捕獲輸入事件、準備創建activity、焦點切換)

android N進程啓動流程(一)(捕獲輸入事件、準備創建activity、焦點切換)

1. 背景

本文主要針對event log中各處節點進行進程啓動流程分析。

//此處使用的是adb指令input tap + location的方法(具體實現可以參考)
//Input.java (frameworks\base\cmds\input\src\com\android\commands\input)
//1、捕獲輸入事件
01-01 00:34:12.298349  8599  8599 I InputManager: PERF:injectInputEvent: MotionEvent { action=ACTION_UP...

//2、準備創建activity
01-01 00:34:12.311531  1135  3103 I am_create_task: [0,38]
01-01 00:34:12.311755  1135  3103 I am_create_activity: [0,201384592,38,test2.com.myapplication/.MainActivity,android.intent.action.MAIN...

//3、焦點切換
01-01 00:34:12.318456  1135  3103 I am_focused_stack: [0,1,0,startedActivity setFocusedActivity]
01-01 00:34:12.323919  1135  3103 I am_focused_activity: [0,test2.com.myapplication/.MainActivity,startedActivity]

//4、上一個activity的暫停,如此處是launcher桌面
01-01 00:34:12.324637  1135  3103 I am_pause_activity: [0,125629571,com.android.launcher/.Launcher]

01-01 00:34:12.342909  2699  2699 I am_on_paused_called: [0,com.android.launcher.Launcher,handlePauseActivity]

//5、進程啓動
01-01 00:34:12.398299  1135  1698 I am_proc_start: [0,8610,10123,test2.com.myapplication,activity,test2.com.myapplication/.MainActivity]

//6、綁定與創建application
01-01 00:34:12.429265  1135  1697 I am_proc_bound: [0,8610,test2.com.myapplication]

//7、創建activity實例並且調用onCreate
01-01 00:34:12.439813  1135  1697 I am_restart_activity: [0,201384592,38,test2.com.myapplication/.MainActivity]

//8、調用active的resume方法
01-01 00:34:12.651913  8610  8610 I am_on_resume_called: [0,test2.com.myapplication.MainActivity,LAUNCH_ACTIVITY]

//9、界面添加完成並可見
01-01 00:34:12.795221  1135  1204 I am_activity_launch_time: [0,201384592,test2.com.myapplication/.MainActivity,417,417]

//10、上一個activity的停止
01-01 00:34:12.832973  1135  1610 I am_stop_activity: [0,125629571,com.android.launcher/.Launcher]
01-01 00:34:12.835790  2699  2699 I am_on_stop_called: [0,com.android.launcher.Launcher,handleStopActivity]

下面主要按照上述流程部分來講解:
* 1、捕獲輸入事件
* 2、準備創建activity
* 3、焦點切換
* 4、上一個activity的暫停,如此處是launcher桌面
* 5、進程啓動
* 6、綁定與創建application
* 7、創建activity實例並且調用onCreate
* 8、調用active的resume方法
* 9、界面添加完成並可見
* 10、上一個activity的停止

此處講解的流程:
=> 在桌面(如android原生luancher:com.android.launcher) -> 點擊test2測試應用的圖標(test2.com.myapplication) -> test2完全顯示給用戶

ps:測試應用test2在該操作之前是沒有進程在後臺運行的。

2. 捕獲輸入事件

輸入事件一般情況有2種: 一種是從觸摸屏點擊屏幕;一種是自動化軟件模擬點擊事件。

捕獲輸入事件流程圖
圖2.1 捕獲輸入事件流程圖

2.1 觸摸點擊屏幕

當用戶手動點擊的時候,會輸出類似如下日誌,如果沒有可以自行添加:

01-01 22:31:24.065   868  1085 D InputReader: PERF:AMOTION_EVENT_ACTION_POINTER_UP:Up

代碼位置:
InputReader.cpp (frameworks\native\services\inputflinger)

    //讀取輸入事件,分發點擊事件給上層
    void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
    //...
    // Dispatch pointer up events.
    while (!upIdBits.isEmpty()) {
        uint32_t upId = upIdBits.clearFirstMarkedBit();
        {
            //輸出點擊擡起的log,這段log原生是沒有的
            ALOGD("PERF:AMOTION_EVENT_ACTION_POINTER_UP:Up");

            //分發action up的事件
            dispatchMotion(when, policyFlags, mSource,
                    AMOTION_EVENT_ACTION_POINTER_UP, 0, 0, metaState, buttonState, 0,
            //...
        }
    }

    // Dispatch pointer down events using the new pointer locations.
    while (!downIdBits.isEmpty()) {
    //...
        if (dispatchedIdBits.count() == 1) {
            // First pointer is going down.  Set down time.
            mDownTime = when;
            //輸出點擊按下的log,這段log原生是沒有的
            ALOGD("PERF:AMOTION_EVENT_ACTION_POINTER_DOWN:Down");
        }
        //分發action down的事件
        dispatchMotion(when, policyFlags, mSource,
            AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0, metaState, buttonState, 0,
    //...

一般響應時間從點擊擡起開始算時間(一般認爲用戶手擡起了,就認爲用戶的操作已經表述清楚,需要儘快處理)

2.2 自動化模擬點擊事件

此處方法介紹最常見的2種:
1) 使用Instrumentation(代理控制框架)
2) 使用adb指令:input tap

兩種達到的效果是一致的,至於開始的地方可以添加自己想要的日誌信息。

1、使用Instrumentationm模擬點擊事件

    //創建Instrumentation實例
    Instrumentation inst = new Instrumentation();
    //點擊事件開始時間爲當前時間
    long now = SystemClock.uptimeMillis();
    //發送點擊按下事件
    inst.sendPointerSync(MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, mx, my, 0));
    now = SystemClock.uptimeMillis();
    //發送點擊擡起事件
    inst.sendPointerSync(MotionEvent.obtain(now + 10, now + 10, MotionEvent.ACTION_UP, mx, my, 0));

上述是模擬點擊事件的方法,通過這個方法其實很多應用都可以實現自動化測試,這個方法是很基礎的,如有需要大家可以自行拓展。

可以添加輸出日誌的地方:
// frameworks/base/core/java/android/hardware/input/InputManager.java

    public boolean injectInputEvent(InputEvent event, int mode) {
        //...
        //所有模擬的點擊分發事件都是通過這個地方運行,所以方法1、2都可以在這裏添加日誌
        Log.i(TAG, "PERF:injectInputEvent: " + event);
        return mIm.injectInputEvent(event, mode);
        //...
    }

2、使用adb指令:input tap

//mx代表的是橫向x軸的位置,my代表的是縱向y軸的位置
adb shell input tap mx my

至於日誌LOG輸出的地方可以參考上面第一種方法中的injectInputEvent函數

3. 準備創建activity

Launcher響應打開事件(如點擊一個應用圖標)會啓動應用,會調用Activity或者ContextImpl的startActivity啓動應用。

準備創建activity流程圖
圖3.1 準備創建activity流程圖

3.1 startActivity(ContextImpl.java)

ContextImpl.java的startActivity,應用一般都會有context實例,啓動應用最簡單的辦法是startActivity+傳遞一個intent

    //startActivity啓動應用這個api大家應該不陌生
    public void startActivity(Intent intent) {
        startActivity(intent, null);
    }

    public void startActivity(Intent intent, Bundle options) {
        //通過獲取Instrumentation代理來啓動應用,這裏主要是爲了規範化,都通過Instrumentation去中轉
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }

3.2 execStartActivity(Instrumentation.java)

Instrumentation.java其是一個工具類,可以用來啓動應用

    //execStartActivity執行啓動活動對象Activity的操作
    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
            //...
            //中轉AMS,具體實現是在AMS的startActivity
            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
      }

3.3 startActivity(ActivityManagerService.java)

ActivityManagerService.java這裏最後會走到ActivityStarter的 startActivityMayWait函數,纔是真正的啓動應用的地方

    public final int startActivity(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
        //android是支持多用戶的,故此處還需要傳入用戶組的ID
        return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
                resultWho, requestCode, startFlags, profilerInfo, bOptions,
                UserHandle.getCallingUserId());
    }

    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) {
        // ...
        // 最後走的地方是ActivityStarter應用啓動器的startActivityMayWait,
        // 此處傳遞的WaitResult == null,代表啓動完成之後無需返回啓動時間的結果
        return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
                resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
                profilerInfo, null, null, bOptions, false, userId, null, null);
    }

3.4 startActivityMayWait(ActivityStarter.java)

ActivityStarter活動對象啓動器

    final int startActivityMayWait(IApplicationThread caller, int callingUid,
        //...
        //通過PMS解析意圖
        ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
        //...
        //獲取該意圖的活動對象Activity的Info信息,裏面有需要啓動應用的信息如包名test2
        ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);
        //這裏纔是真正啓動應用的地方
        int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
        //...
        //outResult輸出activity的啓動結果,一般自動化測試的中使用
            if (outResult != null) {
                    //...
                    do {
                        try {
                            //只有等到mService.notify()或者mService.notifyAll()此處纔會繼續執行下去
                            mService.wait();
                        } catch (InterruptedException e) {
                        }
                    } while (outResult.result != START_TASK_TO_FRONT
                            && !outResult.timeout && outResult.who == null);
                    //...
            }
        ...
    }

啓動活動對象之前會先解析意圖,和activity的Info,使用AMS的startActivity傳遞的outResult參數一般都是null,故不會等待結果。

ps:自動化測試中使用的是AMS的startActivityAndWait,裏面傳遞的WaitResult對象不爲null,此時會等到界面繪製完成纔會返回結果

3.5 startActivityLocked

    final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
        //...
        //新建需要啓動活動對象activity(如test2)的ActivityRecord
        ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
                intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
                requestCode, componentSpecified, voiceSession != null, mSupervisor, container,
                options, sourceRecord);
        //...
        //看看之前是否還有未啓動的活動對象
        doPendingActivityLaunchesLocked(false);
        //...
        //這裏是真正起到進程的地方,倒數第三個參數doResume=true
        err = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor, 
                startFlags, true, options, inTask);

3.6 startActivityUnchecked

1) 此處會啓動新的ActivityStack,輸出am_create_task
2) 輸出am_create_activity,準備創建activity
3) 設置焦點、焦點切換
4) 上一個activity的onpause

    private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) {
        //...
        //初始化一些參數,如mUserLeaving==true就是在這裏設置的
        setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor);

        //...
        if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
            newTask = true;
            //這裏會進來,啓動新的ActivityStack  mTargetStack
            setTaskFromReuseOrCreateNewTask(taskToAffiliate);
            //...
        }

        if (newTask) {
            //屬於新的task,會在event log中輸出am_create_task
            EventLog.writeEvent(
                    EventLogTags.AM_CREATE_TASK, mStartActivity.userId, mStartActivity.task.taskId);
        }
        //event log中輸出am_create_activity,開始啓動應用
        ActivityStack.logStartActivity(
                EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.task);

        //...
        //stack中的startActivityLocked,此處設置mStartActivity.task(TaskRecord)的
        //topRunningActivityLocked爲test2
        mTargetStack.startActivityLocked(mStartActivity, newTask, mKeepCurTransition, mOptions);

        //mDoResume == true,會進入此處
        if (mDoResume) {
            //mLaunchTaskBehind沒有設置過就是false,也就是會進入setFocusedActivityLocked
            if (!mLaunchTaskBehind) {
                //設置焦點,此處消耗大概6ms左右
                mService.setFocusedActivityLocked(mStartActivity, "startedActivity");
            }
            //TaskRecord最頂點的activity,如test2
            final ActivityRecord topTaskActivity = mStartActivity.task.topRunningActivityLocked();
            //...
            //一般情況都是走的這裏,進行上一個activity的onpause(如launcher)
            mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,
                    mOptions);
            //...
    }

4. 焦點切換

接着章節3.6的startActivityUnchecked中會調用setFocusedActivityLocked(ActivityManagerService.java),
此處會進行焦點切換。

焦點切換流程圖
圖4.1 焦點切換流程圖

4.1 setFocusedActivityLocked(ActivityManagerService.java)

裏面包括3個部分
1) moveActivityStackToFront移動堆棧到頂端
2) setFocusedApp在WMS中設置focus的app,屬於窗體焦點變換
3) event log中輸出am_focused_activity,代表已經在AMS中設置了focus的對象

    boolean setFocusedActivityLocked(ActivityRecord r, String reason) {
        //...
        //設置AMS中foucs的活動對象
        mFocusedActivity = r;
        //...
        if (mStackSupervisor.moveActivityStackToFront(r, reason + " setFocusedActivity")) {
            //moveActivityStackToFront是true纔會進來
            mWindowManager.setFocusedApp(r.appToken, true);
        }
        //...
        //此處是eventlog的am_focused_activity打印
        EventLogTags.writeAmFocusedActivity(
                mFocusedActivity == null ? -1 : mFocusedActivity.userId,
                mFocusedActivity == null ? "NULL" : mFocusedActivity.shortComponentName,
                reason);
        //...
        return true;
    }

4.2 moveActivityStackToFront(ActivityStackSupervisor.java)

移動堆棧,此處會將test2移動到堆棧頂端:
1) mStackSupervisor.setFocusStackUnchecked,event log中輸出am_focused_stack,代表堆棧的焦點已經發生變化
2) insertTaskAtTop,將test2的stack移動到最頂端
3) moveTaskToTop插入WMS窗體相關的task的頂端

    // moveActivityStackToFront(ActivityStackSupervisor.java)
    boolean moveActivityStackToFront(ActivityRecord r, String reason) {
        …
        stack.moveToFront(reason, task);
        return true;
    }

    // moveToFront(ActivityStack.java)
    void moveToFront(String reason, TaskRecord task) {
        //...
        //此處判斷是不是主的顯示設備(注意這裏不是指桌面),一般都是true
        if (isOnHomeDisplay()) {
            //此處會設置mFocusedStack爲當前需要啓動的activity,如test2.com.myapplication, 
            //getFocusedStack時就會是test2
            mStackSupervisor.setFocusStackUnchecked(reason, this);
        }
        if (task != null) {
            //此處是修改activity的task,會將test2放在mTaskHistory中,
            //當調用topRunningActivityLocked(ActivityStack)判斷頂端activity的時候就會返回test2
            insertTaskAtTop(task, null);
        }

        if (task != null) {
            //此處是修改WMS中的task,對應於wm_task_moved: [44,1,2],
            //44代表的是移動task,1代表移動到頂端,2代表移動到task的哪個位置
            //頂端的話最後一個參數是mTasks.size()(也就是上面的2)
            //stack是棧,後進先出,所以放的位置是List最後面
            mWindowManager.moveTaskToTop(task.taskId);
        }
    }

    // setFocusStackUnchecked(ActivityStackSupervisor.java)
    void setFocusStackUnchecked(String reason, ActivityStack focusCandidate) {
        //...
        //如果需要設置的焦點focusCandidate和之前focus的不一樣,會進入此處
        if (focusCandidate != mFocusedStack) {
            //更新堆棧焦點狀態
            mLastFocusedStack = mFocusedStack;
            mFocusedStack = focusCandidate;
            //此處是eventlog的am_focused_stack打印
            EventLogTags.writeAmFocusedStack(
                    mCurrentUser, mFocusedStack == null ? -1 : mFocusedStack.getStackId(),
                    mLastFocusedStack == null ? -1 : mLastFocusedStack.getStackId(), reason);
        }
        //...
    }

4.3 setFocusedApp(WindowManagerService.java)

setFocusedApp用戶更新當前WMS focus的窗體

    //moveFocusNow上面章節4.1傳遞進來的就是true
    public void setFocusedApp(IBinder token, boolean moveFocusNow) {
        //...
        //newFocus是test2,mFocusedApp是launcher,故changed==true
        final boolean changed = mFocusedApp != newFocus;
        //...
        //moveFocusNow==true, changed==true,故這裏是會進來的
        if (moveFocusNow && changed) {
            //更新窗體顯示
            updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
        //...
    }

updateFocusedWindowLocked會先遍歷查找最新的焦點,如果窗體焦點有變化,則進行更新。
至於遍歷查找窗體焦點是在computeFocusedWindowLocked完成的
此處會輸出Changing focus from Window*** to null

    boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
        //會先調用查找最新的焦點窗口,如果返回null代表當前沒有窗口聚焦
        WindowState newFocus = computeFocusedWindowLocked();
        //當焦點有變化的時候會進入此處
        if (mCurrentFocus != newFocus) {
            //...
            //如果需要焦點變化的日誌輸出,可以打開下面的靜態變量
            if (DEBUG_FOCUS_LIGHT || localLOGV) Slog.v(TAG_WM, "Changing focus from " +
                    mCurrentFocus + " to " + newFocus + " Callers=" + Debug.getCallers(4));
            //更新焦點
            final WindowState oldFocus = mCurrentFocus;
            mCurrentFocus = newFocus;
            //...

            if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
                //更新input foucus的焦點,這個是在界面focus的時候做的
                mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows);
            }

            //...
        }
        return false;
    }

computeFocusedWindowLocked這個函數是會遍歷當前顯示的設備(可能有多個顯示設備),返回當前系統焦點所在的窗體

    private WindowState computeFocusedWindowLocked() {
        final int displayCount = mDisplayContents.size();
        //遍歷當前顯示的設備
        for (int i = 0; i < displayCount; i++) {
            final DisplayContent displayContent = mDisplayContents.valueAt(i);
            //查找每一個顯示設備是否有窗體佔用着焦點
            WindowState win = findFocusedWindowLocked(displayContent);
            if (win != null) {
                return win;
            }
        }
        return null;
    }

findFocusedWindowLocked查找當前需要focus的窗體,如果之前有focus,而且需要切換到別的focus的activity窗體的時候,會把上一個launcher的窗體的焦點先切換成null

    WindowState findFocusedWindowLocked(DisplayContent displayContent) {
        final WindowList windows = displayContent.getWindowList();
        for (int i = windows.size() - 1; i >= 0; i--) {
            final WindowState win = windows.get(i);

            //如果窗體可以接受按鍵響應
            if (!win.canReceiveKeys()) {
                continue;
            }
            //...
            //當前接收按鍵相應的窗體仍是launcher(test2進程都還未啓動)
            AppWindowToken wtoken = win.mAppToken;
            //...
            //當便遍歷到token與mFocusedApp相等(test2)的時候,而且該window是允許聚焦
            if (mFocusedApp == token && token.windowsAreFocusable()) {
                //那我們就會把之前的焦點更換,就是把launcher焦點切換成null
                return null;
            }
            //...
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章