阅读 210

Android 四大组件之 Activity 解析(上)

一、概述

简单来讲,Activity 就是一个可视化界面,负责承建一个屏幕窗口,防止 UI 组件,供用户交互。一般来说承建 Activity 有三个步骤:

  1. 承建 Activity 类;
  2. 在 AndroidManifest.xml 中注册;
  3. 设置布局文件(可选)。

二、Activity 的启动方法

2.1、显示启动

明确指定要启动的 Activity 的 class\color{red}{class} 或者 包名.activity类名\color{red}{包名.activity类名},显示启动主要有三种方式:

  • 方式一:class 跳转(最常用)
    Intent intent = new Intent(MainActivity.this, SecondActivity.class);
    startActivity(intent);
    复制代码
  • 方式二:包名.类名 跳转
    Intent intent = new Intent();
    intent.setClassName(MainActivity.this, "com.zjgsu.activitydemo.SecondActivity");
    startActivity(intent);
    复制代码
  • 方式三:ComponentName 跳转
    Intent intent = new Intent();
    intent.setComponent(new ComponentName(MainActivity.this, SecondActivity.class));
    startActivity(intent);
    复制代码

2.2、隐式启动

设置启动过滤器,通过指定的 action\color{red}{action}actiondata\color{red}{action 和 data} 属性,系统会查找符合条件的 Activity,并启动它,隐私启动主要有两种方式:

  • 方式一:传入 actionName
    Intent intent = new Intent("abcd.SecondActivity");
    startActivity(intent);
    复制代码
  • 方式二:设置 action
    Intent intent = new Intent();
    intent.setAction("abcd.SecondActivity");
    startActivity(intent);
    复制代码

注意:如果自己定义的某个 Activity 要通过隐式启动,在 AndroidManifest.xml 中必须加上 android.intent.category.DEFAULT,否则不起作用。

 <activity android:name=".SecondActivity">
     <intent-filter>
         <action android:name="abcd.SecondActivity"/>
         <category android:name="android.intent.category.DEFAULT"/>
     </intent-filter>
 </activity>
复制代码

2.2.1、隐式启动如果两个 activity 的 actionName是一样的会怎样呢?

如下所示,我创建了两个 Activity,然后这两个 Activity 的 actionName 我设置成一样的,然后我通过隐式启动 Activity,会怎样呢?

<activity android:name=".SecondActivity"
   android:label="第二个界面">
    <intent-filter>
        <action android:name="abcd.SecondActivity" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

<activity android:name=".ThirdActivity"
    android:label="第三个界面">
    <intent-filter>
        <action android:name="abcd.SecondActivity" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
复制代码

运行效果如下所示:

隐式启动Activity 看上面的 gif 动图我们就可以看出来,这种情况下 Android 会让我们选择要运行的 Activity。即当有多个 Action 匹配隐式匹配的条件时,将会弹出选择框供用户选择。

三、Activity 的生命周期

我们就以下面这张 Activity 生命周期图为例子吧:


一个 Activity 启动的时候会依次调用 onCreate() --> onStart() --> onResume() --> onPause() --> onStop() --> onDestroy()。当执行到 onResume() 的时候这个 Activity 就变成了可操作状态。

3.1、单 Activity 生命周期的调用顺序

  • onCreate()    创建 Activity 时调用
  • onStart()    当 Activity 界面变为用户可见时调用
  • onResume()    当 Activity 界面获取到焦点时调用(界面按钮可点击,文本框可输入等)
  • onPause()    当 Activity 界面失去焦点时调用(界面按钮不可点击,文本框不可输入等)
  • onStop()    当 Activity 界面变为用户不可见时调用
  • onDestroy()    当 Activity 被销毁时调用
  • onRestart()    当 Activity 再次启动时调用

3.2、多 Activity 生命周期的调用顺序

多Activity的生命周期如下图所示: 多Activity生命周期 上面那张图的操作流程:打开 A activity,点击按钮启动 B activity,在 B activity 中点击返回键。代码运行截图如下: 模拟

四、Activity 的启动模式

Activity 的启动模式决定了新生产的 Activity 实例是否重用已存在的 Activity 实例,是否和其他 Activity 实例共用一个 Task。Task 是一个具有栈结构的对象,一个 Task 可以管理多个 Activity,启动一个应用,也就创建一个与之对应的 Task。

