Android Framework AMS(04)startActivity分析-1(am启动到ActivityThread启动)

该系列文章总纲链接:专题总纲目录 Android Framework 总纲


本章关键点总结 & 说明:

说明:本章节主要解读AMS通过startActivity启动Activity的整个流程的第一阶段:从am启动到ActivityThread启动。

第二阶段文章链接为:

Android Framework AMS(05)startActivity分析-2(ActivityThread启动到Activity拉起)

AMS的startActivity方法目的是确保Activity 的正确启动和正确显示。分析Activity的启动流程方法有很多,我们选择从 adb shell am start 命令开始分析 Activity 的启动流程,那么为什么要选择从这个视角来分析呢?

这是因为这个命令提供了一个标准化和可控的方式来启动 Activity,它模拟了从系统层面发起的启动请求,这种方式可以绕过应用内部的逻辑,直接请求 AMS 进行处理。可以让开发者从系统层面更好地理解 Android 系统的工作原理,以及 AMS 在其中扮演的角色。这种方式有助于揭示 Activity 启动过程中的系统行为,包括进程创建、任务栈管理、Activity 生命周期管理等。

接下来开始我们的分析。

1 从am命令到AMS的startActivty调用

这里我们以启动setting界面为例。在命令行中,我们开始执行:

bash 复制代码
$adb shell am start -a android.intent.action.MAIN -n com.android.settings/.Settings

这里的命令解释如下:

  • adb shell: 启动一个 Android shell 命令行。
  • am: 命令行工具,用于与 Activity Manager 服务交互。
  • start: 指示 am 工具启动一个新的 Activity。
  • -a android.intent.action.MAIN: 指定启动的 Activity 应该处理 MAIN 动作,这是启动 Activity 的常用动作。
  • -n com.android.settings/.Settings: 指定包名和 Activity 名称,这里 com.android.settings 是设置应用的包名,Settings 是设置应用的主 Activity。

这个命令会打开设备的设置主界面(注意:不同的设备可能有细微的差别,特别是在定制过的 Android 系统上,Activity 名称或者包名可能有所不同,如果上述命令不起作用,你可能需要查看设备的特定设置应用的包名和 Activity 名称)。

接下来我们看看am命令是如何编译和运行的?

1.1 am命令的编译和运行

am命令对应的Am.java代码,我们先来看Am.java是如何被编译成jar文件,以及如何被使用的。相关文件均在AOSP的frameworks/base/cmds/am目录下。am命令对应的Android.mk文件内容如下所示:

bash 复制代码
# Copyright 2008 The Android Open Source Project
#
LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE := am
include $(BUILD_JAVA_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := am
LOCAL_SRC_FILES := am
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_TAGS := optional
include $(BUILD_PREBUILT)

这里编译Am.java后生成一个am.jar的文件。这里我们再看am命令,编辑器打开后,如下所示:

bash 复制代码
#!/system/bin/sh
#
# Script to start "am" on the device, which has a very rudimentary
# shell.
#
base=/system

#CLASSPATH 用于指定 Java 程序运行时查找类定义的路径。这里,它被设置为 
#/system/framework/am.jar,意味着 am.jar 文件包含了执行 am 命令所需的类。
export CLASSPATH=$base/framework/am.jar

exec app_process $base/bin com.android.commands.am.Am "$@"

这里详细解读下最后一句脚本的含义:

  • exec:这个 shell 内置命令用于执行指定的命令,并且替换当前 shell 进程。
  • app_process:这是一个用于启动 Android 应用程序和系统服务的底层 C++ 程序。
  • $base/bin:这是 app_process 的路径,指向 /system/bin 目录。
  • com.android.commands.am.Am:这是启动的 Java 主类,定义在 am.jar 中。
  • "$@":这是传递给脚本的所有参数,它们将被传递给 com.android.commands.am.Am

总的来说,这个脚本设置了必要的环境变量,并使用 app_process 来启动 am 命令行工具。am 命令行工具实际上是通过 Java 编写的,并且被打包在 am.jar 文件中。这个脚本使得用户可以通过 shell 界面以命令行的方式与 Activity Manager 服务进行交互。

接下来我们来分析 Am.java文件的内容。

1.2 Am.java解读

Am.java 是 Android 系统源代码中的一个文件,当调用 am start时,进入到Am.java的main函数中,代码内容如下:

java 复制代码
//Am 
   public static void main(String[] args) {
        (new Am()).run(args);
    }

Am类是继承BaseCommand,因此这里的run实际上是调用父类的run方法,BaseCommand的run方法实现如下:

java 复制代码
//BaseCommand
    public void run(String[] args) {
        if (args.length < 1) {
            onShowUsage(System.out);
            return;
        }

        mArgs = args;
        mNextArg = 0;
        mCurArgData = null;

        try {
            //这里调用的是子类Am的onRun方法
            onRun();
        } catch (IllegalArgumentException e) {
            onShowUsage(System.err);
            System.err.println();
            System.err.println("Error: " + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace(System.err);
            System.exit(1);
        }
    }

接着,再回到Am中的OnRun方法实现,代码如下所示:

java 复制代码
//Am 
   @Override
    public void onRun() throws Exception {
        mAm = ActivityManagerNative.getDefault();
        //...

        String op = nextArgRequired();

        if (op.equals("start")) {
            runStart();
        } else if (op.equals("startservice")) {
            runStartService();
        } else if (op.equals("stopservice")) {
            runStopService();
        } else if (op.equals("force-stop")) {
            runForceStop();
		//...
        } else {
            showError("Error: unknown command '" + op + "'");
        }
    }

这里开始,真正的处理命令参数了,关于am start,我们这里只需要关注runStart方法即可,对应的代码实现如下所示:

java 复制代码
//Am
    private void runStart() throws Exception {
		//创建一个 Intent 对象,用于指定要启动的 Activity
        Intent intent = makeIntent(UserHandle.USER_CURRENT);
		//...
		
        String mimeType = intent.getType();
        if (mimeType == null && intent.getData() != null
                && "content".equals(intent.getData().getScheme())) {
            mimeType = mAm.getProviderMimeType(intent.getData(), mUserId);
        }

        do {
            //...
            System.out.println("Starting: " + intent);
			//Activity 将在新的任务栈中启动
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

			//性能分析相关
            ParcelFileDescriptor fd = null;
            ProfilerInfo profilerInfo = null;
            if (mProfileFile != null) {
                try {
                    fd = ParcelFileDescriptor.open(
                            new File(mProfileFile),
                            ParcelFileDescriptor.MODE_CREATE |
                            ParcelFileDescriptor.MODE_TRUNCATE |
                            ParcelFileDescriptor.MODE_READ_WRITE);
                } catch (FileNotFoundException e) {
                    System.err.println("Error: Unable to open file: " + mProfileFile);
                    return;
                }
                profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop);
            }

            IActivityManager.WaitResult result = null;
            int res;
            final long startTime = SystemClock.uptimeMillis();
			//启动activity,关键代码,同时记录启动前后的时间,用于性能分析
            if (mWaitOption) {
                result = mAm.startActivityAndWait(null, null, intent, mimeType,
                            null, null, 0, mStartFlags, profilerInfo, null, mUserId);
                res = result.result;
            } else {
                res = mAm.startActivityAsUser(null, null, intent, mimeType,
                        null, null, 0, mStartFlags, profilerInfo, null, mUserId);
            }
            final long endTime = SystemClock.uptimeMillis();
            PrintStream out = mWaitOption ? System.out : System.err;
            boolean launched = false;
			//activity启动后的结果处理
            switch (res) {
                case ActivityManager.START_SUCCESS:
                    launched = true;
                    break;
				//...
                default:
                    out.println(
                            "Error: Activity not started, unknown error code " + res);
                    break;
            }
            //...
			//重复启动Activity处理
            mRepeat--;
            if (mRepeat > 1) {
                mAm.unhandledBack();
            }
        } while (mRepeat > 1);
    }

这个方法展示了 adb shell am start 命令背后的逻辑,包括 Intent 的创建、性能分析、Activity 的启动以及结果处理。

1.3 Am.java 额外的逻辑解读

1.3.1 mWaitOption解读

mWaitOption 表示是否应该等待目标 Activity 启动完成后再继续执行。当 mWaitOption 设置为 true 时,am start 命令会等待 ActivityManager 启动指定的 Activity 并返回结果。如果 Activity 成功启动,命令会输出启动结果;如果启动失败,命令会输出错误信息和失败原因。在代码中,mWaitOption 可能通过命令行参数设置,例如,如果用户输入了

bash 复制代码
$adb shell am start -W

命令,其中的 -W 参数就会使得 mWaitOption 设置为 true。

1.3.2 mRepeat解读

在 Am.java 的 runStart() 方法中,处理重复启动的代码片段提取如下:

java 复制代码
do {
    //...
    mRepeat--;
    if (mRepeat > 1) {
        mAm.unhandledBack();
    }
} while (mRepeat > 1);

mRepeat 的初始值是由用户通过命令行参数(如 --repeat 或 -r)指定的,表示 Activity 需要被启动的次数。在循环体内部,每次启动 Activity 后,mRepeat 的值会递减(mRepeat --)。如果 mRepeat 大于 1,意味着还需要再次启动 Activity。

在每次成功启动 Activity 并且设置了重复启动之后,if 语句检查 mRepeat 的值。如果还需要再次启动(mRepeat 大于 1),它会调用 mAm.unhandledBack() 方法。这个方法的作用是发送一个"返回"事件(相当于用户按下设备上的返回键)到系统,使得当前 Activity 退到后台,并且将其所在的任务栈上一个 Activity 带到前台。

假设用户执行了如下命令:

bash 复制代码
#这里通过-r参数设置mRepeat的值
$adb shell am start -r 3 com.example/myActivity

这个命令会启动 com.example/myActivity Activity 三次。以下是预期的行为:

  1. 第一次启动 Activity。
  2. 发送返回操作,使得 Activity 退到后台。
  3. 第二次启动 Activity。
  4. 再次发送返回操作。
  5. 第三次启动 Activity。

在第三次启动之后,mRepeat的值会递减到 1,循环结束。

总的来说,mRepeat相关的这段代码实现了通过命令行控制 Activity 重复启动和模拟用户返回操作的功能,有助于进行更复杂的测试和性能评估。

接下来开始进入到AMS中的调用。

2 AMS的startActivityXXX方法分析

2.1 进入到AMS的startActivityMayWait方法

从1.2的分析中,关键代码如下:

java 复制代码
//Am
    //runStart
            if (mWaitOption) {
                result = mAm.startActivityAndWait(null, null, intent, mimeType,
                            null, null, 0, mStartFlags, profilerInfo, null, mUserId);
                res = result.result;
            } else {
                res = mAm.startActivityAsUser(null, null, intent, mimeType,
                        null, null, 0, mStartFlags, profilerInfo, null, mUserId);
            }

这里开始进入到AMS的代理接口调用,最终是调用到了AMS的接口startActivityAndWait 和 startActivityAsUser方法。解读如下:

java 复制代码
//ActivityManagerService
	//...
   @Override
	public final WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage,
			Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
			int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
		// 确保调用者不是隔离进程
		enforceNotIsolatedCaller("startActivityAndWait");

		// 处理用户ID,确保调用者有权启动目标用户ID的Activity
		userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
				false, ALLOW_FULL_ONLY, "startActivityAndWait", null);

		// 创建WaitResult对象,用于存储启动结果
		WaitResult res = new WaitResult();

		// 请求启动Activity并等待结果
		// 调用ActivityStackSupervisor的相关方法来实际启动Activity
		mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
				null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, res, null,
				options, userId, null, null);

		// 返回启动结果
		return res;
	}
	//...
    @Override
	public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
			Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
			int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
		// 确保调用者不是隔离进程
		enforceNotIsolatedCaller("startActivity");

		// 处理用户ID,确保调用者有权启动目标用户ID的Activity
		userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
				false, ALLOW_FULL_ONLY, "startActivity", null);

		// 请求启动Activity但不等待结果,这里传递的res实际上是null
		// 调用ActivityStackSupervisor的相关方法来实际启动Activity
		return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
				resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
				profilerInfo, null, null, options, userId, null, null);
	}

