Activity的生命周期和启动模式

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)也仍然会保存啊。

  1. 扩展 Fragment 类并声明对有状态对象的引用。
  2. 在创建片段后调用 setRetainInstance(boolean)
  3. 使用 FragmentManager 将片段添加到 Activity。
  4. 重启 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 按照优先级从高到低,可以分为如下三种:

  1. 前台 Activity —— 正在和用户交互的Activity
  2. 可见但非前台 Activity ——比如弹出了一个对话框
  3. 后台 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"。

注意:

  1. 模拟器上跟真机验证结果可能会不一样。
  2. 不同版本的系统验证结果也可能不一样。

PS:在 API27 的模拟器上试了,只加orientation 没有加 ScreenSize时,即使切换设备的横竖方向也不会触发重建。

1.2 Activity的启动模式 / 16

为什么Activity需要启动启动模式?

  • 为了满足不同的需求。给予更多的拓展性

默认的启动方式有点傻

四种启动模式:standard、singleTop、singeTask、singleInstance

1.2.1 Activity的LaunchMode / 16

为什么 Android 设计了多种启动模式?

为了适应更多的场景。

如何给 Activity 指定启动模式呢?

  1. 通过 AndroidManifest 为Activity 指定启动模式
  2. 在 Intent 中设置标志位来为 Activity 指定启动模式

两种指定方式的区别:

  1. 优先级上,intent 设置标志位的方式要高于在 manifest 文件中为 Activity 指定启动模式
  2. 在限定范围上有所不同,
    • 比如,第一种方式无法直接为 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"
        大部分情况下,并不需要指定标记位,对于标记位只要理解即可。
        使用时要注意 有些标记位是系统内部使用的,应用程序不需要手动设置这些标记位(以防止出现问题)

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。
判断方法有两种:

  1. PackageManager 的 resolveActivity 方法
    • PackageManager 还提供了 queryIntentActivities 方法。
    • resolveActivity 的区别: 不是返回最佳匹配的 Activity,而是返回所有成功匹配的 Activity 信息。
  2. 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 为主页的界面。

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容