Android 四大组件 之 Activity

子曰:温故而知新,可以为师矣。 《论语》-- 孔子


一、 Activity 生命周期

先来放一张最经典的图

在这里插入图片描述

1.1 典型情况下生命周期分析

  • onCreateActivity 生命周期的第一步。此方法中,常见操作有 setContView() 加载布局资源;业务需求需要的数据做初始化操作,例如 setText()setImage() 等等。
  • onRestart():表示某一个后台 Activity 重新切换到了前台,即从后台不可见状态变成前台可见状态。
  • onStart()Activity 正在被启动,后台可见,但并未出现在前台,即相对用户不可见,用户无法与此 Activity 界面交互。
  • onResume()Activity 可见且出现在前台,用户可交互。
  • onPause()Activity 正在停止,正常情况下,紧接着就会调用 onStop() 方法。此方法中,可以做一些存储数据,停止动画等操作,但是不能太耗时。
  • onStop()Activity 处于停滞状态,可以做一些稍微重量级的操作,但是也不能太耗时。
  • onDestroy()Activity 即将被销毁,可以做一些回收工作和最终资源的释放。

不同场景下的生命周期

1) 第一次启动某一个 Activity:
onCreate --> onStart --> onResume
2) Activity A 跳转到 Activity B(不是透明的 Activity) :
onPause(Activity A)--> onCreate(Activity B)--> onStart(Activity B)--> onStop(Activity A)--> onResume(B)
3)Activity A 跳转到 Activity B(透明的 Activity
onPause(Activity A)--> onCreate(Activity B)--> onStart(Activity B)--> onResume(B)
4)后台 Activity 进入 前台(后台 Activity 并未因为系统内存不足被杀死):
onRestart --> onStart --> onResume
5)后台 Activity 因为系统内存不足被杀死,重新启动该 Activity:
onCreate --> onStart --> onResume
6)锁屏,开屏 :
锁屏  onPause --> onStop 
开屏  onStart --> onResume
7)用户点击 back 键 :
onPause --> onStop --> onDestroy

上面列举了一些常见的场景下生命周期的变化,在此做一个小结:
  • 从整个生命周期来看,onCreateonDestroy 是配对的,分别标识着 Activity 的创建和销毁,并且只可能有一次调用;
  • Activity 是否可见,onStartonStop 是配对的,随着用户操作或者设备点亮熄灭,可能被调用多次;
  • Activity 是否在前台来说,onResumeonPause 是配对的,随着用户操作或者设备点亮熄灭,可能被调用多次。

1.2 异常情况下生命周期分析:

1)资源相关的系统配置发生改变导致 Activity 被杀死并重新创建(常见举例 :横竖屏切换)。
 // onPause 和  onSaveInstanceState 的执行顺序,谁都可能在前
 // 官方建议在 onRestoreInstanceState 方法进行数据恢复。
 onPause --> onSaveInstanceState --> onStop --> onDestroy --> onCreate --> onRestoreInstanceState
2)资源内存不足导致优先级低的 Activity 被杀死。
  • 前台 Activity :正在和用户交互的 Activity,优先级最高。
  • 可见但非前台 Activity :比如 Activity 中弹出了一个对话框,导致 Activity 可见但是位于后台无法和用户直接交互。
  • 后台 Activity :已经被暂停的 Activity,比如执行了 onStop,优先级最低。

1.3 ConfigChanges

如果不想要在系统配置发生改变时,重新创建 Activity ,可以给 Activity 指定 configChanges 属性。

下面列举几个常见的 configChangs 的属性:

项目 含义
locale 设备的本地位置发生了改变,一般指切换了语言系统
keyboardHidden 键盘的可访问性发生了改变,比如用户调用出了键盘
orientation 屏幕方向发生了改变,比如旋转手机屏幕。
screenSize 当屏幕尺寸信息发生了变化,这个选项比较特殊,它和编译选项有关,当编译选项中的 minSdkVersion 和 targetSdkVersion 均低于13时,此选项不会导致 Activity 重启,否则会导致 Activity 重启

具体做法,在AndroidMinfest.xml 文件中,给不想要因为系统配置发生改变而导致 Activity 重建的 Activity 添加如下代码:

// 如果 MiniSdkVersion 和 TargetSdkVersion 属性大于等于13的情况下,需要同时设置 screenSize 和 orientation
android:configChanges="screenSize|orientation"

二、 Activity 启动模式

在默认情况下,当我们多次启动同一个 Activity 时,系统会创建多个实例,并把它们一一放入任务栈中,那么多次启动同一个 Activity,系统就会重复创建多个实例,这是一个很傻的行为,所以提供了启动模式来修改系统的默认行为。

