在Android开发的绿洲中,四大组件犹如皇冠上的明珠,而Activity则是其中最引人注目的那一颗。作为用户体验的视觉入口,每次我们打开应用、切换界面,都离不开Activity的身影。但话虽如此,Activity并非是一个简单的概念,它深藏着Android系统许多精妙绝伦的设计,值得我们仔细探究。今天,就让我带你领略一番Activity的前世今生!


一、Activity 的基本介绍

1、Activity之根:三件宝

要理解什么是Activity,首先需要了解它构建的三大基石:Context、Window和View层级。

这些组件构成了 Android 应用程序的基本框架,它们之间的关系和作用如下:

(1)、Context:Context 是一个接口,提供了应用程序环境的全局信息。它允许应用程序访问资源和生命周期状态,是几乎所有其他组件的基础。

(2)、Window:Window 是一个抽象类,代表了一个用户界面的一部分。它负责管理视图的布局和绘制,是 View 层级的顶级容器。

(3)、View 层级:View 是 Android UI 组件的基类,代表屏幕上的一个元素。ViewGroup 是 View 的子类,可以包含其他 View 对象,从而构建起整个 UI 层级。


下面的 Java 代码示例,演示了如何使用这些基石创建一个基本的 Android 应用程序界面:

import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 使用 Context (this) 来设置全屏和无标题栏
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                             WindowManager.LayoutParams.FLAG_FULLSCREEN);

        // 使用 Context (this) 创建布局
        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.setGravity(Gravity.CENTER); // 使用 Context 来设置布局的对齐方式

        // 使用 Context (this) 创建 Button
        Button button = new Button(this);
        button.setText(R.string.button_text); // 使用 Context 引用资源文件
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 使用 Context (this) 来显示 Toast 消息
                Toast.makeText(MainActivity.this, R.string.toast_text, Toast.LENGTH_SHORT).show();
            }
        });

        // 使用 Context (this) 创建 TextView
        TextView textView = new TextView(this);
        textView.setText(R.string.text_view_text); // 使用 Context 引用资源文件

        // 将 Button 和 TextView 添加到布局中
        layout.addView(button);
        layout.addView(textView);

        // 设置布局为 Activity 的内容视图
        setContentView(layout);
    }
}

Context 在 Android 应用中通常是隐式传递的,尤其是在 Activity 类中。在 ActivityonCreate() 方法中,this 关键字本身就是一个 Context 对象,它代表了当前的 Activity 实例。


以上就是一个标准Activity的创建过程。可以看到,我们通过setContentView为Window指定了根视图,这个根视图最终会贯穿整个UI层级结构。


2、Activity 的作用


在 Android 应用开发中,Activity 是一个非常重要的组件,它是用户界面的一部分,用于显示用户可以与之交互的屏幕。每个 Activity 都代表一个单独的屏幕,可以包含各种视图(View)和视图组(ViewGroup),用于构建用户界面。


Activity 的主要作用包括:

(1)、显示用户界面Activity 可以包含各种 View 对象,如按钮、文本框、图片等,这些对象组成了用户界面。

(2)、处理用户交互:用户在 Activity 上执行的操作(如点击按钮、输入文本等)可以通过事件监听器进行处理。

(3)、管理生命周期Activity 有其自己的生命周期,它在不同的状态下(如运行、暂停、停止、销毁)会执行不同的操作。

(4)、启动和销毁Activity 可以被创建(启动)和销毁,以响应用户的操作或系统的需求。


下面是一个简单的 Activity 示例,演示了 Activity的主要作用:

import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MyActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 设置 Activity 的布局文件
        setContentView(R.layout.activity_my);

        // 获取布局文件中定义的 Button 对象
        Button myButton = findViewById(R.id.my_button);

        // 为 Button 设置点击事件监听器
        myButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 当按钮被点击时,显示一个 Toast 消息
                Toast.makeText(MyActivity.this, "Button Clicked!", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

在这个示例中:

  • MyActivity 继承自 Activity 类,它是一个 Activity 类的子类。

  • onCreate() 方法是 Activity 生命周期中的一个回调方法,当 Activity 被创建时会被调用。

  • setContentView() 方法用于设置 Activity 的布局文件,该文件定义了 Activity 的用户界面。

  • findViewById() 方法用于获取布局文件中定义的 View 对象,这里获取了一个 Button 对象。

  • setOnClickListener() 方法为 Button 设置了一个点击事件监听器,当按钮被点击时,会调用 onClick() 方法,并显示一个 Toast 消息。


此外,为了使上述代码正常工作,你需要在项目的 res/layout/activity_my.xml 文件中定义相应的布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

    <Button
        android:id="@+id/my_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click Me" />

</LinearLayout>

二、生命周期:掌控Activity的万千世界

Android Activity 的生命周期由一系列回调方法(也称为生命周期方法)组成,这些方法在 Activity 的不同状态之间转换时由 Android 系统自动调用。理解这些生命周期方法对于正确管理 Activity 的状态和资源至关重要。


Android世界的入口-深度解锁Activity的秘密-LMLPHP


以下是 Activity 生命周期的主要方法,以及它们被调用的顺序:

  1. onCreate(Bundle savedInstanceState)- 当Activity` 第一次被创建时调用。
  2. onStart() - Activity 变得可见时调用。
  3. onResume() - Activity 准备与用户交互时调用。
  4. onPause() - Activity 部分失去焦点,但仍然可见时调用,通常用于保存状态。
  5. onStop() - Activity 不再可见时调用。
  6. onRestart() - Activity 从停止状态返回到启动状态时调用。
  7. onDestroy() - Activity 被销毁前调用。

以下是一个简单的 Java 代码示例,演示了如何重写这些生命周期方法,并在控制台打印出相应的状态:

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class LifecycleActivity extends Activity {
    private static final String TAG = "LifecycleActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
        // 设置布局等初始化操作
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }
}

在 Android 开发中,控制台日志输出是通过 Logcat 来查看的。当 Activity 的生命周期方法被调用时,你可以在 Android Studio 的 Logcat 窗口中看到相应的日志输出。


以下是 LifecycleActivity 中的日志输出信息及顺序,假设用户打开了这个 Activity,然后马上将其切换到后台(例如通过按下 Home 键),然后再切换回来,最后关闭 Activity

D/LifecycleActivity(23333): onCreate
D/LifecycleActivity(23333): onStart
D/LifecycleActivity(23333): onResume

Activity 被切换到后台时(部分失去焦点但仍可见):

D/LifecycleActivity(23333): onPause

Activity 再次变得可见时:

D/LifecycleActivity(23333): onResume

Activity 被切换到后台并且完全停止时:

D/LifecycleActivity(23333): onStop

如果 Activity 被系统重启(例如,当设备资源不足时):

D/LifecycleActivity(23333): onRestart
D/LifecycleActivity(23333): onStart
D/LifecycleActivity(23333): onResume

当用户关闭 Activity 或者 Activity 被销毁时:

D/LifecycleActivity(23333): onPause
D/LifecycleActivity(23333): onStop
D/LifecycleActivity(23333): onDestroy

请注意,日志输出的确切顺序可能会根据用户的行为和系统状态有所不同。例如,如果用户在 Activity 运行时重启设备,或者系统为了回收资源而终止了 Activity,你可能不会看到 onPause()onStop() 方法的调用。此外,如果 Activity 在后台时用户按下了 Back 键,Activity 将被销毁,你将看到 onPause()onStop()onDestroy() 的日志,而不会看到 onRestart()


了解生命周期很重要,因为我们需要在合适的节点进行对应的操作,如数据的加载、更新UI等,从而保证应用的流畅性与正常运转。


三、任务与返回栈:驾驭程序的纷繁世界

Android世界的入口-深度解锁Activity的秘密-LMLPHP


1、任务(Task)的概念

任务是 Android 中用于维护一系列 Activity 的容器,这些 Activity 按照用户与之交互的顺序排列。任务的概念对于用户导航和应用的多任务处理至关重要。

  • 任务的起点:通常是从设备主屏幕启动的,当用户点击应用图标或主屏幕上的快捷方式时,应用的任务会被启动或带到前台。

  • 任务的创建:如果应用之前未被使用(即没有现存的任务),系统会为应用的“主” Activity 创建一个新任务,这个 Activity 将成为任务中的第一个 Activity,也是返回栈的根。


2、返回栈(Back Stack)

返回栈是任务内部的一个数据结构,用于存储 Activity 的启动顺序,以便用户可以通过按“返回”按钮在这些 Activity 之间导航。

  • 后进先出(LIFO):返回栈遵循后进先出的原则,即最后启动的 Activity 会位于栈顶,并且首先被销毁。

  • Activity 的启动与停止:当一个新的 Activity 被启动时,它会被推入返回栈的顶部,并成为当前拥有焦点的 Activity。前一个 Activity 会进入停止状态,但它的状态会被保存,以便用户返回时可以恢复。

  • 用户导航:用户可以通过按“返回”按钮在返回栈中的 Activity 之间导航,系统会自动销毁栈顶的 Activity,并恢复前一个 Activity


3、任务与返回栈的关系

Android世界的入口-深度解锁Activity的秘密-LMLPHP

  • 任务的维护:Android 系统会维护每个任务的返回栈,即使任务被置于后台,其返回栈也保持不变。

  • 任务的前台与后台:任务可以进入前台或后台。当用户开始新任务或通过按“主页”按钮转到主屏幕时,当前任务会进入后台,但它的返回栈保持不变。

  • 任务的恢复:当任务再次被用户选中时,它可以从后台返回到前台,用户可以继续从他们离开时的状态继续操作。


4、多任务处理

  • 多任务场景:用户可以在多个任务之间切换, 甚至可以启动设备上其他应用中的 Activity,例如,用户可能在一个任务中查看邮件,在另一个任务中浏览网页。

  • 任务切换:用户可以通过主屏幕或“最近应用”(最近屏幕预览)来切换不同的任务。


通过理解任务和返回栈的原理,我们可以更好地设计应用的导航结构,提供流畅和直观的用户体验。同时,合理利用不同的启动模式和 Intent flags 可以对任务的行为进行细粒度的控制。


四、四大标准启动模式:召唤Activity的魔法

在 Android 开发中,Activity 的启动模式决定了多个实例之间以及与任务(Task)的关系。Android 提供了四种标准的启动模式:


1、standard(标准模式)


每次启动 Activity 时都会创建一个新的实例,不管它是否已经存在。

Standard 模式是 Android 的默认启动模式,你不在配置文件中做任何设置,那么这个 Activity 就是 Standard 模式。这种模式下,Activity 可以有多个实例,每次启动 Activity,无论任务栈中是否已经有这个 Activity 的实例,系统都会创建一个新的 Activity 实例。

<activity android:name=".StandardActivity">
    <!-- 其他配置 -->
</activity>
  • 最佳实践:默认情况下,大多数 Activity 应该使用标准模式。
  • 适用场景:当你希望每次用户请求时都创建一个新的 Activity 实例,或者当 Activity 之间没有特定的任务或生命周期关联时。

2、singleTop(栈顶复用模式)


如果 Activity 已经位于任务的栈顶,则不会创建新的实例,而是重用当前的实例。如果不位于栈顶,就会创建新的实例。

<activity android:name=".SingleTopActivity" android:launchMode="singleTop">
    <!-- 其他配置 -->
</activity>
  • 最佳实践:使用此模式的 Activity 应该能够处理意外的重复启动请求。

  • 适用场景:当你希望 Activity 能够接收来自其他 Activity 的意图(Intent),并且如果它已经在栈顶,就不应该重新创建实例。例如,一个新闻阅读器应用中的新闻详情 Activity,当用户从不同新闻条目启动同一个详情 Activity 时,不应该创建多个实例。


3、singleTask(单任务模式)


SingleTask 模式的 Activity 在同一个 Task 内只有一个实例。如果 Activity 已经位于栈顶,系统不会创建新的 Activity 实例,和 SingleTop 模式一样。但 Activity 已经存在但不位于栈顶时,系统就会把该 Activity 移到栈顶,并把它上面的 Activity 出栈。

<activity android:name=".SingleTaskActivity" android:launchMode="singleTask">
    <!-- 其他配置 -->
</activity>
  • 最佳实践:使用此模式的 Activity 应该是一个应用中的单一入口点,并且能够正确处理 Intent 的任务回退行为。

  • 适用场景:当你希望在整个任务(Task)中只有一个 Activity 实例,并且如果有多个实例需要启动,系统会将意图(Intent)传递给已存在的实例。例如,一个应用的主 Activity,它可能需要接收来自其他 Activity 的结果,或者一个购物应用中的购物车 Activity,用户可能从应用的任何地方添加商品到购物车。


4、singleInstance(单实例模式)


整个系统中只有一个 Activity 实例,并且它自己单独位于一个新的任务栈中。

SingleInstance 模式和 SingleTask 不同,SingleTask 只是任务栈内单例,系统里是可以有多个 SingleTask Activity 实例,而 SingleInstance Activity 在整个系统里只有一个实例,启动一个SingleInstance 的 Activity 时,系统会创建一个新的任务栈,并且这个任务栈只有这个 Activity。

<activity android:name=".SingleInstanceActivity" android:launchMode="singleInstance">
    <!-- 其他配置 -->
</activity>

SingleInstance 模式并不常用,如果我们把一个 Activity 设置为 SingleInstance 模式,它启动时会比较慢,切换效果不好,影响用户体验。

它往往用于多个应用之间,例如一个电视 Launcher 里的 Activity,通过遥控器某个键在任何情况可以启动,这个 Activity 就可以设置为 SingleInstance 模式,当在某应用中按键启动这个 Activity,处理完后按返回键,就会回到之前启动它的应用,不影响用户体验。

  • 佳实践:使用此模式的 Activity 应该是完全独立的,并且不会与其他 Activity 共享任务。
  • 适用场景:当你需要一个在系统中只存在一个实例的 Activity,并且该 Activity 应该始终位于一个新任务的开始位置。例如,一个需要用户登录的应用,登录 Activity 可以设置为 singleInstance 模式,以避免登录状态被其他 Activity 干扰。

以下是使用 Java 代码启动不同启动模式 Activity 的示例:

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btnStandard = findViewById(R.id.btn_standard);
        Button btnSingleTop = findViewById(R.id.btn_single_top);
        Button btnSingleTask = findViewById(R.id.btn_single_task);
        Button btnSingleInstance = findViewById(R.id.btn_single_instance);

        // 启动 standard 模式的 Activity
        btnStandard.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, StandardActivity.class);
                startActivity(intent);
            }
        });

        // 启动 singleTop 模式的 Activity
        btnSingleTop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SingleTopActivity.class);
                startActivity(intent);
            }
        });

        // 启动 singleTask 模式的 Activity
        btnSingleTask.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SingleTaskActivity.class);
                startActivity(intent);
            }
        });

        // 启动 singleInstance 模式的 Activity
        btnSingleInstance.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SingleInstanceActivity.class);
                startActivity(intent);
            }
        });
    }
}

五、flags 选项额外改变 Activity 启动行为

1、flags 选项及其作用介绍


在 Android 中,可以通过在 Intent 对象上设置标志位(flags)来改变 Activity 启动时的行为。这些标志位(flags)提供了对 Activity 如何启动和它与任务(Task)的关系的额外控制。以下是一些常用的 flags 选项及其作用:

  • FLAG_ACTIVITY_NEW_TASK:使 Activity 成为一个新任务的开始。

  • FLAG_ACTIVITY_CLEAR_TOP:如果存在相同 IntentActivity 实例,则将其上面的所有 Activity 出栈。

  • FLAG_ACTIVITY_SINGLE_TOP:确保 Activity 不会在任务栈的顶部创建重复的实例。

  • FLAG_ACTIVITY_CLEAR_TASK:清除整个任务栈,除了要启动的 Activity

  • FLAG_ACTIVITY_TASK_ON_HOME:当用户回到主屏幕时,如果任务栈顶部是这个 Activity,则不会重新启动它。


以下是使用这些 flags 的 Java 代码示例:

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btnNewTask = findViewById(R.id.btn_new_task);
        Button btnClearTop = findViewById(R.id.btn_clear_top);
        Button btnSingleTop = findViewById(R.id.btn_single_top);
        Button btnClearTask = findViewById(R.id.btn_clear_task);

        // 使用 FLAG_ACTIVITY_NEW_TASK 启动一个新的任务
        btnNewTask.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, NewTaskActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }
        });

        // 使用 FLAG_ACTIVITY_CLEAR_TOP 确保只有一个实例在栈顶
        btnClearTop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, ClearTopActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                startActivity(intent);
            }
        });

        // 使用 FLAG_ACTIVITY_SINGLE_TOP 确保没有重复的实例在栈顶
        btnSingleTop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SingleTopActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
                startActivity(intent);
            }
        });

        // 使用 FLAG_ACTIVITY_CLEAR_TASK 清除任务栈
        btnClearTask.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, ClearTaskActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
                startActivity(intent);
            }
        });
    }
}

在这个示例中,我们创建了一个主 ActivityMainActivity),它包含四个按钮,每个按钮都用于启动具有不同 flags 的 Activity。当用户点击这些按钮时,会创建一个 Intent 并添加相应的 flags,然后使用 startActivity(intent) 启动 Activity

请注意,为了使上述代码正常工作,你需要在项目的 AndroidManifest.xml 文件中声明这些 Activity,并且为它们设置合适的启动模式(如果有的话)。同时,你还需要为这些按钮在布局文件 activity_main.xml 中定义相应的 ID。

使用 flags 对 Activity 的启动行为进行修饰是 Android 应用开发中常见的做法,它允许开发者更精细地控制任务和回退栈的行为。


2、使用 flags 选项 对Activity 额外控制的最佳实践


使用 Intent 的 flags 选项为 Activity 提供额外控制是一种强大的机制,它可以改变 Activity 的启动行为和任务管理方式。以下是一些关于如何使用这些 flags 的最佳实践:


(1)、使用 FLAG_ACTIVITY_CLEAR_TOP 进行状态管理

当你希望用户从其他 Activity 返回时,能够回到应用的特定状态,可以使用 FLAG_ACTIVITY_CLEAR_TOP。这将移除目标 Activity 之上的所有 Activity,确保用户不会回退到一个中间状态。

(2)、使用 FLAG_ACTIVITY_NEW_TASK 启动新任务

如果你希望启动的 Activity 应该独立于当前任务运行,使用 FLAG_ACTIVITY_NEW_TASK。这在创建对话框或模态 Activity 时非常有用。

(3)、避免滥用 FLAG_ACTIVITY_CLEAR_TASK

FLAG_ACTIVITY_CLEAR_TASK 会清除任务栈中除了目标 Activity 之外的所有 Activity。这可能会导致用户丢失他们的位置,因此只有在确实需要时才使用它。

(4)、使用 FLAG_ACTIVITY_REORDER_TO_FRONT

如果你使用 FLAG_ACTIVITY_SINGLE_TOP 并且希望将现有的任务实例移动到前台,可以使用 FLAG_ACTIVITY_REORDER_TO_FRONT。这在某些需要将现有实例重新排序的场景中很有用。

(5)、考虑用户体验

使用 flags 时,始终考虑用户体验。例如,使用 FLAG_ACTIVITY_CLEAR_TOP 可能会导致用户丢失他们在应用中的位置,因此要谨慎使用。

(6)、结合使用多个 flags

有时你可能需要结合使用多个 flags 来实现特定的行为。例如,FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP 可以确保即使 Activity 已经存在,也不会创建新的实例,并且会清除上面的所有 Activity

(7)、避免过度使用 flags

过度使用 flags 可能会使应用的导航逻辑变得复杂和难以维护。只有在确实需要时才使用它们。

(8)、测试不同设备和配置

不同的设备和 Android 版本可能对 flags 的解释略有不同。确保在多种设备和 Android 配置上测试你的应用。


结语:

无论是Activity的保活技术,还是程序化启动Activity的Intent机制,都是Android开发中不可或缺的大熊机理。这些内容都值得我们进一步深入探索,以便掌握更多运用Activity的前沿姿势。


总之,Activity虽是Android的入口,却也同时蕴藏了系统设计中最为深邃的思维。我们有太多值得学习和探索的地方了。期待在下一篇中,进阶探索-保活、Intent及其拓展,以及URI Scheme、多路径实现。

05-06 11:58