4.1、四种启动模式

  1. standard 默认的启动模式,每次激活 Activity 时(startActivity),都创建 Activity 实例,并放入任务栈;
  2. singleTop 每次激活 Activity 时,判断该 Activity 实例是不是在栈顶,如果是,不需要创建,否则需要创建 Activity 实例;
  3. singleTask 如果要激活的那个 Activity 在任务栈中已经存在了,则不需要创建,只需要把此 Activity 以上的 Activity 实例都出栈,这个时候此 Activity 就到栈顶了,若不存在,就创建 Activity 实例;
  4. singleInstance 只有一个实例,并且这个实例独立运行在一个 Task 中,这个 Task 只有这个实例,不允许有别的 Activity 实例存在。

4.2、四种启动模式代码演示

4.2.1、standard

我们先来看下效果:
在这里插入图片描述 可以看到,在 SecondActivity 里面再次启动 SecondActivity,standard 模式下的启动方式会再次创建 SecondActivity 实例。使得任务栈中包含了两个 SecondActivity 实例,所以我们点击返回键只是返回了上一个 SecondActivity,再次按返回键才回到 MainActivity。

主要代码如下所示:

// 启动模式是 standard,不填默认就是 standard
<activity android:name=".SecondActivity"
	android:launchMode="standard"/>

// SecondActivity
public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        findViewById(R.id.btn_start_self).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(SecondActivity.this, SecondActivity.class));
            }
        });
    }
}
复制代码

4.2.2、singleTop

我们还是用上面的例子来演示,只是把 SecondActivity 的启动模式改成 singleTop,我们来看下效果:
在这里插入图片描述 可以看到,这个时候在 SecondActivity 里面再次启动自己,是不会创建 SecondActivity 实例的,应该这个时候 SecondActivity已经在栈顶了,而 singleTop 启动模式会判断该 Activity 实例是不是在栈顶,如果是,不需要创建,否则才创建 Activity 实例。

主要代码如下所示:

<activity android:name=".SecondActivity"
	android:launchMode="singleTop"/>
复制代码

问题1:如果这时候我们再创建一个 ThirdActivity,在 MainActivity 启动 SecondActivity,再在 SecondActivity 里面启动 ThirdActivity,然后在 ThirdActivity 里面启动 SecondActivity,这时候任务栈中 Activity 的实例顺序是怎样的?

:任务栈中自底向上的 Activity 实例顺序为 MainActivity-->SecondActivity-->ThirdActivity-->SecondActivity。

4.2.3、singleTask

我们用上面的问题的例子来演示,只是把 SecondActivity 的启动模式改成 singleTask,我们来看下效果:
在这里插入图片描述 这个时候任务栈中自底向上的 Activity 实例顺序为 MainActivity–>SecondActivity。因为 SingleTask 启动模式如果栈中存在 Activity 实例会把该 Activity 以上的所有 Activity 实例全部出栈。

主要代码如下所示:

<activity android:name=".SecondActivity"
	android:launchMode="singleTask"/>
复制代码

4.2.4、singleInstance

singleInstance 启动模式和 singleTask 启动模式有点像,都会保证任务栈中只有同一个 Activity 的实例,但是 singleInstance 会创建一个新的 Task。要想演示 singleInstance,我们需要把每个 Activity 所在的栈的 ID 打印出来。例子同上,只是修改启动模式为 singleInstance。

打印日志如下所示: singleInstance 我们看到 SecondActivity 所在任务栈的 TaskId 是592,而 MainActivity 和 ThirdActivity 所在任务栈的 TaskId 是 591。这就说明 SecondActivity 运行在一个独立的任务栈里面,这个任务栈里面只有 SecondActivity 这一个 Activity 实例。打印 TaskId 的方法如下:

Log.e("activityDemo2TAG", "ThirdActivity所在的task的id为:" + getTaskId());
复制代码

五、利用 IntentFlag 设置 Activity 的启动方式

讲解这个知识点之前我们先来看一下 Task 和 taskAffinity 这两个概念、

