Activity 知识点快问快答

Activity的启动流程

答案参考自:

大致为以下过程:

  1. Launcher 通过 startActivity 方法调用 AMS
  2. AMSZygote 进程发送创建应用进程的请求。
  3. Zygote 进程接受请求并在 main 方法中孵化应用进程。
  4. 应用进程通过SystemServer#main启动ActivityThread
  5. Activity的入口是ActivityThreadmain函数,在main函数中创建一个新的ActivityThread对象,开启消息循环(UI线程)后创建新的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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// SystemServer#main

public static void main(String[] args) {
new SystemServer().run();
}

private void run() {
···
// Ensure binder calls into the system always run at foreground priority.
// 确保当前进程的 binder 调用总是运行在前台优先级
BinderInternal.disableBackgroundScheduling(true);
···
// 创建主线程 Looper
Looper.prepareMainLooper();

// Initialize native services.
// 加载android_servers.so库,该库包含的源码在frameworks/base/services/目录下
System.loadLibrary("android_servers");
···
// Initialize the system context.
// 初始化系统上下文
createSystemContext();

// Create the system service manager.
//创建系统服务管理
mSystemServiceManager = new SystemServiceManager(mSystemContext);
// 将mSystemServiceManager添加到本地服务的成员sLocalServiceObjects
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);

// Start services.
// 启动各种系统服务
try {
startBootstrapServices(); // 启动引导服务
startCoreServices(); // 启动核心服务
startOtherServices(); // 启动其他服务
} catch (Throwable ex) {
···
}
···
// Loop forever.
// 启动 Looper 循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}

onSaveInstanceStateonRestoreInstanceState的调用时机

答案参考自:

onSaveInstanceState(Bundle outState)

当 Activity 在不正常销毁的情况下,就会调用 onSaveInstanceState 方法,并将 Activity 中需要保存的数据(比如 View 状态 或者我们自己的数据)保存到这个方法的参数 Bundle 中。

onSaveInstanceState(Bundle outState)会在以下情况被调用

  1. 当用户按下HOME键时。
  2. 从最近应用中选择运行其他的程序时。
  3. 按下电源按键(关闭屏幕显示)时。
  4. 从当前activity启动一个新的activity时。
  5. 屏幕方向切换时(无论竖屏切横屏还是横屏切竖屏都会调用)。

Activity的onSaveInstanceState回调时机,取决于app的targetSdkVersion

  • targetSdkVersion低于11的app,onSaveInstanceState方法会在Activity.onPause之前回调;

  • targetSdkVersion低于28的app,则会在onStop之前回调;

  • 28之后,onSaveInstanceState在onStop回调之后才回调。

onRestoreInstanceState(Bundle savedInstanceState)

onRestoreInstanceState(Bundle savedInstanceState)只有在activity确实是被系统回收,重新创建activity的情况下才会被调用。

onRestoreInstanceState(Bundle outState)会在以下情况被调用

  1. 屏幕方向切换时(无论竖屏切横屏还是横屏切竖屏都会调用)
  2. 由于内存紧张导致后台运行的程序被kill掉时(这种不太好模拟)

onRestoreInstanceState(Bundle outState) 的回调时机:

回调发生在onStart回调之后。


onCreateonRestoreInstance方法中恢复数据时的区别

因为onSaveInstanceState 不一定会被调用,所以onCreate里的Bundle参数可能为空,如果使用onCreate来恢复数据,一定要做非空判断。

onRestoreInstanceStateBundle参数一定不会是空值,因为它只有在上次activity被回收了才会调用。


Activity的数据是怎么保存的,进程被Kill后,保存的数据怎么恢复的?

答案参考自:

  • 《Android 开发艺术探索》

通过onSaveInstanceState方法和onRestoreInstanceState方法进行数据的保存和恢复。

onSaveInstanceStateonRestoreInstanceState方法中,系统自动为我们做了一定的恢复工作。当 Activity 在异常情况下需要重新创建时,系统会默认为我们保存当前Activity 的视图结构,并且在Activity重启后为我们恢复这些数据,比如文本框中用户输入的数据、ListView滚动的位置等。


Activity的启动模式和使用场景

答案参考自:

Activity的启动模式有四种:standardsingleTopsingleTasksingleInstance

standard:标准模式

