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种:
- 使用Instrumentation(代理控制框架)
- 使用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
- 此处会启动新的ActivityStack,输出am_create_task
- 输出am_create_activity,准备创建activity
- 设置焦点、焦点切换
- 上一个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个部分
- moveActivityStackToFront移动堆栈到顶端
- setFocusedApp在WMS中设置focus的app,属于窗体焦点变换
- 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移动到堆栈顶端:
- mStackSupervisor.setFocusStackUnchecked,event log中输出am_focused_stack,代表堆栈的焦点已经发生变化
- insertTaskAtTop,将test2的stack移动到最顶端
- 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;
}
//...
}