Android Activity 知识体系

一、生命周期

Google官方公布的Activity生命周期图:

1.正常流程

Created with Raphaël 2.2.0 onCreate onStart onResume 程序运行中 onPause onStop onDestroy

PS:打印生命周期的代码时,可以在当前方法中使用Thread.currentThread().stackTrace[2].methodName获取当前方法名。或提取出方法,使用stackTrace[3]:

fun getCurrentMethod(): String {
    return Thread.currentThread().stackTrace[3].methodName
}

2.跳转到另一个页面再返回

Created with Raphaël 2.2.0 onResume 跳转到第二个页面 onPause 第二个页面onCreate 第二个页面onStart 第二个页面onResume onSaveInstanceState onStop 回到当前页面 第二个页面onPause onRestart onStart onResume 第二个页面onStop 第二个页面onDestory

需要注意的是,onPause是在页面销毁时立即执行,onPause执行完后第二个页面才能执行onCreate -> onStart -> onResume流程,onStop是在第二个页面onResume之后才执行。
由此可知:onPause中不应做太耗时的操作,如果onPause太耗时会影响新Activity的显示。

3.旋转手机

如果当前Activity没有在AndroidManifest中配置android:configChanges="orientation",旋转手机时页面将重建,生命周期如下:

Created with Raphaël 2.2.0 onResume 旋转手机 onPause onSaveInstanceState onStop onDestory onCreate onStart onRestoreInstanceState onResume

这和调用Activity的recreate方法的生命周期是一样的。
如果当前Activity在AndroidManifest中配置了android:configChanges="orientation",旋转手机时页面不会重建,生命周期如下:

Created with Raphaël 2.2.0 onResume 旋转手机 onConfigurationChanged

每旋转一次,就会调用一次onConfigurationChanged。可以通过判断newConfig?.orientation获取当前手机屏幕方向。

override fun onConfigurationChanged(newConfig: Configuration?) {
    super.onConfigurationChanged(newConfig)
    when (newConfig?.orientation) {
        Configuration.ORIENTATION_PORTRAIT -> {
            // 现在是垂直方向
        }
        Configuration.ORIENTATION_LANDSCAPE -> {
            // 现在是横置方向
        }
    }
}

其他配置也是一样,当配置发生改变时,默认重建Activity。如果不希望Activity在配置发生改变时重建,在AndroidManifest中配置相应的configChanges属性即可。

4.onSaveInstanceState() 和 onRestoreInstanceState()触发时机

onSaveInstanceStateonRestoreInstanceState并不是生命周期方法,不一定被触发。以下是onSaveInstanceState调用时机:

(1)、按下HOME键时;
(2)、查看最近运行的程序时;
(3)、息屏时;
(4)、跳转到另一个Activity时;
    这四个调用时机都属于同一种情况:当前 Activity 可能因为长时间不使用或者系统内存不足被销毁,如果Activity确实被销毁了,再次回到当前页面时Activity会重建,onRestoreInstanceState才会被调用,否则不会被调用。
(5)、Activity重建时。
    此时 Activity 必然被销毁,onRestoreInstanceState 必然被调用。

总而言之,onSaveInstanceState 的调用遵循一个重要原则,即当系统存在 未经你许可销毁你的activity的可能时, onSaveInstanceState 会被系统调用,因为这是系统的责任,它必须要提供一个机会让你保存你的数据。如果Activity确实被销毁了,再次回到当前页面时Activity会重建, onRestoreInstanceState 会被调用,并且把onSaveInstanceState 中保存的Bundle对象作为参数传递给 onRestoreInstanceStateonCreate 方法。

如果是用户正常销毁Activity,onSaveInstanceState不会被调用。例如用户按下返回键或程序调用finish方法销毁Activity时,不会触发onSaveInstanceState

另外,和Activity一样,每个View都有onSaveInstanceStateonRestoreInstanceState 方法,android自带的UI控件都恰当的实现了onSaveInstanceStateonRestoreInstanceState 方法。因此,当activity被摧毁和重建时,这些UI控件会自动保存和恢复状态数据。比如EditText控件会自动保存和恢复输入的数据,CheckBox控件会自动保存和恢复选中状态。开发者只需要为这些控件指定一个唯一的ID(通过设置android:id属性即可),剩余的事情就可以自动完成了。如果没有为控件指定ID,这个控件不会进行自动的数据保存和恢复。