这种启动模式为标准模式,也是默认模式。每当我们启动一个Activity,系统就会相应的创建一个实例,不管这个实例是否已经存在。这种模式,一个栈中可以有多个实例,每个实例也都有自己的任务栈。而且是谁启动了此Activity,那么这个Activity就运行在启动它的Activity所在的栈中。

分析总结

标准模式下,只要启动一次Activity,系统就会在当前任务栈新建一个实例。

使用场景

正常的去打开一个新的页面,这种启动模式使用最多,最普通 。

singleTop:栈顶复用模式

这种启动模式下,如果要启动的Activity已经处于栈的顶部,那么此时系统不会创建新的实例,而是直接打开此页面,同时它的onNewIntent()方法会被执行,我们可以通过Intent进行传值,而且它的onCreate(),onStart()方法不会被调用,因为它并没有发生任何变化。

分析总结

  1. 当前栈中已有该Activity的实例并且该实例位于栈顶时,不会创建实例,而是复用栈顶的实例,并且会将Intent对象传入,回调onNewIntent()方法;
  2. 当前栈中已有该Activity的实例但是该实例不在栈顶时,其行为和standard启动模式一样,依然会创建一个新的实例;
  3. 当前栈中不存在该Activity的实例时,其行为同standard启动模式。

使用场景

这种模式应用场景的话,假如一个新闻客户端,在通知栏收到了3条推送,点击每一条推送会打开新闻的详情页,如果为默认的启动模式的话,点击一次打开一个页面,会打开三个详情页,这肯定是不合理的。如果启动模式设置为singleTop,当点击第一条推送后,新闻详情页已经处于栈顶,当我们第二条和第三条推送的时候,只需要通过Intent传入相应的内容即可,并不会重新打开新的页面,这样就可以避免重复打开页面了。

singleTask:栈内复用模式

在这个模式下,如果栈中存在这个Activity的实例就会复用这个Activity,不管它是否位于栈顶,复用时,会将它上面的Activity全部出栈,因为singleTask本身自带clearTop这种功能。并且会回调该实例的onNewIntent()方法。其实这个过程还存在一个任务栈的匹配,因为这个模式启动时,会在自己需要的任务栈中寻找实例,这个任务栈就是通过taskAffinity属性指定。如果这个任务栈不存在,则会创建这个任务栈。不设置taskAffinity属性的话,默认为应用的包名。

分析总结

在复用的时候,首先会根据taskAffinity去找对应的任务栈:

  1. 如果不存在指定的任务栈,系统会新建对应的任务栈,并新建Activity实例压入栈中。
  2. 如果存在指定的任务栈,则会查找该任务栈中是否存在该Activity实例
    1. 如果不存在该实例,则会在该任务栈中新建Activity实例。
    2. 如果存在该实例,则会直接引用,并且回调该实例的onNewIntent()方法。并且任务栈中该实例之上的Activity会被全部销毁。

使用场景

SingleTask这种启动模式最常使用的就是一个APP的首页,因为一般为一个APP的第一个页面,且长时间保留在栈中,所以最适合设置singleTask启动模式来复用。

singleInstance:单实例模式

单实例模式,顾名思义,只有一个实例。该模式具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。

分析总结

启动该模式Activity的时候,会查找系统中是否存在:

  1. 不存在,首先会新建一个任务栈,其次创建该Activity实例。
  2. 存在,则会直接引用该实例,并且回调onNewIntent()方法。
  3. 特殊情况:该任务栈或该实例被销毁,系统会重新创建。

使用场景

很常见的是,电话拨号盘页面,通过自己的应用或者其他应用打开拨打电话页面 ,只要系统的栈中存在该实例,那么就会直接调用。

总结

在使用APP过程中,不可避免页面之间的跳转,那么就会涉及到启动模式。其实在对界面进行跳转时,Android系统既能在同一个任务中对Activity进行调度,也能以Task(任务栈)为单位进行整体调度。在启动模式为standard或singleTop时,一般是在同一个任务中对Activity进行调度,而在启动模式为singleTask或singleInstance是,一般会对Task进行整体调度。


ActivityonNewIntent方法什么时候会执行

答案参考自:

使用SingleTask以及SingleInstance启动模式的时候,会调用onNewInstant方法。