两个方法都调用了 ActivityStackSupervisor 的 startActivityMayWait 方法来实际启动 Activity。其中:

  • startActivityAndWait 方法返回一个 WaitResult 对象,包含启动结果和性能数据。
  • startActivityAsUser 方法立即返回一个整数结果,表示启动操作的结果。

最后总结下,这两个方法是 AMS 中处理 Activity 启动请求的核心方法,分别用于同步和异步启动 Activity。最后也都调用了ActivityStackSupervisor的方法startActivityMayWait。该方法较长,这里分成3个阶段进行解读。3个阶段代码的定位分别是:

  1. 为启动Activity做相关函数的参数初始化。
  2. 启动Activity,也就是最关键操作。
  3. 启动Activity后的返回值处理。

接下来分别进行解读。

2.1.1 startActivityMayWait第一阶段代码分析

ActivityStackSupervisor的方法startActivityMayWait第一阶段代码解读如下:

java 复制代码
//ActivityStackSupervisor
	final int startActivityMayWait(IApplicationThread caller, int callingUid,
			String callingPackage, Intent intent, String resolvedType,
			IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
			IBinder resultTo, String resultWho, int requestCode, int startFlags,
			ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,
			Bundle options, int userId, IActivityContainer iContainer, TaskRecord inTask) {
		// 禁止在 Intent 中传递文件描述符
		if (intent != null && intent.hasFileDescriptors()) {
			throw new IllegalArgumentException("File descriptors passed in Intent");
		}
		
		// 检查 Intent 是否指定了组件
		boolean componentSpecified = intent.getComponent() != null;

		// 创建 Intent 的副本,防止修改客户端的对象
		intent = new Intent(intent);

		// 解析 Intent,获取目标 Activity 的信息
		ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags,
				profilerInfo, userId);

		// 获取容器对象
		ActivityContainer container = (ActivityContainer)iContainer;
		synchronized (mService) {
			//取出调用进程的PID和UID
			final int realCallingPid = Binder.getCallingPid();
			final int realCallingUid = Binder.getCallingUid();
			int callingPid;
			if (callingUid >= 0) {
				callingPid = -1;
			} else if (caller == null) {
				callingPid = realCallingPid;
				callingUid = realCallingUid;
			} else {
				callingPid = callingUid = -1;
			}

			// 获取当前获得焦点的 Activity 栈
			final ActivityStack stack;
			if (container == null || container.mStack.isOnHomeDisplay()) {
				stack = getFocusedStack();
			} else {
				stack = container.mStack;
			}
			stack.mConfigWillChange = config != null
					&& mService.mConfiguration.diff(config) != 0;

			// 清除调用者身份
			final long origId = Binder.clearCallingIdentity();
			
			// 第一阶段,为启动Activity做相关函数的参数初始化
			if (aInfo != null &&
					(aInfo.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
				// 这可能是一个重量级进程!检查是否已经有另一个不同的重量级进程在运行
				if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) {
					if (mService.mHeavyWeightProcess != null &&
							(mService.mHeavyWeightProcess.info.uid != aInfo.applicationInfo.uid ||
							!mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) {
						int appCallingUid = callingUid;
						if (caller != null) {
							ProcessRecord callerApp = mService.getRecordForAppLocked(caller);
							if (callerApp != null) {
								appCallingUid = callerApp.info.uid;
							} else {
								ActivityOptions.abort(options);
								return ActivityManager.START_PERMISSION_DENIED;
							}
						}

						// 创建一个新的 IntentSender 来启动 HeavyWeightSwitcherActivity
						IIntentSender target = mService.getIntentSenderLocked(
								ActivityManager.INTENT_SENDER_ACTIVITY, "android",
								appCallingUid, userId, null, null, 0, new Intent[] { intent },
								new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT
								| PendingIntent.FLAG_ONE_SHOT, null);

						Intent newIntent = new Intent();
						if (requestCode >= 0) {
							// 如果请求了结果,则设置标志
							newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true);
						}
						newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT,
								new IntentSender(target));
						if (mService.mHeavyWeightProcess.activities.size() > 0) {
							ActivityRecord hist = mService.mHeavyWeightProcess.activities.get(0);
							newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP,
									hist.packageName);
							newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK,
									hist.task.taskId);
						}
						newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP,
								aInfo.packageName);
						newIntent.setFlags(intent.getFlags());
						newIntent.setClassName("android",
								HeavyWeightSwitcherActivity.class.getName());
						intent = newIntent;
						resolvedType = null;
						caller = null;
						callingUid = Binder.getCallingUid();
						callingPid = Binder.getCallingPid();
						componentSpecified = true;
						try {
							// 重新解析新的 Intent
							ResolveInfo rInfo =
								AppGlobals.getPackageManager().resolveIntent(
										intent, null,
										PackageManager.MATCH_DEFAULT_ONLY
										| ActivityManagerService.STOCK_PM_FLAGS, userId);
							aInfo = rInfo != null ? rInfo.activityInfo : null;
							aInfo = mService.getActivityInfoForUser(aInfo, userId);
						} catch (RemoteException e) {
							aInfo = null;
						}
					}
				}
			}
		}
        //...

