AMS的深入浅出
AMS启动过程
在Android应用启动流程中,AMS( Activity Manager Service )的启动是非常关键的。以下是AMS的启动过程:
1.进程启动
当我们启动一个应用时,系统会挂起Zygote进程。然后,Zygote生成新的应用进程,乘坐第一辆列车到达了Android的世界。这样一来,应用就有了属于自己的执行环境。
2.初始化AMS
应用进程执行后,它会初始化AMS对象,其实AMS就是整个Android应用启动的大管家,负责管理所有Activity、Service以及Task栈等。
3.启动Activity
用户点击应用图标,系统判断该Activity所在进程是否启动,如果未启动,则调用Zygote生成对应的新进程,生成进程后再创建对应的Activity对象,并展示到界面上(不包括横竖屏切换和窗口切换)。
4.与AMS交互实现Activity启动
- 首先,
ActivityManagerNative.getDefault()
获取跨进程调用的接口对象singleton
; - 然后,调用方法
startActivityAsUser(userId, ..., args)
向AMS发送请求,申请启动一个Activity
; - AMS收到请求后,判断
Activity
所在进程是否存在,若不存在则创建。创建完成后,将ActivityRecord
加入ActivityStack
或task
中。
5.Zygote启动Application
如果说AMS是Android应用启动的大管家,那么Zygote就是应用启动的魔法师。当Zygote进程创建好了Activity
进程,并已经给AMS抛出了Activity
生命周期的事件之后,AMSi即可调用Zygote方法去“打开”这个应用程序。Zygote取得类路径和参数信息,并解析出主函数所对应的类名和方法名,最终通过反射技术,加载指定文件并运行里面的静态main()方法。
AMS重要数据结构解析
-
ProcessRecord:进程记录是AMS管理应用程序的最底层数据结构之一,它维护了一个应用程序的所有信息,包括包名、UID、进程名、用户ID等。每个进程记录都与一个ApplicationRecord关联,表示该进程所对应的应用程序。
-
ActivityRecord:指Activity在AMS内部的表示,其本质是一个封装了Activity对象和相关状态信息的类。AMS通过ActivityRecord来管理Activity组件的创建、启动、停止等操作。
-
TaskRecord:指任务栈记录,又称作任务记录。Android应用程序通常由多个Activity组成,并按照一定的调用顺序形成不同的任务栈。每个TaskRecord记录了一个任务栈中所有Activity的顺序以及运行状态。
-
IntentRecord:指Intent请求记录,也称Activity请求记录,AMS通过此数据结构来存储Activity启动请求,例如Intent的Action、Category、Data等信息,在后续启动过程中进行匹配以确定启动哪个Activity。
在Android实际应用开发中,开发者可以利用AMS提供的API来实现各种复杂的逻辑。例如:
-
跨进程通信:借助Binder机制和AMS的辅助,在不同进程之间传递消息或执行远程方法调用,实现跨应用或服务端/客户端的各种功能。
-
启动模式管理:通过设置不同的Activity启动模式,灵活管理Activity的启动行为,例如SingleTop模式避免Activity重复创建,FlagActivity模式实现不同自定义标记的特殊行为等。
-
standard
:系统的默认模式,一次跳转即会生成一个新的实例。假设有一个activity
命名为MainActivity
,执行语句:
startActivity(new Intent(MainActivity.this, MainActivity.class))
后,MainActivity
将跳转到另外一个MainActivity
,也就是现在的Task栈里面有MainActivity
的两个实例。 按返回键后你会发现仍然是在MainActivity
(第一个)里面。 显示不同列表信息; -
singleTop
:singleTop
跟standard
模式比较类似。如果已经有一个实例位于Activity
栈的顶部时,就不产生新的实例,而只是调用Activity
中的newInstance()
方法。如果不位于栈顶,会产生一个新的实例。例:当MainActivity
为singleTop
模式时,执行跳转后栈里面依旧只有一个实例,如果现在按返回键程序将直接退出。 通知或即时响应界面;在
Activity
类中,如果开发人员需要为活动添加一些输入参数,则可以使用静态的newInstance()
方法来创建一个新的Activity实例。newInstance()
方法必须在实例化的过程中手动设置数据传递,这样可以避免外部代码直接访问类的成员变量,在处理屏幕旋转和内存重新启动时更加方便。例如,对于一个简单的活动,可以定义一个静态的`newInstance()`方法,该方法将通过一个Bundle将整数保存到Activity的arguments字段中。然后,在Activity的onCreate方法中,可以调用getArguments()以获取该数字。 ```java public class MyActivity extends Activity { private static final String ARG_MY_INT = "my_int"; private int myInt; public static MyActivity newInstance(int myInt) { Bundle args = new Bundle(); args.putInt(ARG_MY_INT, myInt); MyActivity f1 = new MyActivity(); f1.setArguments(args); return f1; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { myInt = getArguments().getInt(ARG_MY_INT); } } } ``` 在这个例子中,使用者可以像这样来启动Activity: ```java MyActivity.newInstance(42); ```
-
singleTask
:singleTask
模式和后面的singleInstance
模式都是只创建一个实例的。在这种模式下,无论跳转的对象是不是位于栈顶的activity,程序都不会生成一个新的实例 (当然前提是栈里面已经有这个实例)。这种模式相当有用,在以后的多activity开发中,经常会因为跳转的关系导致同个页面生成多个实例,这个在用户体验上始终有点不好,而 如果你将对应的activity声明为 singleTask 模式,这种问题将不复存在。 比如首页; -
singleInstance: 设置为 singleInstance 模式的 activity 将独占一个task(感觉task可以理解为进程),没有就新建然后放到独立task里。比如系统默认电话界面。
-
- 进程垃圾清理:合理使用AMS提供的
Process.killProcess
等API,及时关闭无用的进程,释放存储空间和CPU资源。
ActivityStack 不是一个具体的物理存在,而是Android系统在内存中维护的一个数据结构。它表示所有已启动的 Activity 的集合以及它们在屏幕上的顺序。
可以通过 ActivityThread 类的 getApplication() & getActivities() 方法来获取应用程序中所有Activity实例相关的信息。其中,getActivities() 方法会返回一个 ArrayMap ,Key 值为 IBinder 对象(通过此对象可以与每个 Activity 交互),Value 值为 WeakReference 对象(弱引用指向了相应的 Activity 实例)。
lntent的Flag与taskAffinity
Activity启动流程大致分为以下几个步骤:
-
当需要启动一个新的Activity时,会向AMS发送启动请求。
-
AMS会根据该请求创建一个WaittingRecord并将其添加到待处理队列中。
-
AMS会检查当前进程的可用性,如果存在一个运行在该进程中的Activity,则该Activity所在的进程会直接被使用;否则,就需要启动一个新的进程来承载即将启动的Activity。
-
如果需要启动一个新进程,则AMS会利用Zygote fork出一个新进程,并启动该进程的主线程。
-
在新的进程中,会创建一个Application Object和ActivityThread对象,并把ActivityThread添加到主线程的消息循环中。
-
ActivityThread会依次完成Looper、AMS Binding、Handler回调等步骤。
-
通过AMS获取ActivityInfo和TaskAffinity,然后再根据Intent中相关配置启动Activity,并使用Token进一步关联Activity和进程之间的交互。
至于
TaskAffinity
和Flag
,其中TaskAffinity是任务归属的属性,在Android系统中会以字符串形式对Activity进行打标签。每一个Activity都绑定着一个Task,标识成为“任务”(Task)的名字就是这个Activity的TaskAffinity属性值, 每个Task可以有多个Activity。而Flag是与Intent相关的,用于定义Activity的启动模式和行为,比如FLAG_ACTIVITY_NEW_TASK会在启动Activity时创建一个新的任务栈。
TaskAffinity
指定了应用程序组件要加入哪个任务。
在 AndroidManifest 文件中,可以为应用程序或 Activity 指定 affinity 属性。如果不使用 affinity 属性,则默认情况下 Activity 的名称成为任务 affinity。TaskAffinity 可以在代码中被动态修改。 要动态更改 TaskAffinity,请在执行 startActivityForResult()
或 startActivity()
时清除 Intent
的 taskAffinity
属性,如以下示例所示:
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP)
// Dynamic modify task affinity
if (newCondition) {
intent.setTaskAffinity("com.example.newtaskaffinity");
} else {
intent.setTaskAffinity("com.example.othertaskaffinity");
}
startActivity(intent);
FLAG_ACTIVITY_NEW_TASK
和 FLAG_ACTIVITY_CLEAR_TOP
标志确保启动一个新的活动且使其成为栈顶,以便直接与其他具有相同 TaskAffinity 的 Activities 进行交互。在上述示例中,通过根据特定条件来动态地更改 setTaskAffinity 调用的参数可以改变任务的规则。
在实际开发中,我们可以利用Flag参数控制Activity的启动方式,很多场景下需要Activity跨进程启动,这时可以使用
FLAG_ACTIVITY_NEW_TASK
。另外,我们也可以通过设置taskAffinity改变Activity所在的任务栈,比如通过设置taskAffinity="com.example.myApp.task1"
将应用程序中的某些Activity归属到同一个任务栈中,从而方便管理。
activity启动流程
在理解Activity的启动流程之前,我们需要先了解以下几个概念:
- Context:表示Android应用程序环境,比如Activity、Service等。
- Application:全局应用程序上下文,一个应用程序只有一个Application实例。
- ActivityManagerService(AMS):负责协调应用程序中所有组件的运行情况的系统服务。
- 启动Activity
Activity的启动过程通常是通过Context.startActivity()方法触发的。当一个Activity要启动时,就会发送一个启动请求给AMS。
- 请求验证和分配task
AMS接收到请求后,首先会验证并分配一个Task给新的Activity实例,这个过程会根据Intent中带的标志进行判断,一般情况下系统会为新的Activity实例创建一个新的Task,并将它放入最近使用的任务列表的顶端;而如果同时在Intent中设置了FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TOP标志,那么AMS就会找到与该Activity具有相同taskAffinity属性的Activity所在的任务栈。
- 发送生命周期回调
AMS会调用Activity的onCreate()、onStart()和onResume()方法,这些方法会导致Activity进入运行状态。然后AMS把Activity添加到栈顶并更新屏幕显示。
- AMS销毁不活跃的Activity
当新的Activity启动后,AMS会对任务栈中其它Activity进行处理。首先回调onPause,等到返回屏幕再去调度其他任务栈。如果他影藏了当前Activity,我们的这个Activity也会被调整到后台并停止响应,Activity渲染的数据会被保存在当前Activity里面。当用户再次打开这个Activity时从数据中读取数据,在屏幕,AMS仅仅涉及到该Activity是否可见,如果不可见就会立即调用onStop()。在后台运行的非常久远的任务栈都可能被AMS kill、清除的。
- 销毁Activity
一般来说,Activity的销毁过程如下:
onPause()->onStop()->onDestroy()。
关于Hook实现启动未注册Activity
假设我们有一个名为TargetActivity
的Activity,并希望在我们的应用程序中动态启动它,但是没有对其进行注册。我们可以使用Hook来实现这一点。具体步骤如下:
1.创建一个代理Activity,例如ProxyActivity
,并将其添加到清单文件中。 在此步骤中,请注意将标签设置为“主要类别”,以便让系统认为它是应用程序的入口点。 以下是示例代码:
<activity android:name=".ProxyActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="app" />
</intent-filter>
</activity>
2.在ProxyActivity
中,使用反射获取ActivityThread
对象,并通过它获取IActivityManager
对象。
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
Object activityThread = currentActivityThreadMethod.invoke(null);
Field iActivityManagerField = activityThreadClass.getDeclaredField("mInstrumentation");
iActivityManagerField.setAccessible(true);
Object iActivityManager = iActivityManagerField.get(activityThread);
3.创建一个假的Intent
,将目标Activity的名称作为参数传递。
Intent intent = new Intent();
intent.setComponent(new ComponentName(getPackageName(), "com.example.TargetActivity"));
4.调用IActivityManager
对象的startActivity方法,以启动指定的Activity。 在调用该方法之前,我们需要创建一个回调PendingIntent
,并将其作为“intentSender”参数传递给该方法。
// Create a Pending Intent for the callback.
Intent callbackIntent = new Intent(this, ProxyActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, requestCode, callbackIntent, PendingIntent.FLAG_ONE_SHOT);
// Start the Target Activity using IActivityManager.
Method startActivityMethod = iActivityManager.getClass().getDeclaredMethod("startActivity", IApplicationThread.class, String.class, Intent.class, String.class, IBinder.class, String.class, int.class, int.class, ProfilerInfo.class, Bundle.class);
startActivityMethod.setAccessible(true);
startActivityMethod.invoke(iActivityManager, null, null, targetIntent, null, null, null, 0, 0, null, null);
在这个示例中,我们假装从ProxyActivity
启动了TargetActivity
,而不是直接从应用程序启动。 这允许我们绕过您必须对所有要启动的Activity进行注册的限制。
Activity的生命周期可分为以下四个状态:
- Running - Activity位于栈顶,正在与用户进行交互。
- Paused - 当前Activity失去了输入焦点,不再接收用户输入,但仍可显示在屏幕上。或者被另一个 Activity 部分遮挡而失去焦点时被调用。
- Stopped - Activity不再可见,已经被另一个Activity完全覆盖了。
- Destroyed - Activity已经被销毁,内存资源已释放。
Activity生命周期方法
以下是Activity生命周期中的主要方法:
onCreate()
- 在该方法中完成必要的初始化操作,如加载布局、绑定事件等。onStart()
- 在该方法中进行Activity的启动操作,如请求网络、打开数据库等。onResume()
- 在该方法中完成Activity的恢复操作,如恢复播放音乐、重新开始动画等。onPause()
- 在该方法中处理失去用户焦点的情况,如保存当前编辑的内容、暂停视频观看等。onStop()
- 在该方法中完成Activity的停止操作,如释放资源,取消注册广播等。onDestroy()
- 在该方法中释放系统资源、取消注册监听器等。
Android中的Activity 生命周期回调顺序
当用户打开一个 Activity 时,Activity 进入 Created 状态并按照以下顺序调用:
onCreate()
- 在活动第一次创建时被调用。onStart()
- 当活动对用户可见时被调用。在这个阶段,它已经获得了焦点。onResume()
- 当活动与用户开始交互时被调用。
如果在此期间用户暂停了 Activity,则该 Activity 将进入 Paused
状态并依次调用:
onPause()
- 当前活动对用户不再可见时调用。当他只是部分可见或透明时,也可能发生这种情况。在执行下一个活动或返回到主屏幕时,系统将继续调用“ onStop()”或onRestart()
方法。onStop()
- 在 Activity 完全被另一个 Activity 覆盖或者销毁时被调用。在在手机上或平板电脑等小型设备上可以忽略该方法。onDestroy()
- 活动从堆栈中弹出时调用。
如果用户想要恢复之前的 Activity,则将按照以下顺序调用它们:
onRestart()
- Called after your activity has been stopped, prior to it being started again. 目的是当活动正在重新启动时来重新初始化某些变量。onStart()
- 与刚刚创建 Activity 时调用的方式相同。onResume()
- 与刚刚创建 Activity 时调用的方式相同。
前台activity和后台activity
-
当前台 Activity 不再可见(如因为新 Activity 启动),系统便会调用 onPause(), 确保 Activity 的数据已经保存。
-
如果用户返回当前 Activity,系统便会调用 onResume(), 允许它重新开始。
-
处理周期性任务、定时器、更新 UI 或执行其他操作,请使用
onResume()
和onPause()
方法。 -
对于较大的内存块,请使用
onStop()
方法保存更改。如果您发现需要立即释放资源且没有延迟容忍的时间,请使用onPause()
代替。 -
如果 Activity 刚退出前台并即将进入后台,可以利用 onSaveInstanceState() 方法在其开始 onSaveInstanceState() 时进行某些处理,持久化 Activity 的图像等重要数据。.onOptionsItemSelected() 是另一个选择。
activity中adj
在Android中,每个应用程序的进程都具有一个adj(oom_adj值),该值决定了系统如何对该应用程序进行内存管理。 adj值越高,表明应用程序是最有可能被终止的。Activity中包含的组件和服务也会继承相同的adj值。
默认情况下,所有应用程序都分配了较低的adj值,这意味着它们占用的内存不够时最有可能被kill。为了防止这种情况发生,并保持应用程序在前台运行,可以调整或设置其adj值。
要设置Activity的ad,则需要使用android:process
属性以及android:priority
属性。具体如下:
<application android:process=":my_process">
<activity android:name=".MyActivity"
android:priority="1000">
</activity>
</application>
上述代码将创建一个新进程(my_process),并将Activity的优先级设置为1000。请注意,使用此选项应该非常小心,因为如果您设置的优先级太高,可能会影响系统的整体性能。
另外,在API 21及更高版本中,您还可以使用TaskDescription类的setTaskDescription方法来设置应用程序堆栈的描述。这反过来会影响系统如何处理应用程序的内存管理。例如:
ActivityManager.TaskDescription description = new ActivityManager.TaskDescription("Label",
BitmapFactory.decodeResource(getResources(), R.drawable.icon),
ContextCompat.getColor(this, R.color.colorPrimary));
setTaskDescription(description);
通过使用setTaskDescription()方法,您可以更改堆栈的标签、图标和颜色,使其在Recents列表中更加易于区分和查看,并帮助您控制应用程序的adj值。