Activity第一次启动的时候执行onCreate -> onStart -> onResume等后续生命周期函数,也就时说第一次启动Activity并不会执行到onNewIntent

而后面如果再有想启动Activity的时候,那就是执行 onNewIntent -> onResart -> onStart -> onResume

如果Android系统由于内存不足把已存在Activity释放掉了,那么再次调用的时候会重新启动Activity即执行onCreate -> onStart -> onResume等。

当调用到onNewIntent(intent)的时候,需要在onNewIntent中使用setIntent(intent)赋值给ActivityIntent。否则,后续的getIntent都是得到旧的Intent


Activity 生命周期

答案参考自:

Activity的生命周期
  1. onCreate

    表示 Activity 正在被创建,这是生命周期的第一个方法。在这个方法中,我们可以做一些初始化工作,比如调用 setContentView 去加载界面布局资源、初始化 Activity 所需数据等。

  2. onStart

    表示 Activity 正在被启动,即将开始,这时 Activity已经可见了,但是还没有出现在前台,还无法和用户交互。这个时候其实可以理解为 Activity 已经显示出来了,但是我们还看不到

  3. onResume

    表示 Activity 已经可见了,并且出现在前台并开始活动

    要注意这个和 onStart 的对比,onStart 和 onResume 都表示 Activity 已经可见,但是 onStart 的时候 Activity 还在后台,onResume 的时候 Activity 才显示到前台。

  4. onPause

    表示 Activity 正在停止,正常情况下,紧接着 onStop 就会被调用。

    在特殊情况下,如果这个时候快速地再回到当前 Activity,那么 onResume 会被调用。笔者的理解是,这种情况属于极端情况,用户操作很难重现这一场景。

    当前 Activity 是可见的,但不能与用户交互状态(即不在前台)。

    此时可以做一些存储数据、停止动画等工作,但是注意不能太耗时,因为这会影响到新 Activity 的显示,onPause 必须先执行完,新 Activity 的 onResume 才会执行。

  5. onStop

    表示 Activity 即将停止,此时 Activity 对用户是不可见的,可以做一些稍微重量级的回收工作,同样不能太耗时

    在系统内存紧张的情况下,有可能会被系统进行回收,所以一般在当前方法可做资源回收。

  6. onDestroy

    表示 Activity 即将被销毁,这是 Activity 生命周期中的最后一个回调,在这里我们可以做一些回收工作和最终的资源释放

  7. onRestart

    表示 Activity 正在重新启动。一般情况下,当当前 Activity 从不可见重新变为可见状态时,onRestart 就会被调用。

    这种情形一般是用户行为所导致的,比如用户按 Home 键切换到桌面或者用户打开了一个新的 Activity,这时当前的 Activity 就会暂停,也就是 onPause 和 onStop 被执行了,接着用户又回到了这个 Activity,就会出现这种情况。

具体情况分析

  1. 针对一个特定的 Activity,第一次启动,回调如下:onCreate -> onStart -> onResume

  2. 当用户打开新的 Activity 或者切换到桌面的时候,回调如下: onPause -> onStop

    这里有一种特殊情况,如果新 Activity 采用了透明主题,那么当前 Activity 不会回调 onStop

  3. 当用户再次回到原 Activity 时,回调如下: onRestart -> onStart -> onResume

  4. 当用户按 back 键回退时,回调如下:onPause -> onStop -> onDestroy

  5. Activity 被系统回收后再次打开,生命周期方法回调过程和(1)一样,注意只是生命周期方法一样,不代表所有过程都一样,比如 onSaveInstanceStateonRestoreInstanceState 的调用。

  6. Activity_A跳转到Activity_B,会先执行 A 活动的 onPause,再执行 B 活动的 onResume

  7. 整个生命周期来说,onCreateonDestroy 是配对的,分别标识着 Activity 的创建和销毁,并且只可能有一次调用。

  8. Activity 是否可见来说,onStartonStop 是配对的,随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次。

  9. Activity 是否在前台来说,onResumeonPause是配对的,随着用户操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次。

生命周期 是否可见 是否在前台
onStart
onResume
onPause
onStop

onStartonResumeonPauseonStop 的区别

答案参考自:

  • 《Android 开发艺术探索》