第一阶段本质上就是为启动Activity做相关函数的参数初始化。

2.1.2 startActivityMayWait第二阶段代码分析

ActivityStackSupervisor的方法startActivityMayWait第二阶段代码解读如下:

java 复制代码
//ActivityStackSupervisor
	final int startActivityMayWait(IApplicationThread caller, int callingUid,
			String callingPackage, Intent intent, String resolvedType,
			IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
			IBinder resultTo, String resultWho, int requestCode, int startFlags,
			ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,
			Bundle options, int userId, IActivityContainer iContainer, TaskRecord inTask) {
        //...
		// 第二阶段,启动Activity,关键操作
		int res = startActivityLocked(caller, intent, resolvedType, aInfo,
				voiceSession, voiceInteractor, resultTo, resultWho,
				requestCode, callingPid, callingUid, callingPackage,
				realCallingPid, realCallingUid, startFlags, options,
				componentSpecified, null, container, inTask);

		// 恢复之前的调用者身份
		Binder.restoreCallingIdentity(origId);

		if (stack.mConfigWillChange) {
			// 如果调用者也需要切换到新的配置,那么现在进行切换。
			// 这样可以确保一个干净的切换,因为我们正在等待当前 Activity 暂停
			// (所以我们不会销毁它),并且还没有启动下一个 Activity。
			mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
					"updateConfiguration()");
			stack.mConfigWillChange = false;
			mService.updateConfigurationLocked(config, null, false, false);
		}
        //...

第二阶段本质上就是调用startActivityLocked启动Activity,也就是最关键操作。此部分也是代码较长,2.2中会专门解读startActivityLocked方法。

2.1.3 startActivityMayWait第三阶段代码分析

ActivityStackSupervisor的方法startActivityMayWait第三阶段代码解读如下:

java 复制代码
//ActivityStackSupervisor
	final int startActivityMayWait(IApplicationThread caller, int callingUid,
			String callingPackage, Intent intent, String resolvedType,
			IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
			IBinder resultTo, String resultWho, int requestCode, int startFlags,
			ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,
			Bundle options, int userId, IActivityContainer iContainer, TaskRecord inTask) {
        //...
        // 第三阶段,启动Activity后的返回值处理
		if (outResult != null) {
			outResult.result = res;
			// 如果启动成功,将结果对象添加到等待列表中,并等待直到超时或获取到启动的组件
			if (res == ActivityManager.START_SUCCESS) {
				mWaitingActivityLaunched.add(outResult);
				do {
					try {
						// 等待Activity启动
						mService.wait();
					} catch (InterruptedException e) {
					}
				} while (!outResult.timeout && outResult.who == null);
			} 
			// 如果启动结果为将任务切换到前台
			else if (res == ActivityManager.START_TASK_TO_FRONT) {
				ActivityRecord r = stack.topRunningActivityLocked(null);
				// 如果顶层Activity是可见并且已经恢复状态
				if (r.nowVisible && r.state == ActivityState.RESUMED) {
					outResult.timeout = false;
					outResult.who = new ComponentName(r.info.packageName, r.info.name);
					outResult.totalTime = 0;
					outResult.thisTime = 0;
				} else {
					// 否则,将结果对象添加到等待列表中,并等待直到超时或获取到可见的组件
					outResult.thisTime = SystemClock.uptimeMillis();
					mWaitingActivityVisible.add(outResult);
					do {
						try {
							// 等待Activity变为可见
							mService.wait();
						} catch (InterruptedException e) {
						}
					} while (!outResult.timeout && outResult.who == null);
				}
			}
		}
		return res;
	}

第三阶段本质上就是启动Activity后的返回值处理逻辑。需要注意的是,只有am命令参数中带有-W,即设置了相关选项,才会进入到wait状态,否则outResult 直接为null,不处理相关逻辑。

2.2 启动Activity的关键方法startActivityLocked代码分析

ActivityStackSupervisor中startActivityLocked方法的实现如下:

java 复制代码
//ActivityStackSupervisor
	final int startActivityLocked(IApplicationThread caller, Intent intent, String resolvedType,
			ActivityInfo aInfo, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
			IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
			String callingPackage, int realCallingPid, int realCallingUid, int startFlags, Bundle options,
			boolean componentSpecified, ActivityRecord[] outActivity, ActivityContainer container,
			TaskRecord inTask) {
		int err = ActivityManager.START_SUCCESS;

		// 获取调用者的进程记录
		ProcessRecord callerApp = null;
		if (caller != null) {
			callerApp = mService.getRecordForAppLocked(caller);
			if (callerApp != null) {
				callingPid = callerApp.pid;
				callingUid = callerApp.info.uid;
			} else {
				err = ActivityManager.START_PERMISSION_DENIED;
			}
		}

		if (err == ActivityManager.START_SUCCESS) {
			final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
		}

		// 获取启动结果的 ActivityRecord
		ActivityRecord sourceRecord = null;
		if (resultTo != null) {
			sourceRecord = isInAnyStackLocked(resultTo);
			if (sourceRecord != null && requestCode >= 0 && !sourceRecord.finishing) {
				resultRecord = sourceRecord;
			}
		}

		// 获取结果 ActivityStack
		ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;

		// 处理FLAG_ACTIVITY_FORWARD_RESULT标志
		final int launchFlags = intent.getFlags();
		if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) {
			if (requestCode >= 0) {
				ActivityOptions.abort(options);
				return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
			}
			resultRecord = sourceRecord.resultTo;
			resultWho = sourceRecord.resultWho;
			requestCode = sourceRecord.requestCode;
			sourceRecord.resultTo = null;
			if (resultRecord != null) {
				resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode);
			}
			if (sourceRecord.launchedFromUid == callingUid) {
				callingPackage = sourceRecord.launchedFromPackage;
			}
		}

		// 检查Intent是否解析成功
		if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
			err = ActivityManager.START_INTENT_NOT_RESOLVED;
		}

		// 检查ActivityInfo是否找到
		if (err == ActivityManager.START_SUCCESS && aInfo == null) {
			err = ActivityManager.START_CLASS_NOT_FOUND;
		}

		// 检查是否与语音交互兼容
		if (err == ActivityManager.START_SUCCESS && sourceRecord != null
				&& sourceRecord.task.voiceSession != null) {
			if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
					&& sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
				try {
					if (!AppGlobals.getPackageManager().activitySupportsIntent(
							intent.getComponent(), intent, resolvedType)) {
						err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
					}
				} catch (RemoteException e) {
					err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
				}
			}
		}

		// 检查是否有启动Activity的权限
		final int startAnyPerm = mService.checkPermission(
				START_ANY_ACTIVITY, callingPid, callingUid);
		final int componentPerm = mService.checkComponentPermission(aInfo.permission, callingPid,
				callingUid, aInfo.applicationInfo.uid, aInfo.exported);
		if (startAnyPerm != PERMISSION_GRANTED && componentPerm != PERMISSION_GRANTED) {
			if (resultRecord != null) {
				resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
					Activity.RESULT_CANCELED, null);
			}
			ActivityOptions.abort(options);
			return ActivityManager.START_PERMISSION_DENIED;
		}

		// 检查IntentFirewall是否允许启动Activity
		boolean abort = !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
				callingPid, resolvedType, aInfo.applicationInfo);

		// 通知监控器Activity正在启动
		if (mService.mController != null) {
			try {
				Intent watchIntent = intent.cloneFilter();
				abort |= !mService.mController.activityStarting(watchIntent,
						aInfo.applicationInfo.packageName);
			} catch (RemoteException e) {
				mService.mController = null;
			}
		}

		// 如果被防火墙或监控器阻止,则终止启动
		if (abort) {
			if (resultRecord != null) {
				resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
						Activity.RESULT_CANCELED, null);
			}
			ActivityOptions.abort(options);
			return ActivityManager.START_SUCCESS;
		}

		// 创建新的ActivityRecord
		ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
				intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
				requestCode, componentSpecified, this, container, options);
		if (outActivity != null) {
			outActivity[0] = r;
		}

		// 获取焦点栈
		final ActivityStack stack = getFocusedStack();

		// 检查是否允许进行应用切换
		if (voiceSession == null && (stack.mResumedActivity == null
				|| stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
			if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
					realCallingPid, realCallingUid, "Activity start")) {
				PendingActivityLaunch pal =
						new PendingActivityLaunch(r, sourceRecord, startFlags, stack);
				mPendingActivityLaunches.add(pal);
				ActivityOptions.abort(options);
				return ActivityManager.START_SWITCHES_CANCELED;
			}
		}

		// 更新应用切换状态
		if (mService.mDidAppSwitch) {
			mService.mAppSwitchesAllowedTime = 0;
		} else {
			mService.mDidAppSwitch = true;
		}

		// 执行挂起的Activity启动
		doPendingActivityLaunchesLocked(false);

		// 关键点:启动Activity
		err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor,
				startFlags, true, options, inTask);

		// 通知状态(Activity 启动过程中出现错误时才会执行)
		if (err < 0) {
			notifyActivityDrawnForKeyguard();
		}
		return err;
	}