目前有4种启动模式:

standardsingleTopsingTasksingleInstance


2.1 standard 模式

  • 标准模式,也是系统默认模式。
举例说明:
  1. 创建 BaseActivity 基类。
  2. MainActivity 继承基类,StandardActivity 类继承基类,同时设置 lanchmodestandard
  3. MainActivity 跳转到 StandardActivity ,在 StandardActivity 类中,点击三次按钮,做跳转自身操作。
  4. 查看 Log 日志。
BaseActivity 代码如下:
public class BaseActivity extends AppCompatActivity {

    public static final String TAG = "TAG";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.i(TAG, "===============onCreate()方法==");

        Log.i(TAG, "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());

        dumpTaskAffinity();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.i("TAG", "===============onNewIntent()方法==");
        Log.i("TAG", "onNewIntent:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
        dumpTaskAffinity();
    }

    private void dumpTaskAffinity() {
        try {
            ActivityInfo info = this.getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
            Log.e(TAG, "taskAffinity:" + info.taskAffinity);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Log 日志输出 :
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 28 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:StandardActivity TaskId: 28 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:StandardActivity TaskId: 28 hasCode:183570408
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:StandardActivity TaskId: 28 hasCode:78785677
E/TAG: taskAffinity:com.example.activitylanuchmode

总结:

  1. StandardActivity 做跳转自身三次操作,日志显示了三个不同的 hasCode 的值, 推出结论: 每次启动一个 Activity 都会重新创建一个新的实例,不管这个实例是否存在。

  2. 日志输出了三次 onCreate 方法,推出结论:被创建的实例的生命周期符合典型情况下的生命周期,它的 onCreateonStartonResume 都会调用。

  3. MainActivityStandardAcitivtytaskAffinity 值一样,推出结论: 在这种模式下,谁启动了这个 Activity,那么这个 Activity 就运行在启动它的那个 Activity 所在的栈中。


2.2 singleTop

  • 栈顶复用模式。
通俗易懂的说一下此模式:

目前栈内情况为 ABCD 四个 Activity,A 位于栈底,D 位于栈顶,假设需要再次启动 D,如果 D 的启动模式是 singleTop ,那么栈内的情况还是 ABCD,如果 D 的启动模式 standard,那么栈内的情况是 ABCDD。

举例说明:
  1. 创建 SingleTopActivitylanchmodesingleTop),OtherActivitylanchmodestandard)。
  2. MainActivity 跳转到 SingleTopActivity ,SingleTopActivity 跳转到 OhterActivity 跳转到 SingleTopActivitySingleTopActivity 自身跳转三次。
  3. 查看 Log 日志。
Log 日志输出:
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 29 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTopActivity TaskId: 29 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:OtherTopActivity TaskId: 29 hasCode:243048765
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTopActivity TaskId: 29 hasCode:227387684
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTopActivity TaskId: 29 hasCode:227387684
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTopActivity TaskId: 29 hasCode:227387684
E/TAG: taskAffinity:com.example.activitylanuchmode

总结:

  1. 第一次创建 SingleTopActivity时,回调了 onCreate 方法,taskId 值一样,推出结论: 当前栈中不存在该 Activity 的实例时,其行为同 standard 启动模式。

  2. OhterTopActivity 返回到 SingleTopActivity 时,从日志看出 onCreate 方法 被调用,且 hasCode 值 与 SingleTopActivity 第一次创建时 hasCode 值不一样,推出结论: 当前栈中已有该 Activity 的实例但是该实例不在栈顶时,其行为和 standard 启动模式一样,依然会创建一个新的实例。

  3. SingleTopActivity 做跳转自身三次操作的 hasCode 值都一样,推出结论: 当前栈中已有该 Activity 的实例并且该实例位于栈顶时,不会新建实例,而是复用栈顶的实例,并且会将 Intent 对象传入,回调 onNewIntent 方法。


2.3 singleTask

  • 栈内复用模式

在讲解此模式前,先说一下什么是 Activity 的任务栈 ?

前文提及过 taskAffinity 这个参数,可以翻译为 任务相关性。 这个参数标识了一个 Activity 所需要的任务栈的名字。

默认情况下,所有的 Activity 所需要的任务栈的名字为应用的包名。
当然,我们也可以为每一个 Activity 单独指定该属性。

设置了相同 taskAffinity 属性的 Activity 在同一个任务栈中。如果你为某一个ActivitytaskAffinity 属性设置为空字符,那么表示该 Activity 不属于任何task。

standardsingleTop 启动模式都是在原任务栈中新建 Activity 实例,不会启动新的Task,即使你指定了 taskAffinity 属性。


通俗易懂的说一下此模式:

当一个具有 singleTask 模式的 Activity 请求启动后,例如 Activity A,系统首先会寻找是否存在 A 想要的任务栈,如果不存在,就重新创建一个任务栈。然后创建 A 的实例,把 A 放入栈中。如果存在 A 所需的任务栈,这时要看 A 是否在栈中有实例存在,如果有实例存在,系统会把A调到栈顶并调用 onNewIntent() 方法,如果实例不存在,就创建 A 的实例并把 A 压入栈中。


举例说明:
  1. 目前任务栈 S1 的情况是 ABC,这个时候 Activity DsingleTask 模式请求启动,所需要的任务栈为 S2,由于 S2 和 D 的实例都不存在,所以系统会先创建任务栈 S2,然后在创建 Activity D 的实例并将其入栈 S2。
  2. 假设 Activity D 所需要的任务栈为 S1,其它如1所示,那么由于 S1 已经存在,那么系统会直接创建 D 的实例并将其压入栈 S1。
  3. 如果 D 需要的任务栈是 S1,且当前 S1 的情况是 ADBC,A栈底,C栈顶,根据栈内复用原则,此时 D 不会被重新创建,系统会把D切换到栈顶并调用 onNewIntent 方法,同时由于 singleTask 默认具有 clearTop 方法,会导致栈内所有的在 D 上面的 Activity 全部出栈。

代码验证:
  1. 创建 SingleTaskActivity,启动模式为 singleTask,同时创建 OtherTaskActivity
  2. 做以下操作:MainActivity 跳转到 SingleTaskActivitySingleTaskActivity 跳转到 OtherTaskActivityOtherTaskActivity 回跳到 SingleTaskActivitySingleTaskActivity 点击跳转自身按钮两次。
  3. 查看 Log 日志。

Log 日志输出:
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 31 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:OtherTaskActivity TaskId: 31 hasCode:243048765
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode

上面的操作都是在未给 SingleTaskActivity 指定 taskAffinity 值的情况下,根据日志:
OtherActivity 跳回 SingleTaskActivitySingleTaskActivity 并未新建,复用了该实例,由 hasCode 值可看出,且调用了 onNewIntent 方法。同时,OhterActivity 出栈了,可以通过 命令adb shell dumpsys activity ,在显示的内容中查看 Running activities 这一块显示区域。


下面,我们给 SingleTaskActivity 指定一下 taskAffinity 的值。注意,这个属性的值时字符串,且中间必须包含分隔符.

<activity
            android:name=".SingleTaskActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.example.activitylanuchmode.singletask"
            />

重复之前的操作,再来看一下日志:

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 33 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask

I/TAG: ===============onCreate()方法==
I/TAG: onCreate:OtherTaskActivity TaskId: 34 hasCode:243048765
E/TAG: taskAffinity:com.example.activitylanuchmode

I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask

I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask

从日志中可以看到区别就是 taskAffinity 的值不同了,说明 SingleTaskActivity 所属的任务栈不同于 MainActivity了。

总结

  • singleTask 模式的 Activity 启动时,首先根据 taskAffinity属性查找当前是否存在一个对应名字的任务栈。
  • 若不存在该属性的任务栈,则会创建一个新的Task,并创建新的 Activity 实例入栈到新创建的 Task 中去。
  • 若存在该属性的任务栈,则查找该任务栈中是否存在该 Activity 实例。
  • 若存在该实例,则将它上面的所有 Activity 实例都出栈,然后回调启动的 Activity 实例的 onNewIntent方法
  • 若不存在该实例,则新建 Activity 入栈。

2.4 singleInstance

  • 单实例模式,加强版 singleTask
简单说明一下此模式

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

举例说明

比如 Activity AsingleInstance 模式,那当 A 启动后,系统会为它创建一个新的任务栈,然后 A 独自在这个任务栈中,由于栈内复用特性,后续的请求都不会创建新的 Activity,除非这个独特的任务栈被系统销毁了。


2.5 Flags 标记位

Activity 的标记位有很多,这里说一些常用的

  • FLAG_ACTIVITY_NEW_TASK : 为 Activity 指定 “singleTask” 启动模式,其效果和在xml中指定该启动模式相同。
  • FLAG_ACTIVITY_SINGLE_TOP : 为 Activity 指定 “singleTop” 启动模式, 其效果和在xml中指定该启动模式相同。
  • FLAG_ACTIVITY_CLEAR_TOP : 启动有此标识的 Activity,在同一个任务栈中,所有位于它上面的 Activity 都要出栈。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS : 有此标识的 Activity 不会出现在历史 Activity 列表中,等同于在XML中指定 Activity 的属性 android:excludeFromRecents="true"

至于 Activityflag 如何使用呢?

前面介绍了四种启动模式,我们给 Activity 指定启动模式都是在 AndroidMenifest.xml 文件中设置的,其实还有一种方法,通过在 Intent 中设置,举例设置 singleTask,代码如下:

Intent intent = new Intent();
intent.setClass(this,SingleTaskActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

三、IntentFilter 匹配规则

隐式调用需要 Intent 能够匹配目标组件的 IntentFilter 中所设置的过滤信息,所以我们需要对 IntentFilter 的过滤信息有所了解。

IntentFilter 的过滤信息有 actioncategorydata

几个要点说明:
  • 一个 Intent 只要能匹配任意一组 intent-filter 即可成功启动对应的 Activity
  • 一个过滤列表中的 actioncategorydata 可以有多个。例如下面:
<activity android:name=".MainActivity"
    android:configChanges="screenSize|orientation">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <action android:name="android.intent.action.MAIN"/>

        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>
  • 一个 Activity 可以有多个 intent-filter(过滤列表)。例如下面:
<activity android:name=".MainActivity"
    android:configChanges="screenSize|orientation" >
        
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <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_MULTIPL>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
            
</activity>

好了,大概的说了一下 IntentFilter 的几个小的注意点,下面来具体说一说那三个过滤信息。


3.1 Action 的匹配规则

  • action 是一个字符串。
  • action 区分大小写。
  • Intent 中必须存在一个 action
  • Intent 中的 actionintent-filter 中的 任意一个 action 的字符串值完全相同,则 action 匹配成功。

3.2 category 的匹配规则

  • category 是一个字符串。
  • Intnet 中的 category 必须和 intent-filter 中的 category 相同。
  • 如果 Intent 没有 category,那么为了 activity 能够被隐式接收,就需要在 intent-filter中添加 "android.intent.category.DEFAULT"

3.3 `data 的匹配规则

首先看一下 data 的结构:

<data android:scheme="String"
      android:host="String"
      android:path="String"
      android:pathPattern="String"
      android:pathPrefix="String"
      android:mimeType="String"

data 由两个部分组成:mimeTypeURL

mimeType: 媒体类型,例如 imgjpeg等。

URL:

<Scheme>://<host>:<pot>/[<path>|<pathPrefix>|<pathPattern>]

// 解释说明:
Scheme: URL 模式。例如 http、file、content
Host: URL 主机名。例如 www.baidu.com
Port: URL 端口号。例如 80
Path、pathPrefix、pathPattern: 表示路径信息。

data 的匹配规则与 action 的匹配规则类似。

  • Intent 中必须有一个 data
  • Intent 中的 data 必须完全匹配 intent-filter 中的 data

举例说明
<intent-fileter
   <data android:mimeType="image/*"/>
   ...
</intent-filter>

上述规则制定了媒体类型为所有类型的图片,没有指定 URL,但是 URL 有默认值:contentfile

如何匹配上述过滤信息,如下:

intent.setDataAndType(Uri.parse("file://abc","image/png"));

有一类 actioncategory 比较重要:

<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

如上的二者缺一不可,表明这是一个入口 Activity ,并且会出现在系统的应用列表中。

好了,关于 IntentFilter 的过滤规则就说完了,下面给出一个完整的举例说明:

过滤规则:
<activity android:name=".DemoActivity"
   android:configChanges="screenSize|orientation">
    <intent-filter>
        <action android:name="com.example.intentfilter.demo"/>
        <category android:name="android.intent.category.demo"/>
        <data android:mimtType="text/plain">
    </intent-filter>
</activity>
匹配:
Intent intent = new Intent("com.example.intentfilter.demo");
intent.addCategory("com.example.category.demo");
intent.setDataAndType(Uri.parse("file://abc"),"text/plain");
startActivity(intent);


写在文末

纸上得来终觉浅,绝知此事要躬行。 《冬夜读书示子聿》-- 陆游

至此,Activity 的基础知识点算是梳理了一遍,各位看官食用愉快。


码字不易,如果本篇文章对您哪怕有一点点帮助,请不要吝啬您的点赞,我将持续带来更多优质文章。

发布了7 篇原创文章 · 获赞 7 · 访问量 3130

猜你喜欢

转载自blog.csdn.net/wild_onlyking/article/details/104328690