重读《Android 开发艺术探索》,本篇是书中的第一章内容的总结。
一、Activity 生命周期
首先看下官方提供的 Activity
生命周期图
1. 正常情况下的生命周期分析
a)首次启动一个 mainActivty 时的调用情况:
main onCreate
main onStart
main onResume
b)从 mainActivty 启动 childActivity 时的调用情况:
main onPause
child onCreate
child onStart
child onResume
main onStop
c)从 childActivity 回退到 mainActivty 时的调用情况:
child onPause
main onRestart
main onStart
main onResume
child onStop
child onDestroy
d)在 mainActivty 按 home 键回到桌面或在当前页面锁屏的调用情况:
main onPause
main onStop
e)接着上一步再打开 mainActivty 或解锁屏幕时的调用情况:
main onRestart
main onStart
main onResume
f)显示 Toast、Dialog
显示 Taost 不会调用任何生命周期
显示 Dialog 不会调用任何生命周期
再延伸一下,在 dialog 中有个按钮,点击这个按钮之后打开 childActivity,此时的生命周期调用同直接在 mainActivity 中打开 childActivity 是一样的。
g)特殊情况,childActivity 设置了透明主题
透明主题设置,在 AndroidManifest.xml
中设置
<activity
android:name=".ChildActivity"
android:theme="@style/TranslateTheme"
android:exported="false" />
TranslateTheme
在 themes.xml
中定义
<style name="Translate" parent="Theme.AppCompat">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
</style>
从 mainActivty 启动 childActivity 时的调用情况:
main onPause
child onCreate
child onStart
child onResume
在 childActivity 锁屏或按 Home 键回到桌面,调用情况:
child onPause
child onStop
main onStop
解锁屏幕,调用情况:
main onRestart
main onStart
child onRestart
child onStart
child onResume
从 childActivity 回退到 mainActivty 时的调用情况:
child onPause
main onResume
child onStop
child onDestroy
可以看到同上面 b、c 中相比较不会调用 onStop、onRestar、onStart,这种情况可以更好的理解:onStart 的时候就可见了,但是不能同用户交互,onStop 之后就不可见了,因为透明主题两个 Activity 都是可见的,所以不会调用 onStop,也就不会调用 onRestar、onStart。
2. 异常情况下生命周期分析
a)不做额外设置的前提下,横竖屏切换,生命周期调用情况:
main onPause
main onStop
main onSaveInstanceState // 存储数据
main onDestroy
main onCreate
main onStart
main onRestoreInstanceState // 恢复 onSaveInstanceState 中保存的数据
main onResume
onSaveInstanceState
、onRestoreInstanceState
为当 Activity
被销毁并且有机会再次显示(常见系统配置发送改变如屏幕旋转、切换了系统语言、插入外接键盘)的情况时才会被调用,正常调用 finish()
或按返回键退出并不会调用这两个方法。
Editext 需要设置 id 才能在横竖屏切换后自动恢复输入的内容
b)设置 Activity 当设备配置发送改变后不重建
通过在 AndroidManifest.xml
文件中对指定 activity 做出如下设置,则可以在屏幕旋转后不重建。
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize" // 设置当屏幕旋转不发生重建
android:exported="false">
</activity>
二、Activity 的启动模式
1. 启动模式
包含:stadard、singleTop、singleTask、singleInstance
四种。
解释见下图:
在模式为 singleTask
的 activityA 中打开模式为 standard
的 ActivityB,则 ActivityB 也处于 ActivityA 的任务栈中。
TaskAffinity
用来标识一个 Activity 所需要的任务栈的名字(名字必须包含至少一个 .),默认为应用的包名。该属性结合 singelTask
启动模式或者 allowTaskReparenenting
配对使用。
设置启动模式及任务栈的方式
<activity
android:name=".ChildActivity"
android:exported="false"
// 设置启动模式
android:launchMode="singleTask"
// 设置任务栈名称,至少包括一个 .
android:taskAffinity="hyh.task_h" />
查看 activity 堆栈信息 adb shell dumpsys activity activities
查看堆栈相关信息
...
Application tokens in top down Z order:
// hyh.task_h 为堆栈名,即 taskAffinity 属性的值
* Task{63ac7df #625 visible=true type=standard mode=fullscreen translucent=false A=10128:hyh.task_h U=0 StackId=625 sz=2}
mLastOrientationSource=ActivityRecord{b27edcd u0 com.hyh.okhttpdemo/.ThirdActivity t625}
bounds=[0,0][1440,2560]
// 该任务栈中的 activity 信息
* ActivityRecord{b27edcd u0 com.hyh.okhttpdemo/.ThirdActivity t625}
* ActivityRecord{18e2a58 u0 com.hyh.okhttpdemo/.ChildActivity t625}
* Task{3944544 #1 visible=false type=home mode=fullscreen translucent=true I=com.android.launcher3/.uioverrides.QuickstepLauncher U=0 StackId=1 sz=1}
mLastOrientationSource=Task{2a97ab0 #5 visible=true type=home mode=fullscreen translucent=true I=com.android.launcher3/.uioverrides.QuickstepLauncher U=0 StackId=1 sz=1}
bounds=[0,0][1440,2560]
* Task{2a97ab0 #5 visible=true type=home mode=fullscreen translucent=true I=com.android.launcher3/.uioverrides.QuickstepLauncher U=0 StackId=1 sz=1}
mLastOrientationSource=ActivityRecord{7adeb2d u0 com.android.launcher3/.uioverrides.QuickstepLauncher t5}
bounds=[0,0][1440,2560]
* ActivityRecord{7adeb2d u0 com.android.launcher3/.uioverrides.QuickstepLauncher t5}
* Task{dec510e #624 visible=false type=standard mode=fullscreen translucent=true A=10128:com.hyh.okhttpdemo U=0 StackId=624 sz=1}
mLastOrientationSource=ActivityRecord{e977410 u0 com.hyh.okhttpdemo/.MainActivity t624}
bounds=[0,0][1440,2560]
* ActivityRecord{e977410 u0 com.hyh.okhttpdemo/.MainActivity t624}
* Task{6c48fa8 #3 visible=false type=undefined mode=split-screen-primary translucent=true ?? U=0 StackId=3 sz=0}
bounds=[0,0][1440,1221]
* Task{9668ac1 #4 visible=false type=undefined mode=split-screen-secondary translucent=true ?? U=0 StackId=4 sz=0}
bounds=[0,1255][1440,2560]
...
2. Flags
标记位,作用:设置 activity 的启动模式,影响 activity 的运行状态。
常用标记位
FLAG_ACTIVITY_NEW_TASK
:指定为 singleTask 启动模式,同在 xml 中设置一样
FLAG_ACTIVITY_SINGLE_TOP
:指定为 singleTop 启动模式,同在 xml 中设置一样
FLAG_ACTIVITY_CLEAR_TOP
:当具有该标记位的 activity 启动时,在同一任务栈中所有位于它上面的 activity 都要被出栈
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
:具有该标记的 activity,不会出现在历史列表中。
3. IntentFilter 匹配规则
启动 Activity 分为显式调用和隐式调用。
如果 Intent 同时存在显式调用和隐式调用,以显式调用为主。
显式调用:明确指定对象的组件信息,包括包名、类名。
// 显式调用例子
startActivity(Intent(this, ChildActivity::class.java))
隐式调用通过匹配 IntentFilter
中所设置的过滤信息来启动 Activity。
IntentFilter 包括: action、category、data。
详细说明见下图
使用列子
定义 IntentFilter 的代码:
<activity
android:name=".ThirdActivity"
android:exported="false" >
<intent-filter>
<action android:name="com.hyh.third"/>
<category android:name="hyh.third"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
启动 ThirdActivity 部分代码:
val intent = Intent("com.hyh.third")
intent.addCategory("hyh.third") // 可以省略不写
intent.setDataAndType(Uri.parse("content://abc"), "text/plain") // 因为只设置了mimeType ,所以等同与:intent.type = "text/plain"
// 校验是否可以匹配到指定 activity
packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)?.let {
startActivity(intent)
}
App 默认启动 Activity 的 intent-filter 配置
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
另外:Servie、BroadcastRecivier 也是同样的匹配规则。