这段代码主要处理启动 Activity 的请求,包括参数检查、权限检查、Intent 检查、创建新的 ActivityRecord、检查应用切换、执行挂起的启动操作等。通过这些步骤,系统能够确保在启动 Activity 时遵循一定的规则和流程,同时确保操作的安全性和合法性。

总结下,做了这么多,实际上就是除了一堆的检查和初始化的一些操作。接下来这里着重解读下为什么在启动Activity 前要执行doPendingActivityLaunchesLocked来执行挂起的Activity启动?

在 Android 系统中,可能会有多个 Activity 启动请求同时到来。为了避免同时启动多个 Activity 导致的问题,系统会将一些请求挂起,等到合适的时机再处理。这一步是为了确保在启动新的 Activity 之前,系统已经处理了所有挂起的 Activity 启动请求。这样做可以避免多个 Activity 同时竞争资源,确保用户体验的流畅性。总之,是为了确保系统资源得到合理调度,用户体验得到保障。

接下来我们继续分析关键方法startActivityUncheckedLocked的实现。该部分代码也是较多,这里拆分成3个部分进行解读,同时对部分异常处理和日志相关代码进行删减。整理后代码解读如下。

2.2.1 startActivityUncheckedLocked 第一阶段代码分析

ActivityStackSupervisor的方法startActivityUncheckedLocked第一阶段代码解读如下:

