Activity的生命周期和启动模式
1.1 Activity的生命周期全面分析
将 Activity 的生命周期分为两部分内容
- 典型情况下的生命周期分析
- 只在有用户参与的情况下,Activity 所经历的生命周期的改变。
- 异常情况下的生命周期分析
- Activity 被系统回收 或 由于当前设备的 configuration 发生改变从而导致 Activity 被销毁重建。
1.1.1 典型情况下的生命周期分析
几种情况:
- 当用户打开新的 Activity 或者 切换到桌面 的时候,回调如下 onPause =》 onStop
- 如果新 Activity 采用了透明主题,那么 当前 Activity 不会回调 onStop (因为当前 Activity 仍然可见嘛)
- 当用户按下 back 键回退时,回调如下:
onPause
=》onStop
=》onDestroy
(销毁了) - 从整个生命周期来看,onCreate 与 onDestroy 是配对的,分别标识着 Activity 的创建和销毁,只可能有一次调用。
- 从Activity 是否可见来说,onStart 和 onStop 是配对的,随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次;
- 从Activity 是否在前台来说,onResume 和 onPause 是配对的。随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次;
onStart 和 onStop ,onResume 和 onPause 描述上看起来差不多,对于开发人员而言有何区别呢?
- 这两个配对的回调分别表示不同的意义,
- onStart 和 onStop 是从Activity 是否可见这个角度来回调。
- onResume 和 onPause 是从Activity 是否位于前台这个角度来回调。
新 Activity 启动之前,要等栈顶的 Activity 执行完onPause 后,新Activity 才能==启动==。(onCreate )
03-26 10:08:59.983 9030-9030/com.android.rdc.myapplication I/MainActivity: onCreate:
03-26 10:09:00.163 9030-9030/com.android.rdc.myapplication I/MainActivity: onStart:
03-26 10:09:00.163 9030-9030/com.android.rdc.myapplication I/MainActivity: onResume:
03-26 10:09:04.387 9030-9030/com.android.rdc.myapplication I/MainActivity: onPause:
03-26 10:09:04.397 9030-9030/com.android.rdc.myapplication I/SecondActivity: onCreate:
03-26 10:09:04.427 9030-9030/com.android.rdc.myapplication I/SecondActivity: onStart:
03-26 10:09:04.427 9030-9030/com.android.rdc.myapplication I/SecondActivity: onResume:
03-26 10:09:04.838 9030-9030/com.android.rdc.myapplication I/MainActivity: onStop:
03-26 10:09:08.872 9030-9030/com.android.rdc.myapplication I/SecondActivity: finish:
03-26 10:09:08.892 9030-9030/com.android.rdc.myapplication I/SecondActivity: onPause:
03-26 10:09:08.912 9030-9030/com.android.rdc.myapplication I/MainActivity: onRestart:
03-26 10:09:08.912 9030-9030/com.android.rdc.myapplication I/MainActivity: onStart:
03-26 10:09:08.922 9030-9030/com.android.rdc.myapplication I/MainActivity: onResume:
03-26 10:09:09.303 9030-9030/com.android.rdc.myapplication I/SecondActivity: onStop:
03-26 10:09:09.303 9030-9030/com.android.rdc.myapplication I/SecondActivity: onDestroy:
另外,如果调用了 finsh方法,会先调用 onPause 而不是 直接调 onDestroy
onPause 和 onStop 都不能执行耗时操作,尤其是 onPause ,这这意味着,我们应当尽量在 onStop 方法中做操作,从而让 新Activity 尽快显示出来并切换到前台。
1.1.2 异常情况下的生命周期分析 / 8
情况1. 资源相关的系统配置发生改变导致Activcity 被杀死并重新创建
Activity 在异常情况下终止,系统会 调用 onSaveInstance
来保存当前 Activity 的状态。
这个方法的调用是在 onStop 之前,但是==和 onPause 没有既定的时序关系==。
注意:onSaveInstance 只会在 Activity 被异常终止的情况下被回调,正常情况系统不会回调这个方法。
当 Activity 被重新创建之后, 系统会调用 onRestoreInstanceState
,并把 Activity 的销毁时 onSaveInstance
方法所保存的Bundle 对象作为参数同时传递给 onCreate 方法 和 onRestoreInstance
方法。
可以在 onRestoreInstance
方法 和 onCreate
方法中判断 Activity 是否被重建了,以此进行数据恢复。
在 onSaveInstance 和 onRestoreInstance 方法中,系统自动帮我们做了一定的恢复工作。
- 系统默认为我们保存当前 Activity 的视图结构,并且在 Activity 重启之后为我们恢复这些数据,比如 ListView 滚动的位置 等。
关于保存和恢复View层次结构,系统的工作流程是酱紫的:
- 首先 Activity 被意外终止时,Activity 会调用 onSaveInstance 去保存数据,然后 Activity 会 委托Window 去保存数据, Window 委托 它上面的顶级容器去保存数据。顶级容器是一个 ViewGroup,一般来说它很可能是 DecorView 。最后顶层容器再去一一通知它的子元素来保存数据,这样这个数据的保存过程就完成了。
onCreate 方法 和 onRestoreInstance 方法的区别:
-
onRestoreInstance
一旦被调用,其参数一定是有值的,不需要判空- 官方建议使用
onRestoreInstance
去恢复数据。
- 官方建议使用
-
onCreate
方法则不一定,因为如果是正常启动的话,其参数 Bundle onSaveInstance 可能为 null。
通过 onSaveInstanceState 方法保存状态的不足之处
依靠系统通过onSaveInstanceState()
回调为你保存的 Bundle
,可能无法完全恢复 Activity 状态,因为它并非设计用于携带大型对象(例如位图),而且其中的数据必须先序列化,再进行反序列化,这可能会消耗大量内存并使得配置变更速度缓慢。
通过保留 Fragment
来减轻重新初始化 Activity 的负担
当 Android 系统因配置变更而关闭 Activity 时,不会销毁你已标记为要保留的 Activity 的Fragment。 你可以将此类Fragment添加到 Activity 以保留有状态的对象。
注:通过调用 Fragment#setRetainInstance(true)
将 Fragment 标记为要保留。在模拟器上边测试,即使没有 调用setRetainInstance(true)
也仍然会保存啊。
- 扩展
Fragment
类并声明对有状态对象的引用。 - 在创建片段后调用
setRetainInstance(boolean)
。 - 使用
FragmentManager
将片段添加到 Activity。 - 重启 Activity 后,使用
FragmentManager
检索片段。
onSaveInstance 的最终数据保存到哪里?内存里面吗?
保存在 ActivityClientRecord 的 Bundle 类型字段 里面,而 ActivityThread 中含有一个 final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
用于存放 一组ActivityClientRecord
android.os.Bundle 是什么
class Bundle extends BaseBundle implements Cloneable, Parcelable
A mapping from String keys to various Parcelable values.
从字符串键到各种Parcelable值的映射。
ActivityClientRecord 又是在哪里创建的呢?
android.app.ActivityThread#startActivityNow
public final Activity startActivityNow(Activity parent, String id,
Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
Activity.NonConfigurationInstances lastNonConfigurationInstances) {
ActivityClientRecord r = new ActivityClientRecord();
//...
return performLaunchActivity(r, null);
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//……
mActivities.put(r.token, r);//添加到 ArrayMap 中
//……
return activity;
}
Activity 异常销毁的情况下,FragmentActivity 的 onSaveInstanceState 方法的默认实现会保存其中所有的 Fragment 的状态
然后在 android.support.v4.app.FragmentActivity#onCreate
方法中恢复 Fragment 保存的状态。
android.support.v4.app.FragmentManagerImpl#saveAllState
“关于保存和恢复View层次结构,系统的工作流程是这样的:首先Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window再委托它上面的顶级容器去保存数据(这里的说法不恰当,应该是 Windows 里面的顶级容器才对吧)。顶层容器是一个ViewGroup,一般来说它很可能是DecorView。最后顶层容器再去一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。可以发现,这是一种典型的委托思想,上层委托下层、父容器委托子元素去处理一件事情,这种思想在Android中有很多应用,比如View的绘制过程、事件分发等都是采用类似的思想。至于数据恢复过程也是类似的,”
Activity 异常销毁—> 委托 window 保存数据—>委托顶级容器 逐个通知子元素保存数据,
子元素通过重写 onSaveInstanceState 来保存状态的时候通常都会调用 super.onSaveInstanceState,
状态保存的起点实际上是 android.app.Activity#performSaveInstanceState(android.os.Bundle)
//The hook for {@link ActivityThread} to save the state of this activity.
final void performSaveInstanceState(Bundle outState) {
onSaveInstanceState(outState);
saveManagedDialogs(outState);
mActivityTransitionState.saveState(outState);
storeHasCurrentPermissionRequest(outState);
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
}
实际上是保存在 android.app.ActivityThread.ActivityClientRecord 里面的。这个东西 android.app.ActivityThread.ActivityClientRecord#state
调用源头——ActivityThread
保存状态的调用流程
—>Activity.onSaveInstanceState(MainActivity.java:50)
—>android.app.Activity.performSaveInstanceState(Activity.java:1496)
—>android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.java:1386)
—>android.app.ActivityThread.callCallActivityOnSaveInstanceState(ActivityThread.java:4721)
—> android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4672)
—> android.app.ActivityThread.-wrap18(Unknown Source:0)
—> android.app.ActivityThread$H.handleMessage(ActivityThread.java:1595)
—> android.os.Handler.dispatchMessage(Handler.java:106)
—> android.os.Looper.loop(Looper.java:164)
—> android.app.ActivityThread.main(ActivityThread.java:6494)
—> java.lang.reflect.Method.invoke(Native Method)
—> com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
—>at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
恢复保存的状态的调用流程
—> Activity#onRestoreInstanceState(MainActivity.java:61)
—> android.app.Activity.performRestoreInstanceState(Activity.java:1057)
—> android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1260)
—> android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2751)
—> android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
—> android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4699)
—> android.app.ActivityThread.-wrap18(Unknown Source:0)
—> android.app.ActivityThread$H.handleMessage(ActivityThread.java:1595)
—> android.os.Handler.dispatchMessage(Handler.java:106)
—> android.os.Looper.loop(Looper.java:164)
—> android.app.ActivityThread.main(ActivityThread.java:6494)
—> java.lang.reflect.Method.invoke(Native Method)
—> com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
系统内存不足的时候,系统会按照上述优先级去杀死目标 Activity 所在的进程。
后台工作放到哪里比较合适?比较好的方法是将后台工作放入Service中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死。
当配置发生改变的时候,有没有办法不重新创建?
有的。android:configChanges="orientation"
不重新创建的话,前提是不销毁呀。
Activity#onConfigurationChanged
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.e(TAG, " " + newConfig);
}
请谨记:在声明由 Activity 处理配置变更时,你需要负责重置要为其提供备用资源的所有元素。 如果你声明由 Activity 处理方向变更,而且有些图像应该在横向和纵向之间切换,则必须在
onConfigurationChanged()
期间将每个资源重新分配给每个元素。
如果无需基于这些配置变更更新应用,则可不用实现 onConfigurationChanged()
。在这种情况下,仍将使用在配置变更之前用到的所有资源,只是你无需重启 Activity。 但是,应用应该始终能够在保持之前状态完好的情况下关闭和重启,因此你不得试图通过此方法来逃避在正常 Activity 生命周期期间保持你的应用状态。 这不仅仅是因为还存在其他一些无法禁止重启应用的配置变更,还因为有些事件必须由你处理,例如用户离开应用,而在用户返回应用之前该应用已被销毁。
情况2. 资源内存不足导致低优先级的Activity 被杀死
Activity 按照优先级从高到低,可以分为如下三种:
- 前台 Activity —— 正在和用户交互的Activity
- 可见但非前台 Activity ——比如弹出了一个对话框
- 后台 Activity —— 已经被暂停的 Activity,比如 执行了 onStop
一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死。
当系统配置发生改变之后,Activity 会重新被创建。
有没有办法在配置改变时不重新创建呢?
有的,系统配置中有很多内容,如果当某项内容改变之后,我们不想系统重新创建 Activity ,可以给 Activity 指定 configChanges 属性。
config 项目有很多,常用的只有 locale 、 orientation 、keyboardHidden 这三个选项
注意: screenSize 和 smallestScreenSize ,这两个行为比较特殊,他们的行为和 编译选项有关,但和运行的环境无关。
“mcc“ 移动国家号码,由三位数字组成,每个国家都有自己独立的MCC,可以识别手机用户所属国家。
“mnc“ 移动网号,在一个国家或者地区中,用于区分手机用户的服务商。
“locale“ 所在地区发生变化。
“touchscreen“ 触摸屏已经改变。(这不应该常发生。)
“keyboard“ 键盘模式发生变化,例如:用户接入外部键盘输入。
“keyboardHidden“ 用户打开手机硬件键盘
“navigation“ 导航型发生了变化。(这不应该常发生。)
“orientation“ 设备旋转,横向显示和竖向显示模式切换。 //经常发生
“fontScale“ 全局字体大小缩放发生改变
自从Android 3.2(API 13),在设置Activity的android:configChanges="orientation|keyboardHidden"
后,还是一样会重新调用各个生命周期的。因为screen size也开始跟着设备的横竖切换而改变。所以,在AndroidManifest.xml里设置的MiniSdkVersion和 TargetSdkVersion属性大于等于13的情况下,如果你想阻止程序在运行时重新加载Activity,除了设置"orientation",你还必须设置"ScreenSize"。
注意:
- 模拟器上跟真机验证结果可能会不一样。
- 不同版本的系统验证结果也可能不一样。
PS:在 API27 的模拟器上试了,只加orientation 没有加 ScreenSize时,即使切换设备的横竖方向也不会触发重建。
1.2 Activity的启动模式 / 16
为什么Activity需要启动启动模式?
- 为了满足不同的需求。给予更多的拓展性
默认的启动方式有点傻
四种启动模式:standard、singleTop、singeTask、singleInstance
1.2.1 Activity的LaunchMode / 16
为什么 Android 设计了多种启动模式?
为了适应更多的场景。
如何给 Activity 指定启动模式呢?
- 通过 AndroidManifest 为Activity 指定启动模式
- 在 Intent 中设置标志位来为 Activity 指定启动模式
两种指定方式的区别:
- 优先级上,intent 设置标志位的方式要高于在 manifest 文件中为 Activity 指定启动模式
- 在限定范围上有所不同,
- 比如,第一种方式无法直接为 Activity 指定 FLAG_ACTIVITY_CLEAR_TOP 标识
- 第二种方式无法为 Activity 指定 singleInstance 模式
也就是说 如果 Activity A 启动 Activity B,则 Activity B 可以在其清单文件中定义它应该如何与当前任务关联(如果可能),并且 Activity A 还可以请求 Activity B 应该如何与当前任务关联。如果这两个 Activity 均定义 Activity B 应该如何与任务关联,则 Activity A 的请求(如 Intent 中所定义)优先级要高于 Activity B 的请求(如其清单文件中所定义)。
注:某些适用于清单文件的启动模式不可用作 Intent 标志,同样,某些可用作 Intent 标志的启动模式无法在清单文件中定义。
1. standard
标准模式:(系统默认)。每次启动一个 Activity 都会新建一个新的实例。
谁启动了这个 Activity,那么这个 Act 就运行在启动它的那个 Activity 所在的那个栈中。
当我们用 ApplicationContext 去启动 standard 模式的 Activity 时会报错。
解决这个问题的方法是, 为带启动的Activity 指定 FLAG_ACTIVITY_NEW_TASK 标志位,这样启动时,就会为它新建一个任务栈。这时的 activity 其实是以 singleTask 模式启动的
在5.0和8.0的模拟器、8.0的真机(一加5t)中,使用 ApplicationContext 去启动standard模式的 Activity 也没有crash。
2. singleTop
栈顶复用。如果新 Activity 已经位于 任务栈的栈顶,那么此Activity不会被重建。同时它的 onNewIntent
方法会被回调。
- 通过 onNewIntent 方法的参数 我们可以取出当前请求的信息。
3. singleTask
栈内复用模式。一种单实例模式。只要 Activity 在一个栈中存在,就会复用这个 Activity ,同时它的 onNewIntent 方法会被回调。
- singleTask 默认具有 clearTop 效果,会导致栈内 所有在目标 Activity 之上的 Activity 全部出栈
我们假设目前有2个任务栈,前台任务栈的情况为AB,而后台任务栈的情况为CD,这里假设CD的启动模式均为singleTask。现在请求启动D,那么整个后台任务栈都会被加入到前台任务栈中,这个时候整个后退列表变成了ABCD。当用户按back键的时候,列表中的Activity会一一出栈。
如果是启动 C,那么后退列表就是 ABC。
也就是说,启动 singleTask 模式的 Activity 会把它下面的所有 Activity 也带到当前的栈中。
4. singleInstance
单实例模式:一种加强版的 singleTask 模式,具有此种模式的 Activity 只能单独地位于一个任务栈中。
singleTask 中提到某个 Activity 所需的任务栈,
何谓Activity 所需任务栈? 任务相关性
从一个参数说起:TaskAffinity (任务相关性)。该参数标识了一个 Activity 所需要的任务栈的名字,默认情况下,所有 Activity 所需的任务栈的名字为应用的包名。
- 如果要给 Activity 设定不同的任务栈,可把该属性值改为与包名不同的值。
TaskAffinity 主要与 singleTask 启动模式 、 allowTaskReparenting 属性配对使用,其他情况下无意义。
每个Activity都有TaskAffinity属性,这个属性指出了它希望进入的Task。如果一个Activity没有显式的指明该Activity的TaskAffinity,那么它的这个属性就等于Application指明的TaskAffinity,如果Application也没有指明,那么该TaskAffinity的值就等于包名。而Task也有自己的affinity属性,它的值等于它的根Activity的TaskAffinity的值。
如果加载某个Activity的intent,Flag被设置成FLAG_ACTIVITY_NEW_TASK时,它会首先检查是否存在与自己taskAffinity相同的Task,如果存在,那么它会直接宿主到该Task中,如果不存在则重新创建Task。
注:taskAffinity 的值至少要有一个 「.」。否则应用会因为解析出错而无法安装。
1.2.2 Activity的 Flags / 27
Activity 的 flag 有很多 ,这里主要分析一些比较常用的==标记位==。
标记位的作用
-
设定待启动的 Activity 的启动模式
- FLAG_ACTIVITY_NEW_TASK 《===》 singleTask 启动模式
- FLAG_ACTIVITY_SINGLE_TOP 《===》 singleTop 启动模式
- FLAG_ACTIVITY_CLEAR_TOP 通常与
FLAG_ACTIVITY_NEW_TASK
结合使用。一起使用时,通过这些标志,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent 的位置。
-
影响 Activity 的运行状态
-
FLAG_ACTIVITY_CLEAR_TOP
具有此标志位的 Activity,当它启动时,在同一个任务栈内所有位于其上的 Activity 都需要出栈。 一般 需要和 FLAG_ACTIVITY_NEW_TASK 配合使用 -
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有这个标记的 Activity 不会出现在历史 Activity 列表中,- 等价于在 XML 中指定 Activity 的属性
android:excludeFromRecents = "true"
大部分情况下,并不需要指定标记位,对于标记位只要理解即可。
使用时要注意 有些标记位是系统内部使用的,应用程序不需要手动设置这些标记位(以防止出现问题)
- 等价于在 XML 中指定 Activity 的属性
-
1.3 IntentFilter的匹配规则 / 28
“显式调用需要明确地指定被启动对象的组件信息,包括包名和类名,而隐式调用则不需要明确指定组件信息。原则上一个Intent不应该既是显式调用又是隐式调用,如果二者共存的话以显式调用为主。”
一个过滤列表中的 action、category、data可以有多个
IntentFilter 中的过滤信息有
- action、
- category、
- data
只有一个Intent同时匹配action类别、category类别、data类别才算完全匹配,只有完全匹配才能成功启动目标Activity。一个Activity 可以有多组 intent-filter,一个 Intent 只要匹配任何一组 intent-filter 即可成功启动对应的 Activity,
<activity
android:name=".MainActivity"
android:configChanges="locale|orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.SYNC"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
1. action 的匹配规则
如果过滤列表中有指定 action 的话,要求 Intent 中的 action 存在且必须和过滤规则中的其中一个 action 相同。
- ==注意: action 区分大小写==。
2. category 的匹配规则
intent 中的如果含有 category ,那么所有的 category 必须和过滤规则中的所有 category 相同。 即 「一一对应,==完全匹配==」。
为什么不设置 category 也能匹配呢?
- 原因:系统在 startActivity 或者 startActivityForResult 的时候,会默认为 Intent 加上
"android.intent.category.DEFAULT"
这个 category。 -
注:要使我们的 activity 能够接受隐式调用,就必须在 intent-filter 中指定
android.intent.category.DEFAULT"
这个 category。(不加的话因为系统默认给Intent添加了这样的一个 category 会导致不匹配)
3. data的匹配规则
data的匹配规则与 action 的匹配规则相类似。匹配其中的一组即可
data的语法如下
<intent-filter>
<data
android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="text/plain"/>
</intent-filter>
data 由 两部分组成,mimeType 和 URI。
- mimeType 指媒体类型,比如:image/jpeg
- URI 统一资源标识符
URI 的结构:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
栗子:
content://com.example.project:200/folder/subfolder/etc
https://www.baidu.com:80/search/info
每个数据的含义:
- Scheme: URI 的模式, eg: http、file、content。
- 默认值为 content 和 file
- Host: URI 主机名, eg: www.baidu.com
- Port: URI 中的端口号,
- path pathPattern 和 pathPrefix:这三个参数表述路径信息,
- path 表示完整的路径信息
- pathPattern 也表示完整的路径信息,但是它里面可以包含通配符 「* 」,「 *」表示 0 个 或者 多个任意字符。
- 由于 正则表达式 的规范, 如果想表示真实的字符串, 「* 」 要写成「\* 」, 「\ 」要写成「\\ 」。
- pathPrefix 表示路径的前缀信息
如果要在代码中为 Intent指定完整的 data,必须调用 setDataAndType 方法, setData 和 setType 方法会互相清除对方的值。
data 的特殊写法
可以在一个 data 标签中写出多个 scheme host port,也可以在不同行中分别写。
写法一
<data
android:host="www.baidu.com"
android:mimeType="text/plain"
android:scheme="http"/>
写法二
<data android:mimeType="video/mpeg"/>
<data android:scheme="http"/>
<data android:host="www.baidu,com"/>
开发中的 Tip
对于 Service 建议是,尽量使用显式调用方式来启动服务
通过 隐式启动一个 Activity 的时候,可以做一下判断,看是都存在 Activity 能够响应我们的隐式 Intent。
判断方法有两种:
- PackageManager 的
resolveActivity
方法- PackageManager 还提供了
queryIntentActivities
方法。 - 与
resolveActivity
的区别: 不是返回最佳匹配的 Activity,而是返回所有成功匹配的 Activity 信息。
- PackageManager 还提供了
- Intent 的
resolveActivity
方法
resolveActivity
找不到对应的 Activity 会返回 null。
public abstract List<ResolveInfo> queryIntentActivities(Intent intent, @ResolveInfoFlags int flags);
public abstract ResolveInfo resolveActivity(Intent intent, @ResolveInfoFlags int flags);
注意: 第二个参数,要使用 MATCH_DEFAULT_ONLY
这个标记位,如果没有,会把那些==没有在 intent-filter 中声明 <category android:name="android.intent.category.DEFAULT"/>
的 Activity 也匹配出来==,从而导致 startActivity
失败
- 含义是:仅匹配 那些在 intent-filter 中声明 了
<category android:name="android.intent.category.DEFAULT"/>
这个 category 的 Activity。
在 action 和 category 中,有一类 action 和 category 比较重要
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
这两者的共同作用是用来标明这是一个入口 Activity 并且会出现在 系统的应用列表中,少了任何一项都没有实际意义,也无法出现在系统列表中,也就是二者缺一不可。
如果一个应用中有n个 Activity 都在 manifest 文件中注明了上述的 intent-filter 那么应用列表会出现n个 该App 的图标。点击各个图标分别进入以相应 Activity 为主页的界面。
参考资料
- 官方文档
- 《Android 开发艺术探索》