5.1、Task 基本概念

  • Task 是一个具有栈结构的容器,可以放置多个 Activity 实例;
  • 启动一个应用,系统会为之创建一个 Task,来放置根 Activity;
  • 一个 Activity 启动另一个 Activity 时,默认情况下两个 Activity 是放置在同一个 Task 中的,后者被压入前者所在的 Task 栈,当用户按下返回键,后者从 Task 中被弹出,前者又显示在栈顶。

5.2、taskAffinity 基本概念

  • 定义了 Activity 实例想要进入的 Task;
  • 如果一个 Activity 没有显示的指明该 Activity 的 taskAffinity 属性,那么它的这个属性就等于 Application 所指明的 taskAffinity,如果 Application 也没有指明,那么该 taskAffinity 的值就等于包名。

5.3、IntentFlag 的常用值

IntentFlag 的种类很多,我们这里就只挑选几个平时常用的来讲解一下。

5.3.1、FLAG_ACTIVITY_NEW_TASK

系统会寻找或创建一个新的 Task 来放置目标 Activity,寻找时依据目标 Activity 的 taskAffinity 属性进行匹配,如果找到一个 Task 的 taskAffinity 与之相同,就将目标压入此 Task 中,如果查找无果,则创建一个新的 Task,并将该 Task 的 taskAffinity 值设置为目标 Activity 的 taskAffinity,将目标 Activity 放置于此 Task 中 。

5.3.1.1、代码演示

设置 IntentFlag 的方法:

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
复制代码

第一种情况\color{red}{第一种情况}:以 FLAG_ACTIVITY_NEW_TASK 方式启动 SecondActivity,但是 SecondActivity 不添加 taskAffinity 属性,我们来看下效果: 效果 可以看到,我们虽然以 FLAG_ACTIVITY_NEW_TASK 启动,但是两个 Activity 所在的 Task 还是同一个,这是为什么呢?那是因为如果一个 Activity 没有显示的指明该 Activity 的 taskAffinity 属性,那么它的这个属性就等于 Application 所指明的 taskAffinity,如果 Application 也没有指明,那么该 taskAffinity 的值就等于包名。 也就是说此时 SecondActivity 和 MainActivity 两个 taskAffinity 的值都是包名,所以 SecondActivity 肯定会被放入 MainActivity 所在的栈中了。

第二种情况\color{red}{第二种情况}:以 FLAG_ACTIVITY_NEW_TASK 方式启动 SecondActivity,并且 SecondActivity 设置 taskAffinity 属性:

<activity android:name=".SecondActivity" 
	android:taskAffinity="flag.newIntent.test"/>
复制代码

我们来看下效果: 效果二 可以看到这时候 SecondActivity 和 MainActivity 已经不在同一个栈里面了。

5.3.2、FLAG_ACTIVITY_SINGLE_TOP

同四种启动模式中的 singleTop,这里就不演示了。

5.3.3、FLAG_ACTIVITY_CLEAR_TOP

同四种启动模式中的 singleTask,这里也不演示。

5.3.4、FLAG_ACTIVITY_REORDER_TO_FRONT

这个启动模式的意思是如果栈中已经存在 Activity 实例,会将它拿到栈顶,不会启动新 Activity,也不会删除它之上的 Activity 实例。

5.3.4.1、代码演示

我们先来看下效果:
在这里插入图片描述 我们在 MainActivity 里面启动 SecondActivity,然后在 SecondActivity 里面启动 ThirdActivity,然后再在 ThirdActivity 里面通过 FLAG_ACTIVITY_REORDER_TO_FRONT 的方式启动 SecondActivity,这个时候因为栈中已经有 SecondActivity 实例了,所以会把该实例拿到栈顶,并且不会销毁 SecondActivity 之上的所有实例,所以此时栈自底向上的 Activity 实例顺序是 MainActivity --> ThirdActivity --> SecondActivity。

六、小结

这篇文章主要讲了 Activity 的五种启动方法,包括三种显示启动和两种隐式启动,又讲了单和多 Activity 的生命周期,以及在 AndroidManifest 中通过 lunchMode 设置 Activity 的启动模式和通过Intent.setFlag() 方法设置 Activity 的启动模式。

下一篇文章我们会讲解在 Activity 中利用 Intent 传参利用Bundle传递数据复杂数据的传递以及启动系统 Activity的方法

具体可以看Android 四大组件之 Activity 解析(下)