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加入ActivityStacktask中。

5.Zygote启动Application

  如果说AMS是Android应用启动的大管家,那么Zygote就是应用启动的魔法师。当Zygote进程创建好了Activity进程,并已经给AMS抛出了Activity生命周期的事件之后,AMSi即可调用Zygote方法去“打开”这个应用程序。Zygote取得类路径和参数信息,并解析出主函数所对应的类名和方法名,最终通过反射技术,加载指定文件并运行里面的静态main()方法。

AMS重要数据结构解析

  1. ProcessRecord:进程记录是AMS管理应用程序的最底层数据结构之一,它维护了一个应用程序的所有信息,包括包名、UID、进程名、用户ID等。每个进程记录都与一个ApplicationRecord关联,表示该进程所对应的应用程序。

  2. ActivityRecord:指Activity在AMS内部的表示,其本质是一个封装了Activity对象和相关状态信息的类。AMS通过ActivityRecord来管理Activity组件的创建、启动、停止等操作。

  3. TaskRecord:指任务栈记录,又称作任务记录。Android应用程序通常由多个Activity组成,并按照一定的调用顺序形成不同的任务栈。每个TaskRecord记录了一个任务栈中所有Activity的顺序以及运行状态。

  4. IntentRecord:指Intent请求记录,也称Activity请求记录,AMS通过此数据结构来存储Activity启动请求,例如Intent的Action、Category、Data等信息,在后续启动过程中进行匹配以确定启动哪个Activity。

在Android实际应用开发中,开发者可以利用AMS提供的API来实现各种复杂的逻辑。例如:

  1. 跨进程通信:借助Binder机制和AMS的辅助,在不同进程之间传递消息或执行远程方法调用,实现跨应用或服务端/客户端的各种功能。

  2. 启动模式管理:通过设置不同的Activity启动模式,灵活管理Activity的启动行为,例如SingleTop模式避免Activity重复创建,FlagActivity模式实现不同自定义标记的特殊行为等。

  • standard :系统的默认模式,一次跳转即会生成一个新的实例。假设有一个activity命名为MainActivity,执行语句:
    startActivity(new Intent(MainActivity.this, MainActivity.class))后,MainActivity将跳转到另外一个MainActivity,也就是现在的Task栈里面有MainActivity的两个实例。 按返回键后你会发现仍然是在MainActivity(第一个)里面。 显示不同列表信息;

  • singleTopsingleTopstandard 模式比较类似。如果已经有一个实例位于Activity栈的顶部时,就不产生新的实例,而只是调用Activity中的newInstance()方法。如果不位于栈顶,会产生一个新的实例。例:当MainActivitysingleTop 模式时,执行跳转后栈里面依旧只有一个实例,如果现在按返回键程序将直接退出。 通知或即时响应界面;

    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里。比如系统默认电话界面。

  1. 进程垃圾清理:合理使用AMS提供的Process.killProcess等API,及时关闭无用的进程,释放存储空间和CPU资源。

ActivityStack 不是一个具体的物理存在,而是Android系统在内存中维护的一个数据结构。它表示所有已启动的 Activity 的集合以及它们在屏幕上的顺序。

可以通过 ActivityThread 类的 getApplication() & getActivities() 方法来获取应用程序中所有Activity实例相关的信息。其中,getActivities() 方法会返回一个 ArrayMap ,Key 值为 IBinder 对象(通过此对象可以与每个 Activity 交互),Value 值为 WeakReference 对象(弱引用指向了相应的 Activity 实例)。

lntent的Flag与taskAffinity

Activity启动流程大致分为以下几个步骤:

  1. 当需要启动一个新的Activity时,会向AMS发送启动请求。

  2. AMS会根据该请求创建一个WaittingRecord并将其添加到待处理队列中。

  3. AMS会检查当前进程的可用性,如果存在一个运行在该进程中的Activity,则该Activity所在的进程会直接被使用;否则,就需要启动一个新的进程来承载即将启动的Activity。

  4. 如果需要启动一个新进程,则AMS会利用Zygote fork出一个新进程,并启动该进程的主线程。

  5. 在新的进程中,会创建一个Application Object和ActivityThread对象,并把ActivityThread添加到主线程的消息循环中。

  6. ActivityThread会依次完成Looper、AMS Binding、Handler回调等步骤。

  7. 通过AMS获取ActivityInfo和TaskAffinity,然后再根据Intent中相关配置启动Activity,并使用Token进一步关联Activity和进程之间的交互。

