Activity 启动流程学习总结(附源码)

栏目: IOS · Android · 发布时间: 5年前

内容简介:关于 Activity 启动,Android 中场景大致有两个:本文主要介绍第一种场景由于 Activity 的启动流程中涉及了大量的进程间通信,例如:ActivityManagerService 和 ActivityStack 位于同一进程,ApplicationThread 和 ActivityThread 位于同一进程。所以有必要梳理一下进程与线程的区别

关于 Activity 启动,Android 中场景大致有两个:

  1. 从 launcher 中启动应用,触发该应用默认 Activity 的启动。这种 Activity 都是在新进程和新的任务栈中启动的,所以涉及到新进程和新任务栈的初始化
  2. 应用程序内部启动非默认 Activity, 被启动的 Activity 一般在原来的进程和任务栈中启动

本文主要介绍第一种场景

背景知识

进程与线程

由于 Activity 的启动流程中涉及了大量的进程间通信,例如:ActivityManagerService 和 ActivityStack 位于同一进程,ApplicationThread 和 ActivityThread 位于同一进程。所以有必要梳理一下进程与线程的区别

从操作系统的角度看,进程和线程有什么区别?

  1. 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程造成影响。
  2. 线程没有独立的地址空间,线程只是进程所属进程的不同执行路径

从 JVM 的运行时数据分区的角度,进程和线程有什么关系?

JVM 运行时数据分区如图所示:

Activity 启动流程学习总结(附源码)

由图可以看出如下几点:

  1. 一个进程中的多个线程共享堆区和方法区
  2. 每个线程拥有自己的程序计数器,虚拟机栈,本地方法栈

Activity 启动模式

启动模式横向对比

四中启动模式的概念就不详述了,这里只是对关键点做出横向对比,如图所示

Activity 启动流程学习总结(附源码)

一些鲜为人知的 Intent Flag

下面是一些 Intent Flag 及介绍,如图所示

Activity 启动流程学习总结(附源码)

Activity 启动流程源码分析

概念

首先介绍一下 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 {
			......
		}
 
 
	......
复制代码

这里有两个有趣的场景:

  1. startActivityForResult() 不能用于启动 singleTask 为启动模式的 Activity, 否则会从 onActivityForResult() 中收到 cancel 事件
  2. 假设有这样一个场景,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 需要被挂起。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

维多利亚时代的互联网

维多利亚时代的互联网

[英] 汤姆·斯丹迪奇 / 多绥婷 / 后浪丨江西人民出版社 / 2017-8 / 38.00元

人类历史上的第一次大连接 回顾互联网的前世 预言互联网的未来 ……………… ※编辑推荐※ ☆《财富》杂志推荐的75本商务人士必读书之一 ☆ 回顾互联网的前世,颠覆你的思维,升级你对互联网的认知 ☆ 人类历史上一次全球大连接是维多利亚时期的电报时代,那时候也有疯狂的资本、 巨大的泡沫、网络新型犯罪、网络亚文化崛起……现在的互联网时代就是电报时代的重演;回顾那......一起来看看 《维多利亚时代的互联网》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换