面试题十七:Activity的启动流程是怎样的?

Activity的启动流程是怎样的?

一、面试官视角:这道题想考察什么?

  • 是否熟悉Activity启动过程中与AMS(Activity Manager Service)的交互过程
  • 是否熟悉Binder通信机制
  • 是否了解插件化框架如何Hook Activity启动
  • 阐述Activity转场动画的实现原理
  • 阐述Activity的窗口显示流程

二、题目剖析

0、Activity跨进程启动

跨进程启动流程:

在【请求进程A】中调用startActivity,通过AMP启动Activity,AMP指的是AMS在通信过程中在客户端的一个代理,我们通过AMP来调用AMS一些方法来启动Activity,启动Activity的过程当中,会在【system_server进程】中解析Activity信息、处理启动参数、启动目标进程、绑定新进程,而启动目标进程是通过Zygote,直接Fork出来一个进程,在Android里面所有的进程都是通过Zygote Fork出来的,这样的话我们就可以共享到一些预加载的资源等,启动速度也会变得更快。然后通过调用ATP(ATP指的是ApplicationThread在system_server端的一个代理)的scheduleLaunchActivity,本质上是调用的ApplicationThread的方法,进而在【目标进程B】中去启动Activity。

以上就是跨进程启动Activity的流程,比如通过Launcher来启动自己的进程就是这么干的,Launcher在里面调用startActivity,你点桌面icon的时候就会触发这个流程。

1、Activity进程内启动

在【请求进程A】中调用startActivity,通过AMP启动Activity,AMP指的是AMS在通信过程中在客户端的一个代理,我们通过AMP来调用AMS一些方法来启动Activity,启动Activity的过程当中,会在【system_server进程】中解析Activity信息、处理启动参数,然后通过ATP(ATP指的是ApplicationThread在system_server端的一个代理)的scheduleLaunchActivity,本质上是调用的ApplicationThread的方法,进而在【请求进程A】中去启动Activity。

2、与AMS如何交互

通过AMP,AMP指的是AMS在通信过程中在客户端的一个代理,我们通过AMP来调用AMS一些方法来启动Activity。

3、Activity的参数和结果如何传递

启动Activity的时候,有跨进程的调用,那么跨进程的调用是通过Binder来实现通信的,Binder本身有一个缓冲区,但这个缓冲区有大小的限制,如果你启动Activity的时候,携带数据过大的话会有问题的,而且数据必须可以序列化。

数据过大的话,如何传递呢?

如果能确保Activity在同一进程当中,使用Model进行数据共享,首先在Activity A将数据设置到Model中,然后从Activity A传递一个key到Activity B就可以了,然后在Activity B中通过key去Model里面读取数据即可,这样的话,也可以实现数据与页面的解耦。

如果Activity跨进程的话,就需要实现可进程间通信,这个Model可以是一个进程间可以访问的,比如使用ContentProvider来访问。

4、Activity如何实例化

1
2
3
4
5
6
7
Instrumentation.java源码:

public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
String pkg = intent != null && intent.getComponent() != null?intent.getComponent().getPackageName():null;
// 实例化Activity
return getFactory(pkg).instantiateActivity(cl, className, intent);
}
1
2
3
4
5
6
7
instantiate()源码实现:

public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className, @Nullable Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
// 反射
// newInstance能够调用的话,必须要有一个默认的无参的构造方法,这就是为什么我们写Activity的时候,从来没有写过构造方法的原因,如果你写了,Activity压根就无法实例化,同时Idea也会提示你不能这么写,因为这样会导致Activity无法启动。
return (Activity)cl.loadClass(className).newInstance();
}

5、Activity、Fragment为什么不能添加有参数的构造方法?

这就涉及到Activity状态保存和恢复,比如从ActivityA到ActivityB,然后又从ActivityB回到ActivityA,之前讲匿名内部类也是这种场景,其实同样的场景下还涉及到一个Activity状态如何保存和恢复的问题!状态的保存和恢复有个重要的点就是FragmentManager,它会在ActivityA被销毁的时候,也就是ActivityA要保存状态的时候,把ActivityA里面持有的Fragment保存到一个叫android:fragments这么一个key的数据里面,等到ActivityA要恢复的时候再把它恢复出来,那Activity和Fragment如何恢复呢?还不就是通过上面所说的类似调用newInstance()方法进行恢复,要想成功调用newInstance()方法,就必须要有一个默认的无参的构造方法,所以这就是为什么不能添加有参数的构造方法的原因所在,就算你写了有参的构造方法,也要提供一个默认的无参的构造方法。

5、Activity生命周期如何流转

ActivityThread - handleLaunchActivity:

  • newActivity

  • activity - attach

  • activity - create

  • activity - start

  • activity - restoreState

  • activity - postCreate

  • activity - resume

  • activity - makeVisible

6、Activity的窗口如何展示

ActivityThread - handleLaunchActivity:

  • newActivity

  • activity - attach(调用createPhoneWindow,创建窗口)

  • activity - create

  • activity - start

  • activity - restoreState

  • activity - postCreate

  • activity - resume

    从create()到resume(),才会去触发DecorView的创建(期间包括addContentView、setContentView、getDecorView)

  • activity - makeVisible

    makeVisible()才会显示DecorView,显示的内容包括status bar、action bar、content view;所以在resume()或之前,想获取Activity View的大小的话,你会发现它们返回的始终是0,因为DecorView还没有显示丫!但如果你在resume()使用handler直接调用post,让它放到下一帧去获取view的大小就可以获取到了,因为在resume()之后马上就把DecorView给显示出来了。

7、Activity转场动画的实现机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ExitTransitionCoordinator.java源码:

// Android 5.0的新特性
// 原理:在播放动画之前或实现页面跳转之前,先把你需要实现转场的这些元素拿到,然后把它的位置给记录下来,比如捕捉它的位置信息、大小信息等等,然后跳转到新的页面,在新的页面显示之前,再把刚才捕捉到的页面共享元素位置、大小信息拿到,把它应用到我的新页面这些共享元素的属性当中,再让它从当前的共享元素的值开始,播放到目标元素,也就是新页面当中这些被共享的元素它应该所在的位置。
// 这种特性可以直接在View上面使用,也可以在Frgment中使用,Fragment也有sharedElements这样的参数,可以去设置。
private void startExitTransition() {
Transition transition = getExitTransition();
ViewGroup docorView = getDecor();
if(/*no null*/) {
setTransitioningViewsVisiblity(View.VISIBLE,false);
TransitionManager.beginDelayedTransition(decorView, transition);
setTransitioningViewVisiblity(View.INVISIBLE, false);
decorView.invalidate();
}else {
transitionStarted();
}
}

private void startSharedElementTransition(Bundle sharedElementState) {
...
ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState, mSharedElementNames);
...

Transition transition = beginTransition(decorView, ...);
if(startEnterTransition) {
startEnterTransition(transition);
}
...
}

三、题目结论

  • Activity启动流程是一个庞大的题目,足够灵活
  • 启动过程中涉及到AMS的交互与插件化紧密相关
  • 参数传递机制的可与架构设计联系进行迁移
  • 生命周期与窗口展示可以向事件处理、UI绘制等话题迁移