- Activity作为四大组件之首,是使用最为频繁的一种组件,中文直接翻译为"活动",不过如果被翻译为"界面"会更好理解。正常情况,除了Window,Dialog和Toast , 我们能见到的界面只有Activity。
1. 进程模式
- APP进程的级别,由其
活跃的
或处于栈顶
的组件的活动状态(比如生命周期)决定。 - Activity的进程模式分为:前台进程、可见进程、Service进程、后台进程、空白进程
- 前台进程:
适用于目前操作所需的进程
。在不同的情况下,进程可能因为其所包含的各种应用组件而被视为前台进程。以下任一条件成立,就是前台进程。
- 当前
进程Activity
正在与用户进行交互。(已调用onResume) - 当前
Service
正在执行回调的代码 生命周期(Service.onCreate() , Service.onStart(), Service.onDestory()) - 进程有一个正在运行的
BroadcastReceiver
(BroadcastReceiver.onReceive()正在执行)
- 可见进程:前台进程是一个Dialog;进程有一个Service,这个Service正在和一个可见的Activity绑定。
Activity
在屏幕上对用户可见,但是失去了焦点,调用了onPause,暂时无法操作。Service
正在通过Service.startForeground()。- 实现特定功能的系统服务:动态壁纸,输入法服务。
-
服务进程:服务进程包含了一个已使用startService()方法启动的Service的进程。
-
后台进程:当Activity的onStop被调用,但是onDestroy并没有被调用
-
空白进程:当系统需要内存的时候,会暂时将背景进程清除,就成为了空白进程。
- 进程优先级:前台进程 > 可见进程 > service进程 > 后台进程 > 空进程
- 系统内存不足时,会删除优先级低的进程。
2. Activity的生命周期
- onCreate:表示Activity正在被创建,这是生命周期的第一个方法。可以做一些初始化的工作(加载界面布局资源,初始化Activity所需数据)
- onRestart:表示Activity正在被重新启动。一般情况下,当当前Activity从不可见更新为可见状态的时候,onRestart就会被调用
- onStart:表示Activity正在被启动,即将开始。这时Activity已经可见,但是没有出现在前台,还无法和用户交互了。
- onResume:表示Activity已经可见,并且出现在前台并开始活动。要注意这个和onStart的对比。onStart和onResume都表示Activity已经可见,但是onStart的时候Activity还在后台,onResume的时候Activity才显示到前台。
- onPause:表示Activity正在暂停,正常情况下,紧跟着onStop就会被调用。在特殊情况下,如果快速再回到当前Activity,那么onResume会被调用。(这种属于特殊情况,一般不会发生)。在这可以做一些存储数据,停止动画等操作,但是注意不能太耗时,不然会影响到新Activity的显示,onPause执行完,新的Activity的onResume才会执行。
- onStop:表示Activity即将停止,可以做一些稍微重量级的回收工作,同样不能太过耗时。
- onDestroy:表示Activity即将被销毁,这是生命周期中的最后一个回调。在这里,我们可以做一些回收工作和最终资源的释放。
2.1 典型情况下的生命周期
- 打开新的Activity或者切换到桌面的时候,生命周期的回调?
- onStart 和 onResume、onPause 和 onStop实质有什么不同?
- 假设当前Activity为A,如果这时用户打开了一个新的ActivityB,那么B的onResume和A的onPause哪个先执行呢?
2.2 异常情况下的生命周期
- Activity除了受用户操作所导致的正常的生命周期方法调度,还有一些异常情况,比如当
资源相关的系统配置发生改变
以及系统内存不足
时,Activity可能会被杀死。
2.2.1 情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建
- 首先要对系统的资源加载机制有一定的理解。把一张图片放在drawable目录后,可以通过Resources去获取这张图片。同时为了兼容不同的设备,我们可能还需要在其他一些目录放置不同的图片,比如drawable-mdpi、drawable-hdpi 等等。 这样,当应用程序启动的时候,系统就会根据设备去加载合适的Resources资源。比如说横屏手机和竖屏手机会**拿到两张不同的图片。
- 突然旋转屏幕,由于**系统配置发生了变化,在默认情况下,
Activity就会被销毁并且重新创建
,当然我们也可以组织重新创建我们的Activity
2.2.2 情况2:资源内存不足导致优先级低的Activity被杀死
- 这种情况不好模拟,但是其数据存储和恢复过程和情况1完全一致。这里我们简单描述一下Activity的优先级情况。
- 前台进程 > 可见进程 > 服务进程 > 后台进程 > 空进程
- 前台Activity——正在和用户进行交互的Activity,优先级最高
- 可见但非前台Activity——比如Activity弹出了一个对话框,导致Activity可见,但是位于后台无法和用户直接交互。
- 后台Activity——已被暂停的Activity,比如执行了onStop,优先级最低。
如果一个进程中没有四大组件在执行,那么这个进程很快就会被系统杀死。因此,一些后台工作不适合脱离四大组件而独自运行在后台中,这样进程很容易被杀死。比较好的方法是将后台工作放入Service中从而保证有一定的优先级,这样就不会轻易被杀死。
3. Activity的重建机制
- 重建机制就是在onStop()后,调用onSaveInstanceState将Activity的某些状态保存下来。然后在重新创建onStart()之后调用onRestoreInstanceState()把这状态显示出来。
- onSaveInstanceState:这个方法将要保存的数据 以
键值对
形式保存在Bundle
对象中。并把保存下来的bundle对象作为参数同时传递给onRestoreInstanceState和onCreate,保存了当前Activity的视图结构。(API29之后一定会调用在onStop之后) - onRestoreInstanceState:一旦被调用,
Bundle参数一定有值,
不用额外判空,但onCreate正常启动Bundle参数为null。
3.1 onSaveInstanceState & onRestoreInstanceState
- 主要是
onSaveInstanceState
和onRestoreInstanceState
方法。 - 系统为我们做了一定的恢复工作。当Activity在异常情况下需要重新创建时,系统会默认为我们保存当前Activity 的视图结构,并且在 Activity重启后为我们恢复这些数据。
- 例如:文本框(TextView)中用户输入的数据、ListView滚动的位置等。这些View相关的状态系统都能够默认为我们恢复。
- 针对某一特定的View系统能够为我们恢复哪些数据,我们可以查看View的源码。 和Activity一样,每个View都有
onSaveInstanceState
和onRestoreInstanceState
。 看一下它们的具体实现,就可以知道系统自动为每个View恢复哪些数据。 - 拿TextView来说,我们分析一下它保存了哪些数据。
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
// Save state if we are forced to
final boolean freezesText = getFreezesText();
//文本框是否选中
boolean hasSelection = false;
int start = -1;
int end = -1;
//如果文本框不为空
if (mText != null) {
start = getSelectionStart();
end = getSelectionEnd();
if (start >= 0 || end >= 0) {
// Or save state if there is a selection
//表示选中
hasSelection = true;
}
}
if (freezesText || hasSelection) {
SavedState ss = new SavedState(superState);
if (freezesText) {
if (mText instanceof Spanned) {
final Spannable sp = new SpannableStringBuilder(mText);
if (mEditor != null) {
removeMisspelledSpans(sp);
sp.removeSpan(mEditor.mSuggestionRangeSpan);
}
ss.text = sp;
} else {
//保存文本框上的数据
ss.text = mText.toString();
}
}
if (hasSelection) {
// XXX Should also save the current scroll position!
ss.selStart = start;
ss.selEnd = end;
}
if (isFocused() && start >= 0 && end >= 0) {
ss.frozenWithFocus = true;
}
ss.error = getError();
if (mEditor != null) {
ss.editorState = mEditor.saveInstanceState();
}
return ss;
}
return superState;
}
3.2 Activity的configChanges属性
- 上面分析了系统的数据存储和恢复机制,我们知道,当系统配置发生改变的时候,Activity会被重新创建,有没有办法不重新创建呢?系统配置中有很多内容,如果当某项内容发生改变后,我们不想系统重新创建Activity。可以给Activity指定configChanges属性。
android:configChanges="orientation"
- 如果我们想指定多个值,可以用 " |" 连接起来。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.activitytext" >
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ActivityText" >
<activity
android:name=".MainActivity"
//我们常用的只有locale,orientation,keyboardHidden这三个选项
android:configChanges="orientation|screenSize"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3.3 重建机制中保存和恢复数据
保存数据
Activity
通过onSaveInstanceState()
方法来保存状态,onSaveInstanceState()执行在onStop()后。- 然后Activity会委托Window去保存数据,接着
Window
再委托它上面的等级容器去保存数据。 - 顶层容器是一个
ViewGroup
,一般来说可能是一个DecorView
。 - 最后顶层容器再去通知它的子元素来保存数据,这样整个保存数据过程就完成了。
Activity里面有两个数据结构专门保存状态
- View States保存View的状态
- Instance State存储View states 以及开发者在 onSaveInstanceState中手动保存的Activity成员变量。
恢复数据
- 通过
onRestoreInstanceState
方法来恢复状态,在保存期间,会自动收集View Hieracht
(视图层次)中每一个实现了状态保存和恢复方法的View状态,这些数据会在onRestoreInstanceState方法时回传给View,并且回传是根据view的id来逐一匹配。
注意事项
- 为了成功保存状态,要求在View内部实现保存和恢复的算法。(原生的View都有做到,自定义View需要自己来重写实现)
- 为了成功恢复状态,要给View赋对应的ID
- 如果需要保存Activity的成员变量,重写方法时,需要保留基类的实现。
4. Activity的启动模式
4.1 LaunchMode
- standard(标准模式)
- singleTop(栈顶复用模式)
- singleTask(栈内复用模式)
如果D所需的任务栈为S1,并且当前任务栈S1的情况为 ADBC,根据栈内复用的原则,此时D不会重新创建,系统会把D切换到栈而并调用onNewIntent 方法,同时由于singleTask 默认具有clearTop的效果,会导致栈内所有在D上面的 Activity全部出栈,于是最终S1中的情况为 AD。这一点比较特殊,在后面还会对此情况详细分析。
- singleInstance(单实例模式)
- 设置启动模式
//通过在Intent中设置标志位来为Activity指定启动模式
Intent intent = new Intent();
intent.setClass(MainActivity.this,SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
4.2 复用调用的方法 onNewIntent()
- 当ActivityA的
LaunchMode
为SingleTop
,并且ActivityA在栈顶,现在启动ActivityA,会调用onNewIntent()方法 。
- 生命周期顺序为:onResume—>onPause—>onNewIntent—>onResume
- 当ActivityA的
LaunchMode
为SingleInstance,SingleTask
。
- 如果之前栈内没有它,就会去创建一个它的实例,也就是会调用 onCreate()→onStart()→onResume()
- 如果之前栈内有它的实例存在的话,生命周期的调用变成了onNewIntent()→onRestart()→onStart()→onResume 。
- 只对以上的情况,再次启动它们的时候才会调用,即只对startActivity有效。如果仅仅从后台切换到前台而不再次启动的情形,不会触发onNewIntent。
- 当ActivityA的LaunchMode为SingleTop,并且ActivityA在栈顶,现在启动ActivityA,会调用onNewIntent()方法 。
- 生命周期顺序为:onResume—>onPause—>onNewIntent—>onResume
- 当ActivityA的LaunchMode为SingleInstance,SingleTask。
- 如果之前栈内没有它,就会去创建一个它的实例,也就是会调用 onCreate()→onStart()→onResume()
- 如果之前栈内有它的实例存在的话,生命周期的调用变成了onNewIntent()→onRestart()→onStart()→onResume 。
- 只对以上的情况,再次启动它们的时候才会调用,即只对startActivity有效。如果仅仅从后台切换到前台而不再次启动的情形,不会触发onNewIntent。
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent); //设置新的intent
int data = getIntent().getIntExtra("tanksu", 0);//此时的到的数据就是正确的了
}
5. Activity的TaskAffinity
- 我们在上文中,多次提到了某个Activity所需的任务栈,什么是Activity所需要的任务栈?这要从一个参数说起:TaskAffinity [afineting] ,可以翻译为任务相关性。这个参数标识了一个Activity所需要的任务栈的名字。
- 如果没有显示指明taskAffinity,那么它的taskAffinity就等于Application指明的taskAffinity,如果Application也没有指明,那么该taskAffinity的值就等于包名。
android:taskAffinty="com.ning.task1"
- taskAffinity的作用就是指定想要的任务栈。
- 当启动模式设置为standard或singleTop时,taskAffinity是不起作用的。待启动的 Activity 会跟随源 Activity 的任务栈,即使你显式声明了不一样的taskAffinity。
- 当启动模式设置了 singleTask或者 singleInstance时,taskAffinity就会新建任务栈来存储待启动的 Activity 实例。
5.1 TaskAffinity和allowTaskReparenting结合
TaskAffinity属性
主要是用于和allowTaskReparenting属性配对使用。- 另外,任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再调到前台。
- allowTaskReparenting允许Activity在任务栈之间进行迁移。
具体点来说,就是一个Activity现在是处于某个Task当中的,但是它与另外一个Task具有相同的affinity值。当另外这个任务切换到前台的时候,该Activity就可以转移到切换到前台的这个任务当中。allowTaskReparenting默认是继承至application中的allowTaskReparenting=false,不可以。
- 当一个应用A启动应用B的某个Activity之后,如果这个Activity的allowTaskReparenting属性为true的话。那么当应用B被启动后,此Activity会直接从应用A的任务栈转移到应用B的任务栈中。
- 具体一点说就是,比如有两个应用A和B,A启动了B的一个ActivityC,然后按Home 键回到桌面,然后再单击B的桌面图标,这个时候不是启动了B上的 主Activity,而是重新显示了已经被应用A启动的ActivityC。或者说,C从A的任务栈转移到了B的任务栈中。
5.2 使用FLAG_ACTIVITY_NEW_TASK标记
单独的FLAG_ACTIVITY_NEW_TASK并不等于singleTask
,它仅表示寻找Activity所需的任务栈压入。(即TaskAffinity指定的任务栈)- FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP
也不等价于启动模式singleTask
- 当调用startActivity()方法来启动一个Activity时,默认是将它放入到当前的Task(任务)当中。但是,如果在Intent中加入了FLAG_ACTIVITY_NEW_TASK 的话,情况就会变的复杂起来。首先,系统会去检查这个 Activity要求的affinity是否与当前任务栈相同。
- 如果相同,把Activity放到当前的Task当中。
- 如果不同,先去检查是否有一个名字与该Activity的Affinity相同的Task。
- 如果有,这个Task被调到前台,同时将这个Activity显示到这个Task的顶端。
- 如果没有,系统将会尝试为这个Activity创建一个新的Task。需要注意的是,如果一个Activity在manifest文件中声明的启动模式是”singleTask”,那么他被启动的时候,行为模式会和前面提到的指定FLAG_ACTIVITY_NEW_TASK一样。
5.3 任务栈和返回栈
- 任务栈和返回栈是独立存在的。
- 任务栈:Activity想要的任务栈,taskAffinity的作用就是指定想要的任务栈,是APP层面的一个东西。
- 任务栈一般有前台任务栈和后台任务栈之分。比如我们手机中打开了飞书,按下Home键之后打开了抖音,此时抖音就在前台任务栈,飞书就在后台任务栈。
- 返回栈:它是Activity层面的,用户页面的返回依赖的是返回栈,而不是任务栈。 一个返回栈中可能会包含来自不同任务栈的Activity,返回栈就是为了维护正确的回退栈关系。
6. Activity的FLAG
- 标记位只能在Activity中设置
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
FLAG_ACTIVITY_NEW_TASK:这个标记位的作用是为Activity指定"singleTask"启动模式(近似于)。
- 在不设置taskAffinity的情况下,单独设置FLAG_ACTIVITY_NEW_TASK并没有任何意义,不会创建新的任务栈,每次都会创建新的Activity实例,不会栈内复用。
- 在一个新的任务栈中启动Activity。如果这个Activity想要的任务栈已经存在,并且其中已经运行着待启动的Activity,那么这个任务栈就会被带到前台,并回调onNewIntent。这个行为和singleTask一致。
-
FLAG_ACTIVITY_SINGLE_TOP:这个标记位的作用是为Activity指定"singleTop"启动模式,其效果和在XML中指定该启动模式相同
-
FLAG_ACTIVITY_CLEAR_TOP:具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。这个标记位一般会和singleTask启动模式一起出现。如果此时被启动的Activity采用standard模式启动,那么它连同它之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶
- NEW_TASK+CLEAR_TOP != singleTask,因为在standard模式下启动,会把栈内本身的Activity也删除,所以不等于signleTask。
-
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有这个标识位的Activity不会出现在历史Activity的列表中,当某些情况下我们用户通过历史列表回到我们的Activity的时候这个标记比较有用。他等同于在XML中指定Activity属性:
android:excludeFromRecents="true"
- FLAG_ACTIVITY_NO_HOSTORY:被指定的Activity在跳转到其他Activity后,将它从任务栈中移除。
7. IntentFilter
- 我们知道启动Activity,一般有显示调用和隐式调用两种。显示调用需要明确指定被启动对象的组件信息,包括包名和类名。而隐式调用则不需要。
- 这里简单介绍一下隐式调用,隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息。
- 只有一个Intent同时匹配了action类别、category类别、data类别才算完全匹配,只有完全匹配才能启动目标Activity。
- 一个Activity可以有多个IntentFilter,一个Intent只要能匹配任何一组IntentFilter即可成功启动对应的Activity。
8. Activity的数据传输
- 使用Intent的putExtra传递(传递数据大小有限制,1M左右,根据Binder)
- SharedPreference进行数据传递
- 使用静态变量传递数据
- 使用Bundle对象
- 使用Activity销毁时传递数据(onActivityResult)
- 使用序列化对象Seriazable
9. Activity的finish()
- finish()用于结束Activity进程,关闭Activity但不关闭栈。
- 调用finish()方法后,先pause()当前Activity,会通过AMS的ApplicationThread.scheduleDestroyActivity方法,向ActivtyThread(主线程)发送一个H.DESTROY_ACTIVITY消息
- 主线程会调用handleDestroyActivity来处理这个消息
- 在通过层层调用,回调Activity的onDestroy方法。
- 业务场景
- 比如在订单列表A点击新建订单进入新建订单ActivityB中,点击保存后,跳转到订单详细C中。在C中我们点击返回键要求返回到A中。
10. 相关问题
10.1 进程模式相关问题
- 何谓进程模式?
10.2 生命周期相关问题
- onPause() 和 onStop()的区别?
- A启动B,然后B退回A,两者的生命周期变化?
- 为什么先Activity的onResume()执行,然后才是调用源Activity的onStop()方法呢?
- MainActivity跳转到DialogActivity(盖不住MainActivity)再跳转到secondActivity的生命周期?
- 从secondActivity返回到DialogActivity的生命周期?
- 弹出Dialog对Activity的生命周期有什么影响?
10.3 重建机制相关问题
- onSaveInstanceState方法调用的时机?
- 什么时候会发生重建?
- 为什么会有重建机制?
- 优先级较低的Activity在内存不足的被回收后,怎样恢复到销毁前的状态?
10.4 启动模式相关问题
- Activity的启动模式,A是standard,B是singleInstance, A启动B,又启动A会有几个对象?
- 启动模式和FLAG的区别?
10.5 Activity和application有什么区别?
Activity是UI界面的抽象,而application是应用程序的抽象
。两者都是Context的子类。- 应用程序每次启动的时候,系统会为其创建一个application对象且只有一个(单例类),用来存储一些系统的信息,相当于一个容器。
- 启动application时,系统会创建一个PID(进程ID),所有的Activity都在这个进程上运行,在application创建时会初始化一个全局变量,同一个应用的activity,都可以获取到这个变量。
10.6 onStop和onDestroy()回调延时及延时10S的问题?
为什么回调会延时
- Activity调用流程:打开ActivityA -> 打开ActivityB -> 关闭ActivityB -> 回到ActivityA
- 由于要关闭的Activity B或者要打开的Activity A 往主线程的MessageQueue中连续不断地post大量的msg。
- 导致主线程一直在不断地进行消息循环处理 没有停歇。 因此APP不能向AMS发起IPC来进行ActivityB的销毁。
- 所以finish ActivityB之后,onDestroy不会被及时回调。具体延时多久,要看主线程中堆积的msg什么时候被处理完。
延时10S
-
Android系统安排了一套流程来保证即使正常流程被阻断以后,ActivityB还是能被销毁。
-
在关闭Activity B返回Activity A的时候,当AMS侧发起 IPC通知 APP侧的Activity A执行resume的时候,同时也会向AMS自己的主线程发送一个msg,该msg延时10s后执行。
-
该msg的具体内容与 正常流程APP空闲时段需要执行的任务一致,当然也包括销毁B。
-
这样Activity B的onDestroy方法也就在延时10s后调用执行。
-
我们就该在写代码的时候,及时关闭、清理、移除不必要的主线程消息,并且尽可能的保证每个消息处理时间不要太长。