在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
类中。在 Activity
的 onCreate()
方法中,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
的状态和资源至关重要。
以下是 Activity
生命周期的主要方法,以及它们被调用的顺序:
- onCreate(Bundle savedInstanceState)
- 当
Activity` 第一次被创建时调用。 onStart()
-Activity
变得可见时调用。onResume()
-Activity
准备与用户交互时调用。onPause()
-Activity
部分失去焦点,但仍然可见时调用,通常用于保存状态。onStop()
-Activity
不再可见时调用。onRestart()
-Activity
从停止状态返回到启动状态时调用。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等,从而保证应用的流畅性与正常运转。
三、任务与返回栈:驾驭程序的纷繁世界
1、任务(Task)的概念
任务是 Android 中用于维护一系列 Activity
的容器,这些 Activity
按照用户与之交互的顺序排列。任务的概念对于用户导航和应用的多任务处理至关重要。
-
任务的起点:通常是从设备主屏幕启动的,当用户点击应用图标或主屏幕上的快捷方式时,应用的任务会被启动或带到前台。
-
任务的创建:如果应用之前未被使用(即没有现存的任务),系统会为应用的“主”
Activity
创建一个新任务,这个Activity
将成为任务中的第一个Activity
,也是返回栈的根。
2、返回栈(Back Stack)
返回栈是任务内部的一个数据结构,用于存储 Activity
的启动顺序,以便用户可以通过按“返回”按钮在这些 Activity
之间导航。
-
后进先出(LIFO):返回栈遵循后进先出的原则,即最后启动的
Activity
会位于栈顶,并且首先被销毁。 -
Activity 的启动与停止:当一个新的
Activity
被启动时,它会被推入返回栈的顶部,并成为当前拥有焦点的Activity
。前一个Activity
会进入停止状态,但它的状态会被保存,以便用户返回时可以恢复。 -
用户导航:用户可以通过按“返回”按钮在返回栈中的
Activity
之间导航,系统会自动销毁栈顶的Activity
,并恢复前一个Activity
。
3、任务与返回栈的关系
-
任务的维护: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:如果存在相同
Intent
的Activity
实例,则将其上面的所有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);
}
});
}
}
在这个示例中,我们创建了一个主 Activity
(MainActivity
),它包含四个按钮,每个按钮都用于启动具有不同 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、多路径实现。