Activity 知识点快问快答
Activity
的启动流程
答案参考自:
大致为以下过程:
Launcher
通过startActivity
方法调用AMS
。AMS
向Zygote
进程发送创建应用进程的请求。Zygote
进程接受请求并在main
方法中孵化应用进程。- 应用进程通过
SystemServer#main
启动ActivityThread
。 Activity
的入口是ActivityThread
的main
函数,在main
函数中创建一个新的ActivityThread
对象,开启消息循环(UI线程)后创建新的Activity
。
1 |
|
onSaveInstanceState
和onRestoreInstanceState
的调用时机
答案参考自:
onSaveInstanceState(Bundle outState)
当 Activity 在不正常销毁的情况下,就会调用 onSaveInstanceState 方法,并将 Activity 中需要保存的数据(比如 View 状态 或者我们自己的数据)保存到这个方法的参数 Bundle 中。
onSaveInstanceState(Bundle outState)会在以下情况被调用:
- 当用户按下HOME键时。
- 从最近应用中选择运行其他的程序时。
- 按下电源按键(关闭屏幕显示)时。
- 从当前activity启动一个新的activity时。
- 屏幕方向切换时(无论竖屏切横屏还是横屏切竖屏都会调用)。
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)会在以下情况被调用:
- 屏幕方向切换时(无论竖屏切横屏还是横屏切竖屏都会调用)
- 由于内存紧张导致后台运行的程序被kill掉时(这种不太好模拟)
onRestoreInstanceState(Bundle outState) 的回调时机:
回调发生在onStart
回调之后。
onCreate
和onRestoreInstance
方法中恢复数据时的区别
因为onSaveInstanceState
不一定会被调用,所以onCreate
里的Bundle
参数可能为空,如果使用onCreate
来恢复数据,一定要做非空判断。
而onRestoreInstanceState
的Bundle
参数一定不会是空值,因为它只有在上次activity
被回收了才会调用。
Activity
的数据是怎么保存的,进程被Kill
后,保存的数据怎么恢复的?
答案参考自:
- 《Android 开发艺术探索》
通过onSaveInstanceState
方法和onRestoreInstanceState
方法进行数据的保存和恢复。
在 onSaveInstanceState
和 onRestoreInstanceState
方法中,系统自动为我们做了一定的恢复工作。当 Activity
在异常情况下需要重新创建时,系统会默认为我们保存当前Activity
的视图结构,并且在Activity
重启后为我们恢复这些数据,比如文本框中用户输入的数据、ListView
滚动的位置等。
Activity
的启动模式和使用场景
答案参考自:
Activity的启动模式有四种:standard
、singleTop
、singleTask
和singleInstance
。
standard:标准模式
这种启动模式为标准模式,也是默认模式。每当我们启动一个Activity,系统就会相应的创建一个实例,不管这个实例是否已经存在。这种模式,一个栈中可以有多个实例,每个实例也都有自己的任务栈。而且是谁启动了此Activity,那么这个Activity就运行在启动它的Activity所在的栈中。
分析总结
标准模式下,只要启动一次Activity,系统就会在当前任务栈新建一个实例。
使用场景
正常的去打开一个新的页面,这种启动模式使用最多,最普通 。
singleTop:栈顶复用模式
这种启动模式下,如果要启动的Activity已经处于栈的顶部,那么此时系统不会创建新的实例,而是直接打开此页面,同时它的onNewIntent()方法会被执行,我们可以通过Intent进行传值,而且它的onCreate(),onStart()方法不会被调用,因为它并没有发生任何变化。
分析总结
- 当前栈中已有该Activity的实例并且该实例位于栈顶时,不会创建实例,而是复用栈顶的实例,并且会将Intent对象传入,回调onNewIntent()方法;
- 当前栈中已有该Activity的实例但是该实例不在栈顶时,其行为和standard启动模式一样,依然会创建一个新的实例;
- 当前栈中不存在该Activity的实例时,其行为同standard启动模式。
使用场景
这种模式应用场景的话,假如一个新闻客户端,在通知栏收到了3条推送,点击每一条推送会打开新闻的详情页,如果为默认的启动模式的话,点击一次打开一个页面,会打开三个详情页,这肯定是不合理的。如果启动模式设置为singleTop,当点击第一条推送后,新闻详情页已经处于栈顶,当我们第二条和第三条推送的时候,只需要通过Intent传入相应的内容即可,并不会重新打开新的页面,这样就可以避免重复打开页面了。
singleTask:栈内复用模式
在这个模式下,如果栈中存在这个Activity的实例就会复用这个Activity,不管它是否位于栈顶,复用时,会将它上面的Activity全部出栈,因为singleTask本身自带clearTop这种功能。并且会回调该实例的onNewIntent()方法。其实这个过程还存在一个任务栈的匹配,因为这个模式启动时,会在自己需要的任务栈中寻找实例,这个任务栈就是通过taskAffinity属性指定。如果这个任务栈不存在,则会创建这个任务栈。不设置taskAffinity属性的话,默认为应用的包名。
分析总结
在复用的时候,首先会根据taskAffinity去找对应的任务栈:
- 如果不存在指定的任务栈,系统会新建对应的任务栈,并新建Activity实例压入栈中。
- 如果存在指定的任务栈,则会查找该任务栈中是否存在该Activity实例
- 如果不存在该实例,则会在该任务栈中新建Activity实例。
- 如果存在该实例,则会直接引用,并且回调该实例的onNewIntent()方法。并且任务栈中该实例之上的Activity会被全部销毁。
使用场景
SingleTask这种启动模式最常使用的就是一个APP的首页,因为一般为一个APP的第一个页面,且长时间保留在栈中,所以最适合设置singleTask启动模式来复用。
singleInstance:单实例模式
单实例模式,顾名思义,只有一个实例。该模式具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。
分析总结
启动该模式Activity的时候,会查找系统中是否存在:
- 不存在,首先会新建一个任务栈,其次创建该Activity实例。
- 存在,则会直接引用该实例,并且回调onNewIntent()方法。
- 特殊情况:该任务栈或该实例被销毁,系统会重新创建。
使用场景
很常见的是,电话拨号盘页面,通过自己的应用或者其他应用打开拨打电话页面 ,只要系统的栈中存在该实例,那么就会直接调用。
总结
在使用APP过程中,不可避免页面之间的跳转,那么就会涉及到启动模式。其实在对界面进行跳转时,Android系统既能在同一个任务中对Activity进行调度,也能以Task(任务栈)为单位进行整体调度。在启动模式为standard或singleTop时,一般是在同一个任务中对Activity进行调度,而在启动模式为singleTask或singleInstance是,一般会对Task进行整体调度。
Activity
的onNewIntent
方法什么时候会执行
答案参考自:
使用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)
赋值给Activity
的Intent
。否则,后续的getIntent
都是得到旧的Intent
。
Activity
生命周期
答案参考自:
《Android 开发艺术探索》
onCreate
表示 Activity 正在被创建,这是生命周期的第一个方法。在这个方法中,我们可以做一些初始化工作,比如调用 setContentView 去加载界面布局资源、初始化 Activity 所需数据等。
onStart
表示 Activity 正在被启动,即将开始,这时 Activity已经可见了,但是还没有出现在前台,还无法和用户交互。这个时候其实可以理解为 Activity 已经显示出来了,但是我们还看不到。
onResume
表示 Activity 已经可见了,并且出现在前台并开始活动。
要注意这个和 onStart 的对比,onStart 和 onResume 都表示 Activity 已经可见,但是 onStart 的时候 Activity 还在后台,onResume 的时候 Activity 才显示到前台。
onPause
表示 Activity 正在停止,正常情况下,紧接着 onStop 就会被调用。
在特殊情况下,如果这个时候快速地再回到当前 Activity,那么 onResume 会被调用。笔者的理解是,这种情况属于极端情况,用户操作很难重现这一场景。
当前 Activity 是可见的,但不能与用户交互状态(即不在前台)。
此时可以做一些存储数据、停止动画等工作,但是注意不能太耗时,因为这会影响到新 Activity 的显示,onPause 必须先执行完,新 Activity 的 onResume 才会执行。
onStop
表示 Activity 即将停止,此时 Activity 对用户是不可见的,可以做一些稍微重量级的回收工作,同样不能太耗时。
在系统内存紧张的情况下,有可能会被系统进行回收,所以一般在当前方法可做资源回收。
onDestroy
表示 Activity 即将被销毁,这是 Activity 生命周期中的最后一个回调,在这里我们可以做一些回收工作和最终的资源释放。
onRestart
表示 Activity 正在重新启动。一般情况下,当当前 Activity 从不可见重新变为可见状态时,onRestart 就会被调用。
这种情形一般是用户行为所导致的,比如用户按 Home 键切换到桌面或者用户打开了一个新的 Activity,这时当前的 Activity 就会暂停,也就是 onPause 和 onStop 被执行了,接着用户又回到了这个 Activity,就会出现这种情况。
具体情况分析:
针对一个特定的 Activity,第一次启动,回调如下:
onCreate
->onStart
->onResume
。当用户打开新的 Activity 或者切换到桌面的时候,回调如下:
onPause
->onStop
。这里有一种特殊情况,如果新 Activity 采用了透明主题,那么当前 Activity 不会回调
onStop
。当用户再次回到原 Activity 时,回调如下:
onRestart
->onStart
->onResume
。当用户按 back 键回退时,回调如下:
onPause
->onStop
->onDestroy
。当 Activity 被系统回收后再次打开,生命周期方法回调过程和(1)一样,注意只是生命周期方法一样,不代表所有过程都一样,比如
onSaveInstanceState
和onRestoreInstanceState
的调用。从 Activity_A跳转到Activity_B,会先执行 A 活动的
onPause
,再执行 B 活动的onResume
。从整个生命周期来说,
onCreate
和onDestroy
是配对的,分别标识着 Activity 的创建和销毁,并且只可能有一次调用。从 Activity 是否可见来说,
onStart
和onStop
是配对的,随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次。从 Activity 是否在前台来说,
onResume
和onPause
是配对的,随着用户操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次。
生命周期 | 是否可见 | 是否在前台 |
---|---|---|
onStart |
是 | 否 |
onResume |
是 | 是 |
onPause |
是 | 否 |
onStop |
否 | 否 |
onStart
和 onResume
、onPause
和 onStop
的区别
答案参考自:
- 《Android 开发艺术探索》
从实际使用过程来说,onStart
和 onResume
、onPause
和 onStop
看起来的确差不多,甚至我们可以只保留其中一对,比如只保留onStart
和 onStop
。既然如此,那为什么Android系统还要提供看起来重复的接口呢?
根据上面的分析,我们知道,这两个配对的回调分别表示不同的意义,onStart
和 onStop
是从Activity是否可见这个角度来回调的,而 onResume
和 onPause
是从Activity是否位于前台这个角度来回调的,除了这种区别,在实际使用中没有其他明显区别。
Activity
的显式启动和隐式启动
答案参考自:
启动Activity主要是通过Intent(意图)来实现。主要分为显示的和隐式的两种。
隐式启动 Activity
优点:
- 只要知道被启动
Activity
的Action
和Category
即可,不用知道对应的类名或者是包名。 - 只要
Activity
有对应的Action
和Category
都会被启动起来。然后提供给用户选择要启动哪一个。
需要被启动的Activity
,需要在自己的AndroidManifest.xml
定义对应的action
和 category
。
1 |
|
启动Activity
的地方,把对应的Action
填入即可。
1 |
|
显示启动 Activity
不足:
- 被启动的应用的包名或者类名发生变化后,就会无法启动。
通过类名类启动Activity, 一般是同一个APK里面使用。
1 |
|
Activity
间传递数据的方式
答案参考自:
共六种传递数据的方式。
使用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);使用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);使用Activity销毁时传递数据(startActivityForResult)
第一个Activity中
1
2
3
4
5
6
7
8
9
10Intent 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();SharedPreferences传递数据
第一个Activity中
1
2
3
4
5
6
7
8
9SharedPreferences 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
3SharedPreferences sp = this.getSharedPreferences("info", 1);
//设置数据
tv.setText(sp.getString("data", ""));使用序列化对象Seriazable
工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import 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
8Intent intent = getIntent();
//反序列化数据对象
Serializable se = intent.getSerializableExtra("key");
if(se instanceof DataBean){
//获取到携带数据的DataBean对象db
DataBean db = (DataBean) se;
tv.setText(db.getName() + "===" + db.getSex());
}使用静态变量传递数据
第一个Activity中
1
2
3
4Intent 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
FLAG_ACTIVITY_NEW_TASK
指定启动模式为SingleTask
FLAG_ACTIVITY_SINGLE_TOP
指定启动模式为SingleTop
FLAG_ACTIVITY_CLEAR_TOP
一般与SingleTask启动模式一起出现,启动时位于它上方的Activity出栈,如果被启动的Activity实例已存在,系统则会调用它的onNewIntent
Activity
任务栈是什么
答案参考自:
即Task,栈结构,存放Activity;退出应用程序时只有将所有任务栈找那个的所有Activity出栈,任务栈才能销毁。
任务栈可以移动到后台,在其中保留每一个Activity的状态。
对应的类:ActivityRecord、TaskRecord、ActivityStack。
跨App
启动Activity
的方式,注意事项
答案参考自:
- Android面试——Activity篇 - 向着内核前进! - 博客园 (cnblogs.com)
- 《Android 开发艺术探索》
指定打开的应用
通过 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 是无效的。
如何防止自己的 Activity 被外部非正常启动
给自己的 Activity 添加
android:permission=”xxx.xxx.xx”
,那么想要访问你的 Activity 就必须声明uses-permission xxx.xxx.xx
。
ANR
的四种场景
答案参考自:
Service TimeOut
Service
未在规定时间执行完成,前台服务20s,后台200s。BroadCastQueue TimeOut
未在规定时间内未处理完广播,前台广播10s内,后台60s内。
ContentProvider TimeOut
publish
在10s内没有完成。Input Dispatching timeout
5s内未响应键盘输入、触摸屏幕等事件。
Activity的生命周期回调的阻塞并不在触发ANR的场景里面,所以并不会直接触发ANR。 只不过死循环阻塞了主线程,如果系统再有上述的四种事件发生,就无法在相应的时间内处理从而触发ANR。