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啓動應用。
圖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;
}
//...
}