Q:如何避免配置改变时Activity重建?
这些部分都要归并到 Activity 异常情况下的生命周期分析情况。
一:由于资源相关配置发生改变,导致 Activity 被杀死或重新创建
例如屏幕发生旋转:横竖屏切换的时候,系统会自动调用 onSaveInstanceState 来保存切换时的数据,接着销毁当前的 Activity,然后重新创建一个 Activity,在调用 onRestoreInstanceState 恢复数据。
- onSaveInstanceState --> onPause(不定)--> onStop --> onDestroy --> onCreate --> onStart --> onRestoreInstanceState --> onResume
为了避免由于配置改变导致Activity重建,可在 AndroidManifest.xml 中对应的 Activity 中设置android:configChanges="orientation|keyboardHidden|screenSize"。此时再次旋转屏幕时,该Activity不会被系统杀死和重建,只会调用 onConfigurationChanged。因此,当配置程序需要响应配置改变,指定configChanges属性,重写 onConfigurationChanged 方法即可。
由于系统资源不足,导致优先级低的Activity被回收
Activity优先级排序:
- 前台可见 Activity > 前台可见不可交互 Activity(前台Activity弹出Dialog) > 后台 Activity(用户按下Home键、切换到其他应用)
当系统内存不足时,会按照Activity优先级从低到高去杀死目标 Activity 所在的进程。
若一个进程没有四大组件在执行,那么这个进程将很快被系统杀死。当系统内存不足时,系统就会按照上述优先级去杀死目标Activity所在的进程,并且后续通过 onSaveInstanceState 和 onRestoreInstanceState 来存储和恢复数据,如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死,比较好的方法是将后台工作放入Service 中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死。
1.当 app 处于后台被系统回收时,app 的进程被杀死了,Activity 也被回收了,而 app 的 task 和 activity 栈以及相应的 intent 和数据会被系统保存起来。当 app 被切回前台时,系统会恢复 task 和 activity 栈以及相应的 intent和数据。 2.不要在 Application 类和全局单例类中存放数据,会导致 app 无法正确恢复状态。运行时的临时数据应存放在SharedPreference、临时文件或数据库中 3 Activity 之间传数据应该用系统提供的 intent 机制。
Activity四种启动模式
一:设置 Activity 启动模式的方法.(两种)
(1)在 AndroidManifest.xml 中给对应的 Activity 设定属性,android:launchMode="standard | singleInstance | single Task | singleTop"
(2)通过标记位来设定,方法是 intent.addFlags(Intent.xxx)
二:Activity 的四种 LaunchMode
(1)standard:标准模式,默认
含义:每次启动一个 Activity 的时候都会创建一个新的实例,不论活动栈中是否已经存在当前 Activity 的实例。
tip:使用 ApplicationContext 去启动 Standard 模式 Activity 会报错。因为 standard 模式的 Activity 会默认进入启动它的活动的任务栈,但是非 Activity 的 Context 没有所谓的任务栈。所以会导致错误。
(2)singleTop:栈顶复用模式
含义:如果新 Activity 已经位于任务栈的栈顶,就不会重新创建,并且回掉 onNewIntent(intent) 方法。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例(但前提是位于返回栈顶部的 Activity 并不是 Activity 的现有实例)。**例如,假设任务的返回栈包含根 Activity A 以及 Activity B、C 和位于顶部的 D(堆栈是 A-B-C-D;D 位于顶部)。收到针对 D 类 Activity 的 Intent。如果 D 具有默认的 "standard" 启动模式,则会启动该类的新实例,且堆栈会变成 A-B-C-D-D。但是,如果 D 的启动模式是 "singleTop",则 D 的现有实例会通过 onNewIntent()接收 Intent,因为它位于堆栈的顶部;而堆栈仍为 A-B-C-D。但是,如果收到针对 B 类 Activity 的 Intent,则会向堆栈添加 B 的新实例,即便其启动模式为 "singleTop"也是如此。
(3)singleTask:栈内复用模式
含义:只要该 Activity 存在在一个任务栈中存在,都不会重新创建它的新实例,且系统会通过调用现有实例的onNewIntent()方法向其传送 Intent,而不是创建新实例。一次只能存在 Activity 的一个实例。如果不存在,系统会先寻找是否存在需要的栈,如果不存在该栈,就创建一个任务栈,并把 Activity 放进去;如果存在,就会把 Activity 创建到已经存在的栈中。
(4)singleInstance:单实例模式
含义:具有此模式的 Activity 只能单独位于一个任务栈中,且此任务栈中只有唯一一个实例。由此 Activity 启动的任何 Activity 均在单独的任务中打开。
三:常用的可设定 Activity 启动模式的标记位
(1)FLAG_ACTIVITY_SINGLE_TOP : 对应 singleTop 启动模式。
(2)FLAG_ACTIVITY_NEW_TASK :对应 singleTask 模式。
为什么要研究启动模式
有时候我们的App需要生成给其他App调用的Activity,例如浏览器应用,照相机应用
解决生成重复页面等等Bug
任务栈过深的时候,避免一直按返回键也退不回想要的页面
四:四种启动模式的应用场景
singleInstance :
单一实例模式,整个手机操作系统里面只有一个实例存在。不同的应用去打开这个activity 共享公用的同一个activity。他会运行在自己单独,独立的任务栈里面,并且任务栈里面只有他一个实例存在。应用场景:呼叫来电界面。这种模式的使用情况比较罕见,在Launcher中可能使用。或者你确定你需要使Activity只有一个实例。
singleTop : 消息推送
(1)通知栏弹出 Notification,点击 Notification 跳转到指定 Activity,但是如果我现在页面就停留在那个指定的Activity,会再次打开我当前的 Activity ,这样返回的时候回退的页面和当前页面一样,感官上就会很奇怪。(生成了重复页面的 bug)
(2)登录的时候:登录成功跳转到主页,按下两次登录按钮,生成了两个主页。一些有启动延迟的页面(往往是动画,网络造成)也会有这样的情况。(我自己写小项目的时候就遇到过,因为网络卡顿,所以同一个按钮点击了好几次,这样在回退的时候返回了好几个相同页面,感受很不舒服,不过当时说实话没想这么多。最近在复习这些知识点才意识到问题。)
(3)耗时操作返回页面。还有一种场景 从activity A启动了个 service 进行耗时操作,或者某种监听,这个时候你home 键了,service 收集到信息,要返回 activityA 了,就用 singleTop 启动,实际不会创建新的 activityA,只是resume 了。不过使用 standard 又会创造2个A的实例。
singleTask : 调用的时候清空这个 activity 任务栈上面所有的activity。(注意重点)
做浏览器、微博之类的应用,比如其他App需要打开我们的浏览器页面,就可以配置他为singleTask
模式,保证他只有一个唯一实例,
详细了解可以看这篇文章:https://blog.csdn.net/zivensonice/article/details/51569502
Q:onNewIntent() 的调用时机是什么呢?
当 activity (假设为 A) 的 launchMode 为 singleTop 且 A 的实例已经在 task 栈顶,或者 launchMode 为 singleTask 且 A 的实例已在 task 栈里 (无论是栈顶还是栈中),再次启动 activity A 时,便不会调用 onCreate() 去产生新的实例,而是调用 onNewIntent() 并重用 task 栈里的 A 实例。
如果 A 在栈顶,那么调用顺序依次是 A.onPause() –> A.onNewIntent() –> A.onResume()。A 的 launchMode 可以是 singleTop 或者是 singlTask.
如果 A 不在栈顶,此时它处于 A.onStop() 状态,当再次启动时,调用顺序依次是 [A.onStop()] –> A.onNewIntent() –> A.onRestart() –> A.onStart() –> A.onResume()。A 的 launchMode 只能是 singleTask。
onNewIntent(Intent intent)的调用时机
在上述情况中,可以看到,每次重用activity实例的时候,都会调用onNewIntent()。
重用实例的生命周期如下:
onNewIntent() –> onRestar() –> onStart() –> onResume()
注意:重用实例时如果涉及到通过Intent传递参数,那么在onNewIntent中一定执行一次setIntent(intent),否则会导致通过getInent()拿到的intent是旧数据,而不是最新的参数。
五:IntentFilter匹配规则
原则: (1)一个intent只有同时匹配某个 Activity 的 intent-filter 中的 action、category、data 才算完全匹配,才能启动该 Activity。 (2) 一个Activity可以有多个 intent-filter,一个 intent 只要成功匹配任意一组 intent-filter,就可以启动该 Activity。
a. action 匹配规则:
要求 intent 中的 action 存在且必须和intent-filter中的 其中一个 action相同。如果 InterFilter 中有 action 的话那么在 intent 中代码设置也必须要有 action。
区分大小写。
b. category 匹配规则:
intent中的category可以不存在,这是因为此时系统给该Activity 默认 加上了 < category android:name="android.intent.category.DEAFAULT" /> 属性值。
除上述情况外,有其他category,则要求 intent 中的 category 和 intent-filter 中的所有 category 相同。
c. data 匹配规则:
如果 intent-filter 中有定义 data,那么 Intent 中也必须也要定义 date。
data 主要由 mimeType (媒体类型)和 URI 组成。在匹配时通过 intent.setDataAndType(Uri data, String type) 方法对date进行设置。
要求和 action相似:如果没有指定 URI,默认值为 content 和 file; 有多组 data 规则时,匹配其中一组即可。
IntentFilter
IntentFilter 的意思就是意图过滤器, 当我们隐式的启动系统组件的时候,就会根据 IntentFilter 来筛选出合适的进行启动。
现在我们知道了可以在 Intent 启动的时候对应设置 Action、Category 、DataAndType,这里设置的是为了过滤的时候对应 IntentFilter 匹配 Action、Category 、data。
除开过滤广播的的 IntentFilter 可以在代码中创建外,其它组件的都得在 AndroidManifest.xml 中给设置。
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_BATTERY_LOW);
intentFilter.addCategory(Intent.CATEGORY_APP_EMAIL);
intentFilter.addDataType("video/mpeg");
Reciver reciver = new Reciver();
registerReceiver(reciver, intentFilter);
这是代码设置的方法,我们来看看在 xml 文件中的设置方法
在不断的复习总结中成长,每次再看一遍不仅会加深理解,而且还会发现之前没有注意到的细节,又会发现之前没有注意到的细节(话说哪来这么多细节,,,)
未来没人能看破,作为一个大学生,正在迷茫期。只能像 李莎莎 这首歌唱的一样,沉重的欲望拖着向前走。
愿我们成为真实的自己。