二、启动模式

1.标准模式:android:launchMode="standard"

这是默认的启动模式,每次启动Activity时都会创建一个新的Activity,无论同样的Activity是否已经存在。

2.栈顶复用模式:android:launchMode="singleTop"

如果要启动的新Activity已经在栈顶,此Activity不会被重建,生命周期回调如下:

Created with Raphaël 2.2.0 onPause onNewIntent onResume

onNewIntent中可以获取到新启动请求的Intent信息。

3.栈内复用模式:android:launchMode="singleTask"

在这种模式下,只要Activity在任何一个Activity栈中存在,那么多次启动此Activity都不会重新创建实例,和singleTop一样,系统也会回调其onNewIntent
singleTask默认具有clearTop的效果,即:如果启动的Activity在栈中已经存在,启动时会将位于其上面的Activity全部出栈。

4.单实例模式:android:launchMode="singleInstance"

这是一种加强的singleTask模式,它除了具有singleTask模式的所有特性外,还加强了一点,那就是具有此种模式的Activity只能单独地位于一个任务栈中。

注:如果需要指定Activity运行在一个另一个栈中,除了使用singleInstance外,还可以使用taskAffinity属性+singleTask启动模式。taskAffinity属性默认为包名,指定时至少需要包含一个分隔符"."。例如:

<activity
    android:name=".SecondActivity"
    android:taskAffinity="com.myTask"
    android:launchMode="singleTask" />

查看Activity栈信息的代码如下:

val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
// maxNum表示最多获取多少个Activity栈
val maxNum = 10
val runningTasks = activityManager.getRunningTasks(maxNum)
Log.d("~~~","taksNums = ${runningTasks.size}")
runningTasks.forEach {
    Log.d("~~~", "numActivities = ${it.numActivities},topActivityName = ${it.topActivity.shortClassName}
}

使用前需要先在AndroidManifest中添加权限:

<!--获取Activity任务栈 权限-->
<uses-permission android:name="android.permission.GET_TASKS" />

此权限和getRunningTasks方法都已经标记过时了,此方法的注释上解释道因为不安全所以限制了使用,但使用时仍然可以获取到自己程序的任务栈,stackoverflow上已有过讨论,所以请放心使用。

三、隐式启动Activity

使用IntentFilter隐式启动Activity时,需要action、category、data信息同时匹配成功才能启动。
一个Activity中可以有多个intent-filter,只要Intent能够匹配任何一组intent-filter,即可成功启动Activity。

1.action匹配规则

Intent中必须有action,且必须和intent-filter中的其中一个action相同,action区分大小写;

2.category匹配规则

每一个Intent在 startActivity 或 startActivityForResult 时都会默认添加android.intent.category.DEFAULT这个category,所以自定义intent-filter时必须添加android.intent.category.DEFAULT
Intent中可以不指定category,因为android.intent.category.DEFAULT这个category已经能够匹配intent-filter,如果指定了其他category,则必须和intent-filter中的category其中一个相同。

3.data匹配规则

如果intent-filter中定义了data,与action类似,Intent中必须有data,且必须和intent-filter中的其中一个data相同才能匹配。
data语法如下:

<data
    android:scheme="string"
    android:host="string"
    android:port="8080"
    android:path="/string"
    android:pathPattern="string"
    android:pathPrefix="/string"
    android:mimeType="string"/>

含义是:

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

例如:

content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info

4.隐式跳转的例子

AndroidManifest中定义intent-filter:

<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="action" />
        <category android:name="category" />
        <category android:name="android.intent.category.DEFAULT" />
        <data
            android:mimeType="image/*"
            android:scheme="content" />
    </intent-filter>
</activity>

Intent设置action、category、data隐式跳转:

val intent = Intent()
intent.action = "action"
intent.addCategory("category")
intent.setDataAndType(Uri.parse("content://abc"), "image/png")
startActivity(Intent.createChooser(intent, "Choose app"))

注:隐式启动时,如果没有任何Activity匹配此Intent,程序会报android.content.ActivityNotFoundException: No Activity found to handle Intent异常,所以推荐做法是:
(1)使用

Intent.createChooser(intent, "Choose app")

(2)或检查

packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null

(3)或检查

intent.resolveActivity(packageManager) != null

5.启动Activity的标准的Action常量及对应的字符串

Action常量 对应字符串 简单说明
ACTION_MAIN android.intent.action.MAIN 应用程序入口
ACTION_VIEW android.intent.action.VIEW 显示指定数据
ACTION_ATTACH_DATA android.intent.action.ATTACH_DATA 指定某块数据将被附加到其他地方
ACTION_EDIT android.intent.action.EDIT 编辑指定数据
ACTION_PICK android.intent.action.PICK 从列表中选择某项并返回所选的数据
ACTION_CHOOSER android.intent.action.CHOOSER 显示一个Activity选择器
ACTION_GET_CONTENT android.intent.action.GET_CONTENT 让用户选择数据,并返回所选数据
ACTION_DIAL android.intent.action.DIAL 显示拨号面板
ACTION_CALL android.intent.action.CALL 直接向指定用户打电话
ACTION_SEND android.intent.action.SEND 向其他人发送数据
ACTION_SENDTO android.intent.action.SENDTO 向其他人发送消息
ACTION_ANSWER android.intent.action.ANSWER 应答电话
ACTION_INSERT android.intent.action.INSERT 插入数据
ACTION_DELETE android.intent.action.DELETE 删除数据
ACTION_RUN android.intent.action.RUN 运行数据
ACTION_SYNC android.intent.action.SYNC 执行数据同步
ACTION_PICK_ACTIVITY android.intent.action.PICK_ACTIVITY 用于选择Activity
ACTION_SEARCH android.intent.action.SEARCH 执行搜索
ACTION_WEB_SEARCH android.intent.action.WEB_SEARCH 执行Web搜索
ACTION_FACTORY_TEST android.intent.action.FACTORY_TEST 工厂测试的入口点

6.标准的Category常量及对应的字符串

Category常量 对应字符串 简单说明
CATEGORY_DEFAULT android.intent.category.DEFAULT 默认的Category
CATEGORY_BROWSABLE android.intent.category.BROWSABLE 指定该Activity能被浏览器安全调用
CATEGORY_TAB android.intent.category.TAB 指定该Activity作为TabActivity的Tab页
CATEGORY_LAUNCHER android.intent.category.LAUNCHER Activity显示顶级程序列表中
CATEGORY_INFO android.intent.category.INFO 用于提供包信息
CATEGORY_HOME android.intent.category.HOME 设置该Activity随系统启动而运行
CATEGORY_PREFERENCE android.intent.category.PREFERENCE 该Activity是参数面板
CATEGORY_TEST android.intent.category.TEST 该Activity是一个测试
CATEGORY_CAR_DOCK android.intent.category.CAR_DOCK 指定手机被插入汽车底座(硬件)时运行该Activity
CATEGORY_DESK_DOCK android.intent.category.DESK_DOCK 指定手机被插入桌面底座(硬件)时运行该Activity
CATEGORY_CAR_MODE android.intent.category.CAR_MODE 设置该Activity可在车载环境下使用
Category常量 对应字符串 简单说明
CATEGORY_DEFAULT android.intent.category.DEFAULT 默认的Category
CATEGORY_BROWSABLE android.intent.category.BROWSABLE 指定该Activity能被浏览器安全调用
CATEGORY_TAB android.intent.category.TAB 指定该Activity作为TabActivity的Tab页
CATEGORY_LAUNCHER android.intent.category.LAUNCHER Activity显示顶级程序列表中
CATEGORY_INFO android.intent.category.INFO 用于提供包信息
CATEGORY_HOME android.intent.category.HOME 设置该Activity随系统启动而运行
CATEGORY_PREFERENCE android.intent.category.PREFERENCE 该Activity是参数面板
CATEGORY_TEST android.intent.category.TEST 该Activity是一个测试
CATEGORY_CAR_DOCK android.intent.category.CAR_DOCK 指定手机被插入汽车底座(硬件)时运行该Activity

参考文章

Android开发之InstanceState详解
获取Activity栈,判断当前Activity位置
《Android开发艺术探索》

原创文章 67 获赞 68 访问量 6万+

猜你喜欢

转载自blog.csdn.net/AlpinistWang/article/details/90087553