java 复制代码
//ActivityStackSupervisor
    final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,
            boolean doResume, Bundle options, TaskRecord inTask) {
        //第一阶段
        // 获取启动 Activity 的 Intent 和发起调用的 UID
        final Intent intent = r.intent;
        final int callingUid = r.launchedFromUid;

        // 如果指定了任务,但该任务不在近期任务列表中,则忽略此任务
        if (inTask != null && !inTask.inRecents) {
            inTask = null;
        }

        // 初始化不同的启动模式标志
        final boolean launchSingleTop = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP;
        final boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE;
        final boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK;

        // 获取 Intent 的启动标志
        int launchFlags = intent.getFlags();

        // 如果设置了 FLAG_ACTIVITY_NEW_DOCUMENT 标志,并且启动模式为 SINGLE_INSTANCE 或 SINGLE_TASK,
        // 则忽略 FLAG_ACTIVITY_NEW_DOCUMENT 标志
        if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 &&
                (launchSingleInstance || launchSingleTask)) {
            launchFlags &= ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
        } else {
            // 根据 Activity 的 documentLaunchMode 调整启动标志
            switch (r.info.documentLaunchMode) {
                case ActivityInfo.DOCUMENT_LAUNCH_NONE:
                    break;
                case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING:
                    launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
                    break;
                case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS:
                    launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
                    break;
                case ActivityInfo.DOCUMENT_LAUNCH_NEVER:
                    launchFlags &= ~Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
                    break;
            }
        }

        // 如果 Activity 设置了启动在后台,并且不是 SINGLE_TASK 或 SINGLE_INSTANCE 模式,
        // 并且设置了 FLAG_ACTIVITY_NEW_DOCUMENT,则设置 FLAG_ACTIVITY_MULTIPLE_TASK
        final boolean launchTaskBehind = r.mLaunchTaskBehind
                && !launchSingleTask && !launchSingleInstance
                && (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0;

        // 如果设置了 FLAG_ACTIVITY_NEW_TASK 标志,并且目标 Activity 要求返回结果,
        // 则取消结果返回,并设置 resultTo 为 null
        if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
            r.resultTo.task.stack.sendActivityResultLocked(-1,
                    r.resultTo, r.resultWho, r.requestCode,
                    Activity.RESULT_CANCELED, null);
            r.resultTo = null;
        }

        // 如果设置了 FLAG_ACTIVITY_NEW_DOCUMENT 标志,并且没有设置返回结果,
        // 则设置 FLAG_ACTIVITY_NEW_TASK 标志
        if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && r.resultTo == null) {
            launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
        }

        // 如果设置了 FLAG_ACTIVITY_NEW_TASK 标志,并且 Activity 设置了启动在后台,
        // 或者 documentLaunchMode 设置为 ALWAYS,则设置 FLAG_ACTIVITY_MULTIPLE_TASK
        if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
            if (launchTaskBehind
                    || r.info.documentLaunchMode == ActivityInfo.DOCUMENT_LAUNCH_ALWAYS) {
                launchFlags |= Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
            }
        }

        // 如果没有设置 FLAG_ACTIVITY_NO_USER_ACTION 标志,则表示用户正在离开当前 Activity
        mUserLeaving = (launchFlags & Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0;
        //...

这段代码的主要目的是在启动新 Activity 之前,根据 Activity 的属性和 Intent 的标志,调整和设置正确的启动参数和模式。这包括处理不同的启动模式、文档模式、任务重用、结果回调等。通过这些细致的逻辑处理,系统能够确保 Activity 按照正确的方式启动,同时满足用户和开发者对于 Activity 启动位置和方式的需求。

2.2.2 startActivityUncheckedLocked 第二阶段代码分析

ActivityStackSupervisor的方法startActivityUncheckedLocked第二阶段代码解读如下:

java 复制代码
/ActivityStackSupervisor
    final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,
            boolean doResume, Bundle options, TaskRecord inTask) {
        //...
        //第二阶段
        // 如果不立即恢复,设置延迟恢复标志
        if (!doResume) {
            r.delayedResume = true;
        }

        // 如果设置了 FLAG_ACTIVITY_PREVIOUS_IS_TOP,则保持当前顶部 Activity 在顶部
        ActivityRecord notTop =
                (launchFlags & Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null;

        // 如果设置了 START_FLAG_ONLY_IF_NEEDED 标志,检查是否需要启动 Activity
        if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
            ActivityRecord checkedCaller = sourceRecord;
            if (checkedCaller == null) {
                checkedCaller = getFocusedStack().topRunningNonDelayedActivityLocked(notTop);
            }
            if (!checkedCaller.realActivity.equals(r.realActivity)) {
                startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED;
            }
        }

        // 初始化变量
        boolean addingToTask = false;
        TaskRecord reuseTask = null;

        // 如果没有来源 Activity 但指定了任务,尝试在这个任务中启动新的 Activity
        if (sourceRecord == null && inTask != null && inTask.stack != null) {
            final Intent baseIntent = inTask.getBaseIntent();
            final ActivityRecord root = inTask.getRootActivity();
            //...
            if (root == null) {
                // 合并基础 Intent 和新 Activity 的 Intent 标志
                final int flagsOfInterest = Intent.FLAG_ACTIVITY_NEW_TASK
                        | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT
                        | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
                launchFlags = (launchFlags&~flagsOfInterest)
                        | (baseIntent.getFlags()&flagsOfInterest);
                intent.setFlags(launchFlags);
                inTask.setIntent(r);
                addingToTask = true;
            } else if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
                addingToTask = false;
            } else {
                addingToTask = true;
            }
            reuseTask = inTask;
        } else {
            inTask = null;
        }

        // 如果没有指定任务,检查是否需要创建新任务
        if (inTask == null) {
            if (sourceRecord == null) {
                if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) {
                    launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
                }
            } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
                launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
            } else if (launchSingleInstance || launchSingleTask) {
                launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
            }
        }
        // 初始化用于创建新任务的信息和意图对象
        ActivityInfo newTaskInfo = null;
        Intent newTaskIntent = null;
        ActivityStack sourceStack;

        // 如果存在来源 Activity(sourceRecord)
        if (sourceRecord != null) {
            // 如果来源 Activity 正在完成,我们不能将其作为新 Activity 的来源
            if (sourceRecord.finishing) {
                // 因为与它关联的任务可能现在已为空并正在退出,所以我们不想盲目地将其扔进那个任务。
                // 相反,我们将采取新任务的流程并尝试为它找到一个任务。但要保存任务信息,以便在创建新任务时使用。
                if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
                    // 强制设置新任务标志
                    launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
                    // 保存来源 Activity 的信息和任务的意图,以便用于创建新任务
                    newTaskInfo = sourceRecord.info;
                    newTaskIntent = sourceRecord.task.intent;
                }
                sourceRecord = null;
                sourceStack = null;
            } else {
                sourceStack = sourceRecord.task.stack;
            }
        } else {
            sourceStack = null;
        }

        // 初始化表示是否已将 Activity 移动到主屏幕的标志
        boolean movedHome = false;
        // 初始化目标任务栈对象
        ActivityStack targetStack;

        // 设置 Intent 的标志,这些标志将影响 Activity 的启动方式
        intent.setFlags(launchFlags);

        // 如果需要创建新任务或重用现有任务,找到目标任务栈
        if (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
                (launchFlags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
                || launchSingleInstance || launchSingleTask) {
            if (inTask == null && r.resultTo == null) {
                ActivityRecord intentActivity = !launchSingleInstance ?
                        findTaskLocked(r) : findActivityLocked(intent, r.info);
                if (intentActivity != null) {
                    // 处理任务切换和任务栈的调整
                    // ...
                }
            }
        }
        //...

这段代码的主要目的是负责处理新Activity的启动逻辑。它涵盖了各种复杂的场景,包括延迟恢复、检查是否需要启动Activity、处理不同的启动模式(如单实例或单任务)、以及决定是否在现有任务中启动Activity或创建新任务。代码还涉及了对Intent标志的处理,确保在启动新Activity时能够正确地应用这些标志。此外,它还负责找到合适的任务栈来启动Activity,无论是重用现有的任务还是创建新任务,并处理任务切换和栈的调整。

2.2.3 startActivityUncheckedLocked 第三阶段代码分析

ActivityStackSupervisor的方法startActivityUncheckedLocked第三阶段代码解读如下:

java 复制代码
//ActivityStackSupervisor
    final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,
            boolean doResume, Bundle options, TaskRecord inTask) {
        //...
        //第三阶段
        // 如果当前 Activity 已经在最近使用的栈顶,并且没有设置返回结果的请求,则不需要启动新的 Activity 实例
        if (r.packageName != null) {
            ActivityStack topStack = getFocusedStack();
            ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop);
            if (top != null && r.resultTo == null) {
                if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
                    if (top.app != null && top.app.thread != null) {
                        if ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
                            || launchSingleTop || launchSingleTask) {
                            // 将 Activity 栈中最后一个暂停的 Activity 设置为 null
                            topStack.mLastPausedActivity = null;
                            // 如果需要恢复,恢复栈顶的 Activity
                            if (doResume) {
                                resumeTopActivitiesLocked();
                            }
                            // 终止启动选项
                            ActivityOptions.abort(options);
                            // 如果设置了只启动 Activity 如果需要,则返回一个特殊的结果代码
                            if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
                                return ActivityManager.START_RETURN_INTENT_TO_CALLER;
                            }
                            // 发送新的 Intent 给栈顶的 Activity
                            top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
                            return ActivityManager.START_DELIVERED_TO_TOP;
                        }
                    }
                }
            }
        } else {
            // 如果 Activity 的包名为空,发送取消结果并返回没有找到类的启动结果
            if (r.resultTo != null) {
                r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho,
                        r.requestCode, Activity.RESULT_CANCELED, null);
            }
            ActivityOptions.abort(options);
            return ActivityManager.START_CLASS_NOT_FOUND;
        }

        // 初始化变量
        boolean newTask = false;
        boolean keepCurTransition = false;

        // 如果需要在后台启动任务,则设置关联任务
        TaskRecord taskToAffiliate = launchTaskBehind && sourceRecord != null ?
                sourceRecord.task : null;

        // 如果没有设置返回结果的请求,没有指定任务,没有添加到任务,并且设置了新任务的标志,则创建新任务
        if (r.resultTo == null && inTask == null && !addingToTask
                && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
            if (isLockTaskModeViolation(reuseTask)) {
                return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
            }
            newTask = true;
            targetStack = adjustStackFocus(r, newTask);
            if (!launchTaskBehind) {
                targetStack.moveToFront("startingNewTask");
            }
            if (reuseTask == null) {
                // 创建新任务并设置任务记录
                r.setTask(targetStack.createTaskRecord(getNextTaskId(),
                        newTaskInfo != null ? newTaskInfo : r.info,
                        newTaskIntent != null ? newTaskIntent : intent,
                        voiceSession, voiceInteractor, !launchTaskBehind /* toTop */),
                        taskToAffiliate);
            } else {
                // 重用现有任务
                r.setTask(reuseTask, taskToAffiliate);
            }
            if (!movedHome) {
                if ((launchFlags &
                        (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME))
                        == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) {
                    r.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
                }
            }
        } else if (sourceRecord != null) {
            // 如果有来源 Activity,则将其设置为目标任务
            final TaskRecord sourceTask = sourceRecord.task;
            if (isLockTaskModeViolation(sourceTask)) {
                return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
            }
            targetStack = sourceTask.stack;
            targetStack.moveToFront("sourceStackToFront");
            // ...
            r.setTask(sourceTask, null);
        } else if (inTask != null) {
            // 如果指定了任务,则将其设置为目标任务
            if (isLockTaskModeViolation(inTask)) {
                return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
            }
            targetStack = inTask.stack;
            targetStack.moveTaskToFrontLocked(inTask, r, options, "inTaskToFront");
            // ...
            r.setTask(inTask, null);
        } else {
            // 如果没有来源 Activity 或指定任务,则将新 Activity 添加到当前栈顶的任务
            targetStack = adjustStackFocus(r, newTask);
            targetStack.moveToFront("addingToTopTask");
            ActivityRecord prev = targetStack.topActivity();
            r.setTask(prev != null ? prev.task : targetStack.createTaskRecord(getNextTaskId(),
                            r.info, intent, null, null, true), null);
            mWindowManager.moveTaskToTop(r.task.taskId);
        }

        // 授予 Intent 中的 Uri 权限
        mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
                intent, r.getUriPermissionsLocked(), r.userId);

        // 如果来源 Activity 是最近任务,则设置任务返回类型
        if (sourceRecord != null && sourceRecord.isRecentsActivity()) {
            r.task.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE);
        }

        // 启动 Activity
        targetStack.mLastPausedActivity = null;
        targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options);

        // 如果不是在后台启动任务,则设置焦点
        if (!launchTaskBehind) {
            mService.setFocusedActivityLocked(r, "startedActivity");
        }

        // 返回启动成功
        return ActivityManager.START_SUCCESS;
    }

