前言
关于 Activity 启动,Android 中场景大致有两个:
- 从 launcher 中启动应用,触发该应用默认 Activity 的启动。这种 Activity 都是在新进程和新的任务栈中启动的,所以涉及到新进程和新任务栈的初始化
- 应用程序内部启动非默认 Activity, 被启动的 Activity 一般在原来的进程和任务栈中启动
本文主要介绍第一种场景
背景知识
进程与线程
由于 Activity 的启动流程中涉及了大量的进程间通信,例如:ActivityManagerService 和 ActivityStack 位于同一进程,ApplicationThread 和 ActivityThread 位于同一进程。所以有必要梳理一下进程与线程的区别
从操作系统的角度看,进程和线程有什么区别?
- 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程造成影响。
- 线程没有独立的地址空间,线程只是进程所属进程的不同执行路径
从 JVM 的运行时数据分区的角度,进程和线程有什么关系?
JVM 运行时数据分区如图所示:
image由图可以看出如下几点:
- 一个进程中的多个线程共享堆区和方法区
- 每个线程拥有自己的程序计数器,虚拟机栈,本地方法栈
Activity 启动模式
启动模式横向对比
四中启动模式的概念就不详述了,这里只是对关键点做出横向对比,如图所示
Activity 启动模式比较一些鲜为人知的 Intent Flag
下面是一些 Intent Flag 及介绍,如图所示
鲜为人知但常用的启动模式 flagActivity 启动流程源码分析
概念
首先介绍一下 Activity 启动流程涉及到的核心类
ActivityThread
ActivityThread 的作用是管理应用程序主线程的相关流程,例如管理与处理 activity manager 发送的请求,这些请求可以来自 Activity 或 BroadCast
Instrumentation
它用来监控应用程序与系统之间的交互
ActivityManagerService (AMS)
AMS(ActivityManagerService)是贯穿Android系统组件的核心服务,负责了系统中四大组件的启动、切换、调度以及应用进程管理和调度工作
执行流程
Step 1. Launcher.startActivitySafely
/**
* Launches the intent referred by the clicked shortcut.
*
* @param v The view representing the clicked shortcut.
*/
public void onClick(View v) {
// Make sure that rogue clicks don't get through while allapps is launching, or after the
// view has detached (it's possible for this to happen if the view is removed mid touch).
if (v.getWindowToken() == null) {
return;
}
if (!mWorkspace.isFinishedSwitchingState()) { // Launcher 程序在监听点击事件时会判断页面是否正在滑动,如果在滑动则不响应点击应用程序 icon 的事件
return;
}
Object tag = v.getTag();
if (tag instanceof ShortcutInfo) { // 处理点击应用程序 icon 的场景
// Open shortcut
final Intent intent = ((ShortcutInfo) tag).intent;
int[] pos = new int[2];
v.getLocationOnScreen(pos);
intent.setSourceBounds(new Rect(pos[0], pos[1],
pos[0] + v.getWidth(), pos[1] + v.getHeight()));
boolean success = startActivitySafely(v, intent, tag); // startActivitySafely 这里主要会判断待启动的 Activity 是否存在,若不存在则会报 ActivityNotFound Exception 并捕获
if (success && v instanceof BubbleTextView) {
mWaitingForResume = (BubbleTextView) v;
mWaitingForResume.setStayPressed(true);
}
} else if (tag instanceof FolderInfo) { // 处理点击文件夹的场景
···
} else if (v == mAllAppsButton) {
···
}
}
Launcher 程序在监听点击事件时会判断页面是否正在滑动,如果在滑动则不响应点击应用程序 icon 的事件
Step 2. Activity.startActivity()
由于 Launcher 继承于 Activity, 因此代码执行流程到了 startActivity()
。
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks {
......
@Override
public void startActivity(Intent intent) {
startActivityForResult(intent, -1);
}
......
startActivity 最终是调用 startActivityForResult
, 传入的参数为 -1 代表这次启动 Activity 不需要获取结果
Step 3. Activity.startActivityForResult()
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks {
......
public void startActivityForResult(Intent intent, int requestCode) {
if (mParent == null) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode);
......
} else {
......
}
......
这里有两个有趣的场景:
-
startActivityForResult()
不能用于启动singleTask
为启动模式的 Activity, 否则会从onActivityForResult()
中收到 cancel 事件 - 假设有这样一个场景,Activity A 启动 Activity B, 如果在 A 的 onCreate() / onResume() 中调用
startActivityForResult()
, 并且传入的 requestCode >= 1, 那么 A Activity 所挂钩的 Window 将暂时不可见,这种不可见状态直到收到 B Activity 的回调结果。目的是防止 Activity 跳转时 UI 闪烁
Step 4. ActivityStack.startActivityUncheckedLocked
final int startActivityUncheckedLocked(ActivityRecord r,
ActivityRecord sourceRecord, Uri[] grantedUriPermissions,
int grantedMode, boolean onlyIfNeeded, boolean doResume) {
final Intent intent = r.intent;
final int callingUid = r.launchedFromUid;
int launchFlags = intent.getFlags();
// We'll invoke onUserLeaving before onPause only if the launching
// activity did not explicitly state that this is an automated launch.
mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0;
......
ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
!= 0 ? r : null;
// If the onlyIfNeeded flag is set, then we can do this if the activity
// being launched is the same as the one making the call... or, as
// a special case, if we do not know the caller then we count the
// current top activity as the caller.
if (onlyIfNeeded) {
......
}
if (sourceRecord == null) {
......
} else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
......
} else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
......
}
if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
......
}
boolean addingToTask = false;
if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
(launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
// If bring to front is requested, and no result is requested, and
// we can find a task that was started with this same
// component, then instead of launching bring that one to the front.
if (r.resultTo == null) {
// See if there is a task to bring to the front. If this is
// a SINGLE_INSTANCE activity, there can be one and only one
// instance of it in the history, and it is always in its own
// unique task, so we do a special search.
ActivityRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE
? findTaskLocked(intent, r.info)
: findActivityLocked(intent, r.info);
if (taskTop != null) {
......
}
}
}
......
if (r.packageName != null) {
// If the activity being launched is the same as the one currently
// at the top, then we need to check if it should only be launched
// once.
ActivityRecord top = topRunningNonDelayedActivityLocked(notTop);
if (top != null && r.resultTo == null) {
if (top.realActivity.equals(r.realActivity)) {
......
}
}
} else {
......
}
boolean newTask = false;
// Should this be considered a new task?
if (r.resultTo == null && !addingToTask
&& (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
// todo: should do better management of integers.
mService.mCurTask++;
if (mService.mCurTask <= 0) {
mService.mCurTask = 1;
}
r.task = new TaskRecord(mService.mCurTask, r.info, intent,
(r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0);
......
newTask = true;
if (mMainStack) {
mService.addRecentTaskLocked(r.task);
}
} else if (sourceRecord != null) {
......
} else {
......
}
......
startActivityLocked(r, newTask, doResume);
return START_SUCCESS;
}
由于我们是总 launcher 启动 Activity, 因此 当前的前台任务栈栈顶是 launcher Activity, 因此需要创建一个新的 ActivityStack, 和 ActivityRecord 对象,将 ActivityRecord 放入其中,最终这个新创建的 ActivityStack 被保存到 AMS 中
Step 5. Activity.resumeTopActivityLocked
// If the top activity is the resumed one, nothing to do.
if (mResumedActivity == next && next.state == ActivityState.RESUMED) {
......
}
// If we are sleeping, and there is no resumed activity, and the top
// activity is paused, well that is the state we want.
if ((mService.mSleeping || mService.mShuttingDown)
&& mLastPausedActivity == next && next.state == ActivityState.PAUSED) {
......
}
现在我们已经准备好了新的 ActivityStack 和新的 ActivityRecord, 然后我们就需要处理 launcher Activity 的生命周期,将其置为 pause 状态。根据源码,这里首先会检查被启动的 Activity 是否在栈顶,是否就是启动者,如果是的话,就什么都不做,如果都不满足的话,就需要将目前栈顶的 Activity 置为 Pause 状态,在这之前,首先要检查是否有正在 Pausing 的 Activity, 如果有的话launcher Activity 需要被挂起,
总结
由于 Activity 启动流程较复杂,剩余流程将不再详述,总结如下图所示:
image
网友评论