从实际使用过程来说,onStartonResumeonPauseonStop看起来的确差不多,甚至我们可以只保留其中一对,比如只保留onStartonStop。既然如此,那为什么Android系统还要提供看起来重复的接口呢?

根据上面的分析,我们知道,这两个配对的回调分别表示不同的意义,onStartonStop是从Activity是否可见这个角度来回调的,而 onResumeonPause是从Activity是否位于前台这个角度来回调的,除了这种区别,在实际使用中没有其他明显区别。


Activity的显式启动和隐式启动

答案参考自:

启动Activity主要是通过Intent(意图)来实现。主要分为显示的和隐式的两种。

隐式启动 Activity

优点:

  1. 只要知道被启动ActivityActionCategory即可,不用知道对应的类名或者是包名。
  2. 只要Activity有对应的ActionCategory都会被启动起来。然后提供给用户选择要启动哪一个。

需要被启动的Activity,需要在自己的AndroidManifest.xml定义对应的actioncategory

1
2
3
4
5
6
7
8
<activity
android:name="com.android.activity.demo.SecondActivity"
android:label="@string/second_activity_name" >
<intent-filter>
<action android:name="android.intent.action.SECONDACTIVITY_START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

启动Activity的地方,把对应的Action填入即可。

1
2
3
4
5
6
7
8
9
10
11
private void startSecondActivityByAction() {
Log.d(TAG, "startSecondActivityByAction()");
Intent intent = new Intent();
intent.setAction("android.intent.action.SECONDACTIVITY_START");
intent.addCategory(Intent.CATEGORY_DEFAULT);
try {
startActivity(intent);
} catch (Exception e) {
Log.d(TAG, "start activity error!");
}
}

显示启动 Activity

不足:

  1. 被启动的应用的包名或者类名发生变化后,就会无法启动。

通过类名类启动Activity, 一般是同一个APK里面使用。

1
2
3
4
5
6
7
8
9
private void startSecondActivityByClass() {
Log.d(TAG, "startSecondActivityByClass()");
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
try {
startActivity(intent);
} catch (Exception e) {
Log.d(TAG, "start activity error!");
}
}

Activity间传递数据的方式

答案参考自:

共六种传递数据的方式。

  1. 使用Intent的putExtra传递

    第一个Activity中

    1
    2
    3
    4
    5
    6
    //创建意图对象
    Intent intent = new Intent(this,TwoActivity.class);
    //设置传递键值对
    intent.putExtra("data",str);
    //激活意图
    startActivity(intent);

    第二个Activity中

    1
    2
    3
    4
    5
    6
    // 获取意图对象
    Intent intent = getIntent();
    //获取传递的值
    String str = intent.getStringExtra("data");
    //设置值
    tv.setText(str);
  2. 使用Intention的Bundle传递

    第一个Activity中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //创建意图对象
    Intent intent = new Intent(MainActivity.this,TwoActivity.class);
    //用数据捆传递数据
    Bundle bundle = new Bundle();
    bundle.putString("data", str);
    //把数据捆设置改意图
    intent.putExtra("bun", bundle);
    //激活意图
    startActivity(intent);

    第二个Activity

    1
    2
    3
    4
    5
    //获取Bundle
    Intent intent = getIntent();
    Bundle bundle = intent.getBundleExtra("bun");
    String str = bundle.getString("data");
    tv.setText(str);
  3. 使用Activity销毁时传递数据(startActivityForResult)

    第一个Activity中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Intent intent = new Intent(MainActivity.this,TwoActivity.class);
    //用一种特殊方式开启Activity
    startActivityForResult(intent, 11);

    //设置数据
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    String str = data.getStringExtra("data");
    tvOne.setText(str);
    }

    第二个activity中

    1
    2
    3
    4
    5
    6
    //设置返回的数据
    Intent intent = new Intent();
    intent.putExtra("data", edtOne.getText().toString().trim());
    setResult(3, intent);
    //关闭当前activity
    finish();
  4. SharedPreferences传递数据

    第一个Activity中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    SharedPreferences sp = this.getSharedPreferences("info", 1);
    //获取sp编辑器
    Editor edit = sp.edit();
    edit.putString("data", str);
    edit.commit();
    //创建意图对象
    Intent intent = new Intent(MainActivity.this,TwoActivity.class);
    //激活意图
    startActivity(intent);

    第二个activity中

    1
    2
    3
    SharedPreferences sp = this.getSharedPreferences("info", 1);
    //设置数据
    tv.setText(sp.getString("data", ""));
  5. 使用序列化对象Seriazable

    工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import java.io.Serializable;
    class DataBean implements Serializable {
    private String name;
    private String sex;
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    public String getSex() {
    return sex;
    }
    public void setSex(String sex) {
    this.sex = sex;
    }
    }

    第一个Activity中

    1
    2
    3
    4
    5
    6
    7
    8
    //创建意图
    Intent intent = new Intent(MainActivity.this,TwoActivity.class);
    DataBean bean = new DataBean();
    //通过set方法把数据保存到DataBean对象中
    bean.setName("啦啦");
    bean.setSex("男");
    intent.putExtra("key", bean);
    startActivity(intent);

    第二个Activity中

    1
    2
    3
    4
    5
    6
    7
    8
    Intent intent = getIntent();
    //反序列化数据对象
    Serializable se = intent.getSerializableExtra("key");
    if(se instanceof DataBean){
    //获取到携带数据的DataBean对象db
    DataBean db = (DataBean) se;
    tv.setText(db.getName() + "===" + db.getSex());
    }
  6. 使用静态变量传递数据

    第一个Activity中

    1
    2
    3
    4
    Intent intent = new Intent(MainActivity.this,TwoActivity.class);
    TwoActivity.name = "NAME";
    TwoActivity.str = "STR";
    startActivity(intent);

    第二个Activity中

    1
    2
    3
    4
    //静态变量
    protected static String name;
    protected static String str;
    tv.setText(str + name);