这段代码的主要目的确定如何在Activity栈中处理新的Activity启动请求。代码首先检查是否需要在当前栈顶的Activity上重新投递一个新的Intent,而不是创建一个新的Activity实例。如果需要创建新的Activity实例,代码会根据启动标志(如是否需要新任务、是否是单实例等)来决定是否创建新的任务或重用现有的任务。此外,它还处理了锁屏模式下的任务冲突、Intent中的Uri权限授予,以及在适当的情况下更新最近任务的活动。最终,如果一切条件都满足,这段代码会启动新的Activity,更新系统状态,并确保用户体验的连贯性。

最后总结下:整个三个阶段的核心目的确实是找到合适的任务栈(TaskStack)来启动新的 Activity。这个过程涉及多个步骤和决策,如上面3个小节的分析。

接下来关注这个启动新Activity的方法:ActivityStack的startActivityLocked方法。

2.3 关键方法ActivityStack.startActivityLocked分析

ActivityStack中startActivityLocked方法的实现如下:

java 复制代码
//ActivityStack
    final void startActivityLocked(ActivityRecord r, boolean newTask,
            boolean doResume, boolean keepCurTransition, Bundle options) {
        TaskRecord rTask = r.task;
        final int taskId = rTask.taskId;

        // 如果不是启动任务到后台,且任务不在历史栈中或需要新任务,将其插入到栈顶
        if (!r.mLaunchTaskBehind && (taskForIdLocked(taskId) == null || newTask)) {
            insertTaskAtTop(rTask);
            mWindowManager.moveTaskToTop(taskId);
        }

        TaskRecord task = null;
        if (!newTask) {
            //从任务历史栈的顶部开始查找,以确定是否要启动新的 Activity
            //...
        }

        // 将新 Activity 添加到其任务的顶部,并更新任务历史栈
        task = r.task;
        task.addActivityToTop(r);
        task.setFrontOfTask();
        r.putInHistory();
        
        if (!isHomeStack() || numActivities() > 0) {
            //根据是否是新任务,准备适当的窗口动画
            //...
        }
        
        //关键代码,从am命令到这里值为true,因此一定会执行resumeTopActivitiesLocked方法
        if (doResume) {
            mStackSupervisor.resumeTopActivitiesLocked(this, r, options);
        }
    }

注意:这里的activity切换和动画处理相关的逻辑和整合在一起的。

这段代码是Android系统中负责启动新Activity的关键逻辑,它主要处理将Activity添加到任务栈、更新窗口管理器、准备窗口动画以及在需要时恢复Activity的状态。代码首先检查是否需要将新Activity置于现有任务栈顶或创建新任务,然后根据是否是新任务以及全屏任务的存在与否来确定Activity在任务历史栈中的位置。接着,它更新窗口管理器以准备适当的动画和显示新的Activity,最后,按需执行resume相关的操作。从am命令出发分析,这里doResume为true,一定会执行resumeTopActivitiesLocked方法。接下来主要是resume相关方法的处理。

2.4 resume相关流程分析

ActivityStackSupervisor的resumeTopActivitiesLocked方法,代码实现如下:

java 复制代码
//ActivityStackSupervisor
    //resume一个或多个任务栈顶的 Activity
    boolean resumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord target,
            Bundle targetOptions) {
        // 如果没有指定目标栈,则获取当前获取焦点的栈
        if (targetStack == null) {
            targetStack = getFocusedStack();
        }

        // 定义一个标志,用于记录是否成功resume 栈顶的 Activity
        boolean result = false;

        // 如果目标栈是前台栈,则resume 栈顶的 Activity
        if (isFrontStack(targetStack)) {
            //关键代码
            result = targetStack.resumeTopActivityLocked(target, targetOptions);
        }

        // 遍历所有显示中的所有任务栈
        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
                final ActivityStack stack = stacks.get(stackNdx);
                // 如果当前栈是目标栈,则跳过,因为已经处理过
                if (stack == targetStack) {
                    continue;
                }
                // 如果当前栈是前台栈,则resume 栈顶的 Activity
                if (isFrontStack(stack)) {
                    stack.resumeTopActivityLocked(null);
                }
            }
        }       
        return result;// 返回是否成功resume了目标栈顶的 Activity
    }

这里我们主要是以am命令启动系统设置界面。由于 Launcher 处于前台,并且系统设置与 Launcher 在同一任务栈组中,所以这种情况下 isFrontStack(targetStack) 返回 true。因此我们继续分析关键代码ActivityStack.resumeTopActivityLocked方法的实现。代码如下:

java 复制代码
//ActivityStack
    //关键流程:step1
    final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
        // 检查是否已经在执行恢复顶部 Activity 的操作,防止递归调用
        if (mStackSupervisor.inResumeTopActivity) {
            return false;   // 如果已经在执行恢复操作,则直接返回 false
        }

        // 初始化结果变量,用于记录恢复操作是否成功
        boolean result = false;

        try {
            // 设置一个标志,表示正在执行恢复顶部 Activity 的操作,防止递归
            mStackSupervisor.inResumeTopActivity = true;
            //锁屏状态相关处理
            //...
            //关键方法:调用内部方法实际执行恢复顶部 Activity 的操作
            result = resumeTopActivityInnerLocked(prev, options);
        } finally {
            // 恢复完成后,重置正在执行恢复操作的标志
            mStackSupervisor.inResumeTopActivity = false;
        }

        // 返回恢复操作的结果
        return result;
    }

    //关键流程:step2
    final boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {
        if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen("");
        //...
        cancelInitializingActivities();
        final ActivityRecord next = topRunningActivityLocked(null);
        final boolean userLeaving = mStackSupervisor.mUserLeaving;
        mStackSupervisor.mUserLeaving = false;

        final TaskRecord prevTask = prev != null ? prev.task : null;
        if (next == null) {
            // Launcher...
            ActivityOptions.abort(options);
            final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ?
                    HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo();
            return isOnHomeDisplay() &&
                    mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, "noMoreActivities");
        }

        next.delayedResume = false;
        //...
        mStackSupervisor.mStoppingActivities.remove(next);
        mStackSupervisor.mGoingToSleepActivities.remove(next);
        next.sleeping = false;
        mStackSupervisor.mWaitingVisibleActivities.remove(next);
        if (!mStackSupervisor.allPausedActivitiesComplete()) {
            return false;
        }

        boolean dontWaitForPause = (next.info.flags&ActivityInfo.FLAG_RESUME_WHILE_PAUSING) != 0;
        boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, true, dontWaitForPause);
        if (mResumedActivity != null) {
            pausing |= startPausingLocked(userLeaving, false, true, dontWaitForPause);
        }
        if (pausing) {
            if (next.app != null && next.app.thread != null) {
                mService.updateLruProcessLocked(next.app, true, null);
            }
            return true;
        }
        //...
        AppGlobals.getPackageManager().setPackageStoppedState(next.packageName, false, next.userId);
        //...

        boolean anim = true;
        if (prev != null) {
            //动画处理相关逻辑
            //...
        }

        Bundle resumeAnimOptions = null;
        if (anim) {
            ActivityOptions opts = next.getOptionsForTargetActivityLocked();
            if (opts != null) {
                resumeAnimOptions = opts.toBundle();
            }
            next.applyOptionsLocked();
        } else {
            next.clearOptionsLocked();
        }

        ActivityStack lastStack = mStackSupervisor.getLastStack();
        if (next.app != null && next.app.thread != null) {
            //如果ActivityRecord已经存在进程的相关逻辑,只需要重启activity即可
            //...
        } else {
            // 第一次启动
            if (!next.hasBeenLaunched) {
                next.hasBeenLaunched = true;
            } else {
                //通知WMS显示启动界面
                //...
            }
            //关键方法
            mStackSupervisor.startSpecificActivityLocked(next, true, true);
        }
        return true;
    }

