Activity的初级,中级,高级问法,Android体系化进阶学习图谱

这里先说一下操作流程,依次启动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-alpha02Fragment 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。可以分为如下几个流程

  1. Launcher通知AMS启动App的启动页Activity,AMS记录要启动的Activity信息,并且通知Launcher进入pause状态。

Launcher进入pause状态后,通知AMS已经paused了,可以启动App了

  1. 如果App未开启过,AMS发送创建进程请求,Zogyte进程接受AMS请求并孵化应用进程,应用进程调用ActivityThread并调用mian()方法,并且main()方法中创建ActivityThread对象,activityThread.attach()方法中进行绑定(应用进程绑定到AMS),传入applicationThread以便通讯。

  2. 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 会根据每个进程中运行的组件以及这些组件的状态,将它们放入“重要性层次结构”。这些进程类型包括(按重要性排序):

  1. 前台进程是用户目前执行操作所需的进程。在不同的情况下,进程可能会因为其所包含的各种应用组件而被视为前台进程。如果以下任一条件成立,则进程会被认为位于前台:
  • 它正在用户的互动屏幕上运行一个 Activity(其 onResume() 方法已被调用)。
  • 它有一个 BroadcastReceiver 目前正在运行(其 BroadcastReceiver.onReceive() 方法正在执行)。
  • 它有一个 Service 目前正在执行其某个回调(Service.onCreate()Service.onStart()Service.onDestroy())中的代码。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

学习分享

①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包——————可以点击我的【Github】免费下载,最后觉得有帮助、有需要的朋友可以点个赞

)]

学习分享

①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包——————可以点击我的【Github】免费下载,最后觉得有帮助、有需要的朋友可以点个赞

[外链图片转存中…(img-SNJwBllf-1710494320222)]

[外链图片转存中…(img-Aqip4nWT-1710494320222)]

[外链图片转存中…(img-Mw2Cf0UQ-1710494320222)]

  • 25
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值