至于TaskAffinityFlag,其中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_TASKFLAG_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):负责协调应用程序中所有组件的运行情况的系统服务。
  1. 启动Activity

Activity的启动过程通常是通过Context.startActivity()方法触发的。当一个Activity要启动时,就会发送一个启动请求给AMS。

  1. 请求验证和分配task

AMS接收到请求后,首先会验证并分配一个Task给新的Activity实例,这个过程会根据Intent中带的标志进行判断,一般情况下系统会为新的Activity实例创建一个新的Task,并将它放入最近使用的任务列表的顶端;而如果同时在Intent中设置了FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TOP标志,那么AMS就会找到与该Activity具有相同taskAffinity属性的Activity所在的任务栈。

  1. 发送生命周期回调

AMS会调用Activity的onCreate()、onStart()和onResume()方法,这些方法会导致Activity进入运行状态。然后AMS把Activity添加到栈顶并更新屏幕显示。

  1. AMS销毁不活跃的Activity

当新的Activity启动后,AMS会对任务栈中其它Activity进行处理。首先回调onPause,等到返回屏幕再去调度其他任务栈。如果他影藏了当前Activity,我们的这个Activity也会被调整到后台并停止响应,Activity渲染的数据会被保存在当前Activity里面。当用户再次打开这个Activity时从数据中读取数据,在屏幕,AMS仅仅涉及到该Activity是否可见,如果不可见就会立即调用onStop()。在后台运行的非常久远的任务栈都可能被AMS kill、清除的。

  1. 销毁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的生命周期可分为以下四个状态:

  1. Running - Activity位于栈顶,正在与用户进行交互。
  2. Paused - 当前Activity失去了输入焦点,不再接收用户输入,但仍可显示在屏幕上。或者被另一个 Activity 部分遮挡而失去焦点时被调用。
  3. Stopped - Activity不再可见,已经被另一个Activity完全覆盖了。
  4. Destroyed - Activity已经被销毁,内存资源已释放。

Activity生命周期方法

以下是Activity生命周期中的主要方法:

  1. onCreate() - 在该方法中完成必要的初始化操作,如加载布局、绑定事件等。
  2. onStart() - 在该方法中进行Activity的启动操作,如请求网络、打开数据库等。
  3. onResume() - 在该方法中完成Activity的恢复操作,如恢复播放音乐、重新开始动画等。
  4. onPause() - 在该方法中处理失去用户焦点的情况,如保存当前编辑的内容、暂停视频观看等。
  5. onStop() - 在该方法中完成Activity的停止操作,如释放资源,取消注册广播等。
  6. onDestroy() - 在该方法中释放系统资源、取消注册监听器等。

Android中的Activity 生命周期回调顺序

当用户打开一个 Activity 时,Activity 进入 Created 状态并按照以下顺序调用:

  1. onCreate() - 在活动第一次创建时被调用。
  2. onStart() - 当活动对用户可见时被调用。在这个阶段,它已经获得了焦点。
  3. onResume() - 当活动与用户开始交互时被调用。

如果在此期间用户暂停了 Activity,则该 Activity 将进入 Paused 状态并依次调用:

  1. onPause() - 当前活动对用户不再可见时调用。当他只是部分可见或透明时,也可能发生这种情况。在执行下一个活动或返回到主屏幕时,系统将继续调用“ onStop()”或onRestart()方法。
  2. onStop() - 在 Activity 完全被另一个 Activity 覆盖或者销毁时被调用。在在手机上或平板电脑等小型设备上可以忽略该方法。
  3. onDestroy() - 活动从堆栈中弹出时调用。

如果用户想要恢复之前的 Activity,则将按照以下顺序调用它们:

  1. onRestart() - Called after your activity has been stopped, prior to it being started again. 目的是当活动正在重新启动时来重新初始化某些变量。
  2. onStart() - 与刚刚创建 Activity 时调用的方式相同。
  3. 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值。

posted @ 2023-03-22 22:25  懒懒初阳  阅读(297)  评论(0编辑  收藏  举报