resumeTopActivityLocked中调用了关键方法resumeTopActivityInnerLocked,进而调用到关键方法ActivityStackSupervisor.startSpecificActivityLocked。

2.5 启动应用进程相关流程分析

startSpecificActivityLocked方法主要是调用startProcessLocked来创建一个新进程。然后重启一个特定的 Activity,代码实现如下:

java 复制代码
//ActivityStackSupervisor
    void startSpecificActivityLocked(ActivityRecord r,
            boolean andResume, boolean checkConfig) {
        ProcessRecord app = mService.getProcessRecordLocked(r.processName,
                r.info.applicationInfo.uid, true);

        r.task.stack.setLaunchTime(r);
        // 如果获取到的进程记录不为空,并且进程的线程也不为空
        if (app != null && app.thread != null) {
            try {
                if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
                        || !"android".equals(r.info.packageName)) {
                    app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
                            mService.mProcessStats);
                }
                // 调用实际启动 Activity 的方法
                realStartActivityLocked(r, app, andResume, checkConfig);
                return;
            } catch (RemoteException e) {
                //...
            }
        }
        // 如果进程不存在或线程为空,则请求启动新的进程
        mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
                "activity", r.intent.getComponent(), false, false, true);
    }

这里的逻辑是第一次启动,因此直接调用AMS的startProcessLocked方法。startProcessLocked实际上调用了很多层,相关流程的代码实现如下:

java 复制代码
//ActivityManagerService
    //关键流程:step1
    final ProcessRecord startProcessLocked(String processName,
            ApplicationInfo info, boolean knownToBeDead, int intentFlags,
            String hostingType, ComponentName hostingName, boolean allowWhileBooting,
            boolean isolated, boolean keepIfLarge) {
        return startProcessLocked(processName, info, knownToBeDead, intentFlags, hostingType,
                hostingName, allowWhileBooting, isolated, 0 /* isolatedUid */, keepIfLarge,
                null /* ABI override */, null /* entryPoint */, null /* entryPointArgs */,
                null /* crashHandler */);
    }

    //关键流程:step2
    // 定义一个方法以启动一个新的应用程序进程
    final ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
            boolean knownToBeDead, int intentFlags, String hostingType, ComponentName hostingName,
            boolean allowWhileBooting, boolean isolated, int isolatedUid, boolean keepIfLarge,
            String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
        ProcessRecord app;

        // 如果不是隔离进程,尝试获取现有的进程记录
        if (!isolated) {
            app = getProcessRecordLocked(processName, info.uid, keepIfLarge);
        } else {
            app = null; // 隔离进程不重用现有进程
        }

        // 如果获取到了进程记录并且进程ID有效
        if (app != null && app.pid > 0) {
            // 如果已知进程未死亡或者线程为空(即进程已经启动但尚未绑定)
            if (!knownToBeDead || app.thread == null) {
                // 将新包添加到进程中
                app.addPackage(info.packageName, info.versionCode, mProcessStats);
                return app; // 返回获取到的进程记录
            }

            // 如果进程已死亡,杀死这个进程
            Process.killProcessGroup(app.info.uid, app.pid);
            handleAppDiedLocked(app, true, true); // 处理应用死亡
        }

        // 如果托管名称不为空,转换为短字符串形式
        String hostingNameStr = hostingName != null ? hostingName.flattenToShortString() : null;

        // 如果不是隔离进程,处理进程崩溃和启动策略
        if (!isolated) {
            if ((intentFlags & Intent.FLAG_FROM_BACKGROUND) != 0) {
                // 如果是从后台启动,检查是否是坏进程
                if (mBadProcesses.get(info.processName, info.uid) != null) {
                    return null; // 如果是坏进程,不启动新进程
                }
            } else {
                // 如果不是从后台启动,清除崩溃次数
                mProcessCrashTimes.remove(info.processName, info.uid);
                if (mBadProcesses.get(info.processName, info.uid) != null) {
                    mBadProcesses.remove(info.processName, info.uid); // 移除坏进程记录
                    if (app != null) {
                        app.bad = false; // 重置坏进程标志
                    }
                }
            }
        }

        // 如果没有获取到进程记录,创建新的进程记录
        if (app == null) {
            app = newProcessRecordLocked(info, processName, isolated, isolatedUid);
            if (app == null) {
                return null; // 如果创建失败,返回null
            }
            app.crashHandler = crashHandler; // 设置崩溃处理程序
            mProcessNames.put(processName, app.uid, app); // 将新进程记录添加到映射表
            if (isolated) {
                mIsolatedProcesses.put(app.uid, app); // 隔离进程添加到隔离进程映射表
            }
        } else {
            // 如果是已有的进程记录,添加新包
            app.addPackage(info.packageName, info.versionCode, mProcessStats);
        }

        // 如果系统尚未准备好,不允许在启动过程中启动,并且没有特别允许
        if (!mProcessesReady && !isAllowedWhileBooting(info) && !allowWhileBooting) {
            if (!mProcessesOnHold.contains(app)) {
                mProcessesOnHold.add(app); // 将进程添加到等待列表
            }
            return app; // 返回进程记录
        }

        // 调用实际启动进程的方法
        startProcessLocked(app, hostingType, hostingNameStr, abiOverride, entryPoint, entryPointArgs);
        return (app.pid != 0) ? app : null; // 如果进程ID有效,返回进程记录,否则返回null
    }
    //关键流程:step3
    private final void startProcessLocked(ProcessRecord app, String hostingType,
            String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
        // 如果进程已经存在,移除之前的记录
        if (app.pid > 0 && app.pid != MY_PID) {
            synchronized (mPidsSelfLocked) {
                mPidsSelfLocked.remove(app.pid);
                mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
            }
            app.setPid(0);
        }
        mProcessesOnHold.remove(app);
        updateCpuStats();

        try {
            int uid = app.uid;
            int[] gids = null;
            int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;

            // 获取应用的GID
            if (!app.isolated) {
                int[] permGids = app.info.packageName != null ?
                        mContext.getPackageManager().getPackageGids(app.info.packageName) : null;
                if (permGids != null) {
                    gids = new int[permGids.length + 2];
                    System.arraycopy(permGids, 0, gids, 2, permGids.length);
                } else {
                    gids = new int[2];
                }
                gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
                gids[1] = UserHandle.getUserGid(UserHandle.getUserId(uid));
            }

            // 设置调试标志
            int debugFlags = 0;
            if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
                debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
                debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
            }
            if ((app.info.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0 || mSafeMode) {
                debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
            }

            // 设置所需的ABI
            String requiredAbi = abiOverride != null ? abiOverride : app.info.primaryCpuAbi;
            if (requiredAbi == null) {
                requiredAbi = Build.SUPPORTED_ABIS[0];
            }

            // 设置指令集
            String instructionSet = VMRuntime.getInstructionSet(app.info.primaryCpuAbi);

            // 设置进程参数
            app.gids = gids;
            app.requiredAbi = requiredAbi;
            app.instructionSet = instructionSet;

            // 启动进程
            Process.ProcessStartResult startResult = Process.start(entryPoint,
                    app.processName, uid, uid, gids, debugFlags, mountExternal,
                    app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                    app.info.dataDir, entryPointArgs);

            // 更新进程信息
            app.setPid(startResult.pid);
            app.usingWrapper = startResult.usingWrapper;
            app.removed = false;
            app.killed = false;
            app.killedByAm = false;
            synchronized (mPidsSelfLocked) {
                mPidsSelfLocked.put(startResult.pid, app);
                if (isActivityProcess) {
                    mHandler.sendMessageDelayed(mHandler.obtainMessage(PROC_START_TIMEOUT_MSG, app),
                            startResult.usingWrapper ? PROC_START_TIMEOUT_WITH_WRAPPER
                                    : PROC_START_TIMEOUT);
                }
            }
        } catch (RuntimeException e) {
            // 处理启动过程中的异常
        }
    }