有哪些Activity常用的标记位Flags

  1. FLAG_ACTIVITY_NEW_TASK

    指定启动模式为SingleTask

  2. FLAG_ACTIVITY_SINGLE_TOP

    指定启动模式为SingleTop

  3. FLAG_ACTIVITY_CLEAR_TOP

    一般与SingleTask启动模式一起出现,启动时位于它上方的Activity出栈,如果被启动的Activity实例已存在,系统则会调用它的onNewIntent


Activity任务栈是什么

答案参考自:

即Task,栈结构,存放Activity;退出应用程序时只有将所有任务栈找那个的所有Activity出栈,任务栈才能销毁。

任务栈可以移动到后台,在其中保留每一个Activity的状态。

对应的类:ActivityRecord、TaskRecord、ActivityStack。


App启动Activity的方式,注意事项

答案参考自:

  1. 指定打开的应用

    通过 Intent 隐式启动时,如果有多个 action 值相同的 Activity,系统会让你选择启动哪个,解决办法时通过指定 Intent-filter 的 data 属性,Intent 则要加上一个 URI,该 URI 的 scheme 必须与 data 的 scheme 相同。

    data 由两部分组成,mimeType 和 URI。mimeType 指媒体类型,比如 image/ijpeg、audio/mpeg4-generic 和 video/* 等,可以表示图片、文本、视频等不同的媒体格式,而 URI 中包含的数据就比较多了,下面是 URI 的结构:

    1
    <scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]

    Scheme:URI 的模式,比如 http、file、content等,如果 URI 中没有指定 scheme,那么整个 URI 的其他参数无效,这也意味着 URI 是无效的。

  2. 如何防止自己的 Activity 被外部非正常启动

    给自己的 Activity 添加 android:permission=”xxx.xxx.xx”,那么想要访问你的 Activity 就必须声明 uses-permission xxx.xxx.xx


ANR 的四种场景

答案参考自:

  1. Service TimeOut

    Service未在规定时间执行完成,前台服务20s,后台200s。

  2. BroadCastQueue TimeOut

    未在规定时间内未处理完广播,前台广播10s内,后台60s内。

  3. ContentProvider TimeOut

    publish在10s内没有完成。

  4. Input Dispatching timeout

    5s内未响应键盘输入、触摸屏幕等事件。

Activity的生命周期回调的阻塞并不在触发ANR的场景里面,所以并不会直接触发ANR。 只不过死循环阻塞了主线程,如果系统再有上述的四种事件发生,就无法在相应的时间内处理从而触发ANR。


Activity 知识点快问快答
https://luoyuy.top/posts/c221e751d218/
作者
LuoYu-Ying
发布于
2022年5月21日
许可协议