这里先说一下操作流程,依次启动ActivityX,ActivityY,Activity1,Activity2;ActivityY,ActivityX(这两个都是SingleTask)在后台任务中,Activity2,Activity1在前台任务中,这两个任务的taskAffinity不同,当从Activity2中启动ActivityY的时候,返回栈如第二列所示,然后点击返回键可以一个个退出。
再普及一个概念在 Android 里,每个 Activity 都有一个 taskAffinity,它就相当于是对每个 Activity 预先进行的分组。它的值默认取自它所在的 Application 的 taskAffinity,而 Application 的 taskAffinity 默认是 App 的包名。当然也可以手动指定taskAffinity。
但是图中并没有指明Activity2,Activity1是什么启动模式,实际上我如果我们指定为standard标准模式根本模拟不出这个场景,这一点有点坑,因为这四个Activity分别按2,1,X,Y排列,也即是说启动是从Y,X,1,2一个个启动的,如果Activity1为standard,就算你指定了Activity1的taskAffinity和ActivityY的不同也没有用,Activity1还是会和ActivityY在同一个任务(TaskRecord)中,也就是说standard 和 singleTop 的 Activity 在哪个 TaskRecord 启动,全凭启动它的 Activity 在哪个 TaskRecord,taskAffinity在同时指定为singleTask模式下才有意义(只有一种例外,standard 和 singleTop在 allowTaskReparenting 为 true,且被其他应用以 DeepLink 的方式唤起时,才会在指定的任务中)。
所以我们将Activity2,Activity1也设置为singleTask,同时taskAffinity也相同,才会模拟出上面的场景,点击Activity2启动ActivityY,才会将后台任务栈ActivityY,ActivityX都带到前台任务栈中,也就是都带到返回栈中。
小结
任务就是任务栈(TaskRecord是栈结构),TaskRecord内部维护了一个ArrayList<ActivityRecord>
用来保存和管理ActivityRecord,ActivityRecord包含了一个Activity的所有信息。
一个返回栈可能只包含一个任务,但特殊情况下,可能引入多个任务。返回栈,前台任务栈,后台任务栈其实在源码中并没有明确的定义,而是在我们操作任务栈过程中提出的一些“概念”,为了便于描述和区分。
前台栈比如现在下图A中的Activity2,Activity1所在的任务,后台任务栈是ActivityY,ActivityX所在的任务。
但是问题来了,当Activity2启动ActivityY的时候,返回栈中的内容如下图B所示,这个时候前台任务栈是什么呢?
这个时候后台的任务栈(ActivityY,ActivityX)已经返回到前台,四个Activity都在前台,此时返回堆栈中包含了转到前台任务中的所有Activity(这句话来自官网对这一场景的说明)。
问题又来了,比如我们前面说的后台任务栈是在后台等待恢复(比如ActivityX,ActivityY所在的栈),依次启动ActivityX,ActivityY,Activity1,Activity2,如果你这个时候什么都不做,不断点击返回键,这四个Activity会一个个退出,这个时候你会不会觉得返回栈包含前台任务栈和后台任务栈。但是一开始图A中返回栈(Back Stack)只标明了Activity1,Activity2,这就出现矛盾了,但我的感觉返回栈就是字面上的含义,点击返回键,能退出多少个Activity,那么这些Activity就都在返回栈中,返回栈就是一个概念,当然你也可以理解它的大小动态变化的(点击返回键的过程中可能大小可能新增)。
4.startActivityForResult导致的一系列问题?
在使用Activity的startActivityForResult启动新界面时,在Api20以下调整时会直接返回Activity.RESULT_CANCELED
,官方觉得不应该在两个任务之间setResult。在Api20及以上,对于非startActivity跳转,也就是reqeusetCode>=0,singleTask和SingleInstance模式启动的Activity都不会新建一个任务,还是在原来的栈中。同时官方也建议:
虽然所有 API 级别的 Activity
类均提供底层 [startActivityForResult()
](developer.android.com/reference/a…, int)) 和 [onActivityResult()
](developer.android.com/reference/a…, int, android.content.Intent)) API,但我们强烈建议您使用 AndroidX Activity 1.2.0-alpha02
和 Fragment 1.3.0-alpha02
中引入的 Activity Result API。
5.清除返回栈(Clearing the back stack)的一些概念
如果用户离开任务较长时间,系统会清除任务中除根 Activity 以外的所有 Activity。当用户再次返回到该任务时,只有根 Activity 会恢复。系统之所以采取这种行为方式是因为,经过一段时间后,用户可能已经放弃了之前执行的操作,现在返回任务是为了开始某项新的操作。
您可以使用一些 Activity 属性来修改此行为:
alwaysRetainTaskState
如果在任务的根 Activity 中将该属性设为 "true"
,则不会发生上述默认行为。即使经过很长一段时间后,任务仍会在其堆栈中保留所有 Activity。
clearTaskOnLaunch
如果在任务的根 Activity 中将该属性设为 "true"
,那么只要用户离开任务再返回,堆栈就会被清除到只剩根 Activity。也就是说,它与 alwaysRetainTaskState
正好相反。用户始终会返回到任务的初始状态,即便只是短暂离开任务也是如此。
finishOnTaskLaunch
该属性与 clearTaskOnLaunch
类似,但它只会作用于单个 Activity 而非整个任务。它还可导致任何 Activity 消失,包括根 Activity。如果将该属性设为 "true"
,则 Activity 仅在当前会话中归属于任务。如果用户离开任务再返回,则该任务将不再存在。
6.allowTaskReparenting的使用
Activity 默认情况下只会归属于一个 Task,不会在多个 Task 之间跳来跳去,但你可以通过设置来改变这个逻辑。把它的 allowTaskReparenting 属性设置为 true。如果未设置该属性,则由 <Activity>
元素的相应 allowTaskReparenting
属性所设置的值。默认值为“false
”。
正常情况下,Activity 启动时会与启动它的任务关联,并在其整个生命周期中一直留在该任务处。当不再显示现有任务时,您可以使用该属性强制 Activity 将其父项更改为与其有相似性的任务。该属性通常用于将应用的 Activity 转移至与该应用关联的主任务。
例如,如果电子邮件消息包含网页链接,则点击该链接会调出可显示该网页的 Activity。该 Activity 由浏览器应用定义,但作为电子邮件任务的一部分启动。如果将该 Activity 的父项更改为浏览器任务,则它会在浏览器下一次转至前台时显示,在电子邮件任务再次转至前台时消失。
7.Activity的隐式启动
Activity分为显示启动和隐式启动,显示启动就是我们平时调用的一些startActivityXXX()
方法,隐式启动可以通过action来启动,启动时调用如下,同时要记得添加category为"android.intent.category.DEFAULT"
。
Intent implicitIntent = new Intent();
implicitIntent.setAction(“com.test.image”);
implicitIntent.addCategory(“android.intent.category.DEFAULT”);
MainActivity.this.startActivity(implicitIntent);
具体界面的配置如下:
注意如果是其他App的Activity,需要添加android:exported="true"才能被调用。
8.Activity的启动流程(中级问题)
对很多开发者来说,这可能都是个很沉重的问题,原因很简单,因为回答不好,毕竟里面涉及到的东西很多,需要你拥有很大知识存储量。下面来尝试回答这个问题(基于源码9.0)
首先先普及一些常见的概念
Instrumentation
Android Instrumentation是Android系统中的一套控制方法或者“钩子”,这些钩子可以在正常的生命周期(正常是由操作系统控制的)之外控制Android控件的运行,其实指的就是Instrumentation类提供的各种流程控制方法。
app->instrumentation->ams->app
,自动化测试可以通过Instrumentation来操作Activity等,这个Instrumentation相当于设计了一个统一的入口。
ActivityThread
ActivityThread不是线程类(Thread),只不过它会跑在ActivityThread.main()
方法中,安卓程序的入口就是该方法,同时在该方法中一个Looper不断循环的在消息队列中处理消息。管理应用程序进程中主线程的执行,根据Activity管理者的请求调度和执行activities、broadcasts及其相关的操作。
public static void main(String[] args) {
// 看源码很重要的一个能力就是‘眼中只有你’,认不到的都忽略,看认得到的
···
// 创建主线程的Looper对象,发现和工作线程创建Looper对象调用的方法不一样,这里先记下,以后在详解。
// 主线程原来也有Looper对象啊
Looper.prepareMainLooper();
//创建ActivityThread
ActivityThread thread = new ActivityThread();
thread.attach(false);
// 如果主线程的Handler为空(可以看出,一个好的命名可读性是多么高),那就为主线程创建一个Handler。
// 然后我们还可以在主线程创建Handler,说明一个线程对应多个Handler。多读源码,很多问题都得到了解决啊。
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
// 这里抛了个异常,主线程loop异常退出。说明主线程loop不能退出,这里和前面建立Looper对象的调用方法有关
throw new RuntimeException(“Main thread loop unexpectedly exited”);
}
ActivityManagerService
Android中最核心的服务,主要负责系统中四大组件的启动、切换、调度及应用程序的管理和调度等工作。
ActivityManager
该类提供与Activity、Service和Process相关的信息以及交互方法, 可以被看作是ActivityManagerService的辅助类。
ActivityStackSupervisor
负责所有Activity栈的管理。内部管理了mHomeStack、mFocusedStack和mLastFocusedStack三个Activity栈。其中,mHomeStack管理的是Launcher相关的Activity栈;mFocusedStack管理的是当前显示在前台Activity的Activity栈;mLastFocusedStack管理的是上一次显示在前台Activity的Activity栈。下面是大致的关系图,对于没有分屏功能以及虚拟屏的情况下,ActivityStackSupervisor与ActivityDisplay都是系统唯一。
ActivityStack
ActivityStack负责“Activity栈”的状态和管理,ActivityStack内部包含了多个任务栈(TaskRecord),TaskRecord内部维护了一个ArrayList<ActivityRecord>
用来保存和管理ActivityRecord,ActivityRecord包含了一个Activity的所有信息
如果我们从桌面点击启动app,桌面就是一个Activity,点击app(按钮)启动我们的启动页Activity,从这里分析Activity的启动流程更加全面,而不是在app中去启动一个普通的Activity。可以分为如下几个流程
- Launcher通知AMS启动App的启动页Activity,AMS记录要启动的Activity信息,并且通知Launcher进入pause状态。
Launcher进入pause状态后,通知AMS已经paused了,可以启动App了
-
如果App未开启过,AMS发送创建进程请求,Zogyte进程接受AMS请求并孵化应用进程,应用进程调用ActivityThread并调用mian()方法,并且main()方法中创建ActivityThread对象,
activityThread.attach()
方法中进行绑定(应用进程绑定到AMS),传入applicationThread以便通讯。 -
AMS通知App绑定Application(bindApplication)并启动Activity,并且创建和关联Context,最后调用onCreate等方法。
灵魂拷问:AMS,Zogyte,App进程,Launcher如何通信?
这个问题一旦问出来,能干翻一大堆开发人员,下面来仔细讲讲:
App进程和AMS是如何通信的?
Zogyte去fork一个App进程,后面就是应用进程和AMS两者的事情了,我们知道Android的跨进程通信是通过Binder服务的,AMS所在的进程和应用进程在通过Binder互相通信时,实际上都是通过两者的代理类进行通信的。
ActivityManagerService(AMS)在手机开机后时就已经启动了,应用进程去调用AMS的方法,比如startActivity,很容易调用,因为AMS是一个有名称的Binder服务,在任意地方都可以通过在ServiceManger(SM)里面查询拿到代理类,调用代理类的对应方法,然后再去调用AMS的真正方法。
因为Binder通信是通过代理类来通信的,如果拿不到代理类,其他进程就不知道如何和我们的App通信,系统服务中的AMS也就不知道如何和我们App通信了,所以当App进程创建完成后,会进行设置代理,代理的设置过程如图
就是在ActivityThread.attach(false)
方法中,AMS绑定ApplicationThread对象,即应用进程绑定到AMS,通过调用AMS的attachApplication来将ActivityThread的内部类ApplicationThread对象绑定至AMS,这样AMS就可以通过这个代理对象来控制应用进程。
AMS和Launcher是怎么通信的?
其实Launcher也是一个App,调用startActivity方法,然后调用的是Instrumentation的execStartActivity方法
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
…
try {
…
//获取AMS的代理对象
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException(“Failure from system”, e);
}
return null;
}
在这个方法会调用ActivityManager的getService方法来得到AMS的代理对象,然后调用这个代理对象的startActivity方法
@UnsupportedAppUsage
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
@UnsupportedAppUsage
private static final Singleton IActivityManagerSingleton =
new Singleton() {
@Override
protected IActivityManager create() {
//得到activity的service引用,即IBinder类型的AMS引用
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
//转换成IActivityManager对象
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
可以发现在Singleton中的create方法中由于b是AMS引用作为服务端处于SystemServer进程中,与当前Launcher进程作为客户端与服务端不在同一个进程,所以am返回的是IActivityManager.Stub的代理对象,此时如果要实现客户端与服务端进程间的通信,只需要在AMS继承了IActivityManager.Stub类并实现了相应的方法,而通过下面的代码可以发现AMS刚好是继承了IActivityManager.Stub类的,这样Launcher进程作为客户端就拥有了服务端AMS的代理对象,然后就可以调用AMS的方法来实现具体功能了,就这样Launcher的工作就交给AMS实现了。
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
}
Zygote和AMS是如何通信的?
AMS和Zygote建立Socket连接,然后发送创建应用进程的请求。具体可以参考这里。
最后我们再来看看流程图,看下方的App进程启动过程和Activity.startActivity
这两个流程
这里还要提到一点,Hook Activity的启动流程是一个很重要的运用场景,我们需要欺骗AMS,然后启动真正的TargetActivity,Hook有起始点和终点。这里需要寻找两个地方的hook点,一个是对Intent中Activity的替换(hookIActivityTaskManager方法),一个是对Intent中Activity的还原(hookHandler)。
在回答Activity的启动流程时,具体的方法如何调用并不重要,所以我才会在最后放出整个流程,各个进程之间如何建立通信,如何通信很重要,同时一些Activity相关概念也很重要,熟悉这些,你就很容易把整个流程串起来了。
Activity深层次问题
1.Activity生命周期的变化对进程的优先级有什么影响?
这里先看一下官网上Activity生命周期上对onStart的一段描述,onStart时候Activity就对用户可见了
同时你也可以在《Android开发艺术探索》上看到类似的描述
但是了解Activity启动流程源码的朋友都知道,ActivityThread的handleResumeActivity方法中,首先调用Activity的onResume方法,接着会调用Activity.makeVisible()在该方法中,DecorView真正完成了添加和显示这两个过程,到这里Activity的视图才能被看到。DecoreView和Window进行关联。有兴趣可以看看我这篇文章的分析。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
//DecoreView和WindowManager进行关联。
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
//设置DecorView可见
mDecor.setVisibility(View.VISIBLE);
}
也就是说在onResume方法执行之后再调用Activity.makeVisible()方法,我们才能真正用肉眼看到我们的DecoreView,看到这里你这里不禁会产生一个疑问,那上面官网上的说法(onStart()
调用使 Activity 对用户可见)难道是错误的吗?
带着疑问我们继续在官网上找答案,在进程和生命周期这一章节上可以看到:
为了确定在内存不足时应该终止哪些进程,Android 会根据每个进程中运行的组件以及这些组件的状态,将它们放入“重要性层次结构”。这些进程类型包括(按重要性排序):
- 前台进程是用户目前执行操作所需的进程。在不同的情况下,进程可能会因为其所包含的各种应用组件而被视为前台进程。如果以下任一条件成立,则进程会被认为位于前台:
- 它正在用户的互动屏幕上运行一个
Activity
(其onResume()
方法已被调用)。 - 它有一个
BroadcastReceiver
目前正在运行(其BroadcastReceiver.onReceive()
方法正在执行)。 - 它有一个
Service
目前正在执行其某个回调(Service.onCreate()
、Service.onStart()
或Service.onDestroy()
)中的代码。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
学习分享
①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包——————可以点击我的【Github】免费下载,最后觉得有帮助、有需要的朋友可以点个赞
)]
学习分享
①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包——————可以点击我的【Github】免费下载,最后觉得有帮助、有需要的朋友可以点个赞
[外链图片转存中…(img-SNJwBllf-1710494320222)]
[外链图片转存中…(img-Aqip4nWT-1710494320222)]
[外链图片转存中…(img-Mw2Cf0UQ-1710494320222)]