这里最后通过调用Process.start方法通过socket向Zygote发送一个消息,传递相关信息,让Zygote创建一个对应的应用进程。创建APP部分的逻辑可参考系列文章中开机启动流程分析中的这一章:

android 开机启动流程分析(13)Zygote的分裂

当zygote进程 fork一个应用进程启动后,应用进程的入口是ActivityThread的main函数。下一篇文章我们开始ActivityThread的相关分析。

3 startActivityMayWait流程分析额外知识补充说明

3.1 Binder.clearCallingIdentity() 和 Binder.restoreCallingIdentity()的设计解读

在 Android 系统中,Binder.clearCallingIdentity() 和 Binder.restoreCallingIdentity() 是两个非常重要的方法,它们用于在跨进程通信时管理和控制调用者的身份信息。

  • **Binder.clearCallingIdentity():**这个方法用于临时清除当前线程的调用者身份信息,包括用户 ID(UID)和进程 ID(PID)。这通常在需要执行敏感操作,而这个操作不应该受到调用者身份影响时使用。例如,当一个系统服务需要以自己的身份而不是调用者的身份去执行某个操作时,就会使用这个方法。
  • **Binder.restoreCallingIdentity(long token):**这个方法用于恢复之前通过 Binder.clearCallingIdentity() 清除的调用者身份信息。它接受一个 token 参数,这个 token 是之前 clearCallingIdentity() 方法调用时返回的,包含了清除之前的身份信息。调用 restoreCallingIdentity() 后,当前线程的调用者身份会被恢复。

这种设计的主要目的是为了解决权限和身份管理的问题。在 Android 系统中,不同的操作可能需要不同的权限级别。通过这种方式,系统服务可以确保在执行特定操作时,不会因为调用者的身份而拥有不应该有的权限,从而保证了系统的安全性。

一个典型的使用场景是,当一个应用进程通过 Binder 调用系统服务时,系统服务可能会在处理请求的过程中,需要以自己的身份去调用另一个系统服务。在这种情况下,第一个系统服务会先使用 clearCallingIdentity() 清除调用者的身份,然后执行操作,操作完成后,再通过 restoreCallingIdentity() 恢复调用者的身份。

虽然这两个方法提供了一种控制调用者身份的手段,但它们也必须谨慎使用,以避免潜在的安全风险。例如,如果 restoreCallingIdentity() 没有成对地与 clearCallingIdentity() 使用,或者 token 被传递给了不应该拥有它的代码,都可能导致身份信息的泄露或滥用。

总之,这两个方法的设计是为了在需要的时候能够临时改变调用者的身份,以确保操作的安全性和正确性。它们是 Android IPC 机制中重要的一环,用于保护系统服务不被未授权的调用者滥用。

3.2 重量级进程的概念及设计解读

重量级进程(Heavy-Weight Process)是指那些被系统标记为不适合保存和恢复状态的进程,通常是因为它们运行着一些资源密集型或者难以恢复状态的操作。在Android系统中,重量级进程的概念主要用于处理那些即使在后台运行也不会被系统轻易杀死的进程,以确保用户体验的连贯性和应用的稳定性。

重量级进程的设计目的主要是为了处理那些对用户体验有显著影响的应用,例如游戏或者复杂的绘图应用。这些应用可能在后台运行,但它们的状态很难被保存和恢复,或者保存和恢复的成本非常高。因此,系统会给予这些进程较高的优先级,避免在资源紧张时被系统回收,从而确保用户在返回这些应用时能够获得无缝的体验。

在Android系统中,重量级进程通常通过在应用的AndroidManifest.xml文件中设置android:allowTaskReparenting="true"或者android:taskAffinity=""来标识。这样的应用在启动时,系统会检查是否已经有其他重量级进程在运行,如果有,系统会提供一个切换的选项,让用户决定是否要关闭已有的重量级进程来启动新的重量级进程。

重量级进程的状态本身并不占用很多资源,但是它们运行的任务通常需要大量的内存和CPU资源,因此系统需要特别管理这些进程,以保证整体的系统性能和用户体验。通过这种方式,重量级进程可以确保它们在需要时能够获得足够的资源,同时也避免了不必要的资源浪费。

3.3 FLAG_ACTIVITY_FORWARD_RESULT标志位解读

FLAG_ACTIVITY_FORWARD_RESULT 是 Android 中用于 Intent 的一个标志,它的主要作用是将一个 Activity 的结果转发给另一个 Activity。这个标志通常用于解决多个 Activity 之间值传递的问题,特别是当存在多个中间 Activity 时,可以通过这个标志将结果透传到最终的接收者。总结下,FLAG_ACTIVITY_FORWARD_RESULT 标志位主要解决如下问题:

  • 数据透传:在多个 Activity 连续跳转的情况下,允许数据从源 Activity 直接传递到最终的接收 Activity,即使中间有多个过渡 Activity。
  • 简化结果处理:避免了在每个中间 Activity 中手动处理结果转发的复杂性,系统会自动处理。
  • 避免请求码冲突:在多个 Activity 之间传递结果时,如果每个 Activity 都使用自己的请求码,可能会导致请求码管理复杂,使用 FLAG_ACTIVITY_FORWARD_RESULT 可以避免这个问题。

接下来使用一个例子来更好地理解FLAG_ACTIVITY_FORWARD_RESULT 标志。

假设 Activity A 启动了 Activity B,Activity B 又启动了 Activity C。如果 B 使用了 FLAG_ACTIVITY_FORWARD_RESULT 标志,那么 C 可以通过 setResult() 方法直接将结果返回给 A,而不需要 B 手动处理。对应的代码实现如下:

java 复制代码
Intent intent = new Intent(BuyActivity.this, BuyResultActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
startActivity(intent);
finish();

在这个例子中,如果 BuyActivity(B)启动了 BuyResultActivity(C),并且设置了 FLAG_ACTIVITY_FORWARD_RESULT,那么 BuyResultActivity(C)可以直接将结果通过 setResult() 返回,最终这些结果会被传递给启动 BuyActivity(B)的 Activity(即 A)。

3.4 isolate 隔离进程解读

在Android系统中,设置隔离进程主要是为了增强安全性和稳定性,以及为了实现多用户环境和保护用户数据。隔离进程通常用于运行那些需要更高安全保障的组件,例如处理敏感数据的服务或者需要独立于主应用运行的后台进程。

那么如何设置隔离进程呢?

一般在AndroidManifest.xml文件中指定进程名称。在该文件中为特定的组件(如Service、Activity等)指定android:process属性,使其运行在一个新的进程中。例如:

html 复制代码
<service android:name=".MyService" android:process=":isolated" />

这里的:isolated是一个命名约定,表示进程是一个隔离进程。进程名称前缀为com.example.xxx:,其中com.example.xxx是对应应用的包名。

最后,隔离进程解决的问题总结如下:

  • 安全性: 隔离进程可以防止恶意应用访问或篡改其他进程的数据,从而保护用户数据和系统安全。
  • 稳定性: 如果一个进程崩溃,它不会影响其他进程,这有助于提高整个系统的稳定性。
  • 多用户环境: 在多用户设备上,隔离进程可以确保不同用户的应用实例相互独立,保护用户的隐私。
  • 数据保护: 隔离进程可以保护敏感数据,防止其他应用或进程访问。
相关推荐
Dnelic-2 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen4 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年11 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿14 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神15 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛15 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法16 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter17 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快18 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl18 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5