Android--四大组件之Activity

1 Activity简介

Activity类是Android应用的关键组件,而Activity的启动和组合方式则是该平台应用模型的基本组成部分。在编程范式中,应用是通过main()方法启动的,而Android系统与此不同,它会调用与其生命周期特定阶段相对应的特定回调方法来启动Activity实例中的代码。

移动应用体验与桌面体验的不同之处在于,用户与应用的互动并不总是在同一位置开始,而是经常以不确定的方式开始。Activity类的目的就是促进这种范式的实现。当一个应用调用另一个应用时,调用方应用会调用另一个应用中的Activity,而不是整个应用。通过这种方式,Activity充当了应用与用户互动的入口点。

Activity提供窗口供应用在其中绘制界面。此窗口通常会填满屏幕,但也可能比屏幕小,并浮动在其他窗口上面。通常,一个Activity实现应用中的一个屏幕。

大多数应用包含多个屏幕,这意味着它们包含多个Activity。通常,应用中的一个Activity会被指定为主Activity,这是用户启动应用时出现的第一个屏幕。然后,每个Activity可以启动另一个Activity,以执行不同的操作。

虽然应用中的各个Activity协同工作形成统一的用户体验,但每个Activity与其他Activity之间只存在松散的关联,应用内不同Activity之间的依赖关系通常很小。事实上,Activity经常会启动属于其他应用的Activity。要在应用中使用 Activity,必须在应用的清单中注册关于Activity 的信息,并且必须适当地管理Activity的生命周期。

2 配置清单

2.1 声明Activity

要声明Activity,请打开清单文件AndroidManifest.xml,并添加<activity>元素作为<application>元素的子元素。此元素唯一的必要属性是android:name,该属性用于指定Activity的类名称,也可以添加用于定义标签、图标或界面主题等Activity特征的属性。

2.2 声明intent过滤器

intent过滤器是Android平台的一项非常强大的功能。借助这项功能,不但可以根据显式请求启动Activity,还可以根据隐式请求启动Activity。例如,显式请求可能会告诉系统在Gmail应用中启动“发送电子邮件”Activity,而隐式请求可能会告诉系统在任何能够完成此工作的Activity中启动“发送电子邮件”Activity。当系统界面询问用户使用哪个应用来执行任务时,这就是intent过滤器在起作用。

图1 隐式intent的选择器对话框

要使用此功能,需要在<activity>元素中声明<intent-filter>属性。此元素的定义包括<action>元素,以及可选的<category>元素和/或<data>元素。这些元素组合在一起,可以指定Activity能够响应的intent类型。

2.3 声明权限

可以使用清单的<activity>标记来控制哪些应用可以启动某个Activity。父Activity和子Activity必须在其清单中具有相同的权限,前者才能启动后者。如果为父Activity声明了<uses-permission>元素,则每个子Activity都必须具有匹配的<uses-permission>元素。

常见的清单文件AndroidManifest.xml的基本内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.bytectf.pwnbronzedroid">

<uses-permission android:name="android.permission.INTERNET"/>
<application
android:usesCleartextTraffic="true"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Pwnbronzedroid"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

3 Activity生命周期

3.1 Activity生命周期概念

当用户浏览、退出和返回到应用时,应用中的Activity实例会在其生命周期的不同状态间转换。Activity类会提供许多回调,这些回调会让Activity知晓某个状态已经更改:系统正在创建、停止或恢复某个Activity,或者正在销毁该Activity所在的进程。

为了在Activity生命周期的各个阶段之间导航转换,Activity类提供六个核心回调:onCreate()onStart()onResume()onPause()onStop()onDestroy(),当Activity进入新状态时,系统会调用其中每个回调,图2是对此范例的直观展现。

图2 Activity生命周期的简化图示

当用户开始离开Activity时,系统会调用方法来销毁该Activity。在某些情况下,此销毁只是部分销毁;Activity仍然驻留在内存中(例如当用户切换至另一应用时),并且仍然可以返回到前台。如果用户返回到该Activity,Activity会从用户离开时的位置继续运行。除了少数例外,应用在后台运行时会受到限制,无法启动Activity。

系统终止给定进程及其中Activity的可能性取决于当时Activity的状态。3.3节的Activity状态和从内存中弹出会更详细地介绍状态与弹出漏洞之间的关系。

3.2 生命周期回调

本部分介绍Activity生命周期中所用回调方法的相关概念及实现信息。

某些操作(例如调用setContentView())属于Activity生命周期方法本身。不过,用于实现依赖组件操作的代码应放在组件本身内。为此,必须使依赖组件具有生命周期感知能力。

3.2.1 onCreate()

我们构建Android应用时必须实现此回调,它会在系统首次创建Activity时触发。Activity会在创建后进入“已创建”状态。在onCreate()方法中,需执行基本应用启动逻辑,该逻辑在Activity的整个生命周期中只应发生一次。例如,onCreate()的实现可能会将数据绑定到列表,将Activity与ViewModel相关联,并实例化某些类作用域变量。此方法会接收savedInstanceState参数,后者是包含Activity先前保存状态的Bundle对象。如果Activity此前未曾存在,Bundle对象的值为null。

如果一个生命周期感知型组件与Activity生命周期相关联,该组件将收到ON_CREATE事件。系统将调用带有@OnLifecycleEvent注释的方法,以使生命周期感知型组件可以执行已创建状态所需的任何设置代码。

onCreate()方法的以下示例显示执行Activity某些基本设置的一些代码,例如声明界面(在XML布局文件中定义)、定义成员变量,以及配置某些界面。在本示例中,系统通过将文件的资源ID R.layout.main_activity传递给setContentView()来指定XML布局文件。

onCreate()方法完成执行后,Activity进入“已开始”状态,系统会相继调用onStart()onResume()方法,下一部分将介绍onStart()回调。

3.2.2 onStart()

当Activity进入“已开始”状态时,系统会调用此回调。onStart()调用使Activity对用户可见,因为应用会为Activity进入前台并支持互动做准备。例如,应用通过此方法来初始化维护界面的代码。

当Activity进入已开始状态时,与Activity生命周期相关联的所有生命周期感知型组件都将收到ON_START事件。

onStart()方法会非常快速地完成,并且与“已创建”状态一样,Activity不会一直处于“已开始”状态。一旦此回调结束,Activity便会进入“已恢复”状态,系统将调用onResume()方法。

3.2.3 onResume()

Activity会在进入“已恢复”状态时来到前台,然后系统调用onResume()回调。这是应用与用户互动的状态。应用会一直保持这种状态,直到某些事件发生,让焦点远离应用。此类事件包括接到来电、用户导航到另一个Activity,或设备屏幕关闭。

当Activity进入已恢复状态时,与Activity生命周期相关联的所有生命周期感知型组件都将收到ON_RESUME事件。这时,生命周期组件可以启用在组件可见且位于前台时需要运行的任何功能,例如启动相机预览。

当发生中断事件时,Activity进入“已暂停”状态,系统调用onPause()回调。

如果Activity从“已暂停”状态返回“已恢复”状态,系统将再次调用onResume()方法。因此,我们应实现onResume(),以初始化在onPause()期间释放的组件,并执行每次Activity进入“已恢复”状态时必须完成的任何其他初始化操作。

无论选择在哪个构建事件中执行初始化操作,都请务必使用相应的生命周期事件来释放资源。如果在收到ON_START事件后初始化某些内容,请在收到ON_STOP事件后释放或终止相应内容。如果在收到ON_RESUME事件后初始化某些内容,请在收到ON_PAUSE事件后将其释放。

3.2.4 onPause()

系统将此方法视为用户将要离开Activity的第一个标志(尽管这并不总是意味着Activity会被销毁);此方法表示Activity不再位于前台(尽管在用户处于多窗口模式时Activity仍然可见)。使用onPause()方法暂停或调整当Activity处于“已暂停”状态时不应继续(或应有节制地继续)的操作,以及希望很快恢复的操作。Activity进入此状态的原因有很多。例如:

  • 某个事件会中断应用执行,这是最常见的情况;

  • 在Android 7.0(API级别24)或更高版本中,有多个应用在多窗口模式下运行。无论何时,都只有一个应用(窗口)可以拥有焦点,因此系统会暂停所有其他应用;

  • 有新的半透明Activity(例如对话框)处于开启状态。只要Activity仍然部分可见但并未处于焦点之中,它便会一直暂停。

当Activity进入已暂停状态时,与Activity生命周期相关联的所有生命周期感知型组件都将收到ON_PAUSE事件。这时,生命周期组件可以停止在组件未位于前台时无需运行的任何功能,例如停止相机预览。

还可以使用onPause()方法释放系统资源、传感器(例如GPS)手柄,或当Activity暂停且用户不需要它们时仍然可能影响电池续航时间的任何资源。然而,正如上文的onResume()部分所述,如果处于多窗口模式,“已暂停”的Activity仍完全可见。因此,应该考虑使用onStop()而非onPause()来完全释放或调整与界面相关的资源和操作,以便更好地支持多窗口模式。

onPause()执行非常简单,而且不一定要有足够的时间来执行保存操作。因此,我们一定不要使用onPause()来保存应用或用户数据、进行网络调用或执行数据库事务。因为在该方法完成之前,此类工作可能无法完成。相反,我们应该在onStop()期间执行高负载的关闭操作。

onPause()方法的完成并不意味着Activity离开“已暂停”状态。相反,Activity会保持此状态,直到其恢复或变成对用户完全不可见。如果Activity恢复,系统将再次调用onResume()回调。如果Activity从“已暂停”状态返回“已恢复”状态,系统会让Activity实例继续驻留在内存中,并会在系统调用onResume()时重新调用该实例。在这种情况下,无需重新初始化在任何回调方法导致Activity进入“已恢复”状态期间创建的组件。如果Activity变为完全不可见,系统会调用onStop()。下一部分将介绍onStop()回调。

3.2.5 onStop()

如果Activity不再对用户可见,说明其已进入“已停止”状态,因此系统将调用onStop()回调。例如,当新启动的Activity覆盖整个屏幕时,可能会发生这种情况。如果Activity已结束运行并即将终止,系统还可以调用onStop()

当Activity进入已停止状态时,与Activity生命周期相关联的所有生命周期感知型组件都将收到ON_STOP事件。这时,生命周期组件可以停止在组件未显示在屏幕上时无需运行的任何功能。

onStop()方法中,应用应释放或调整在应用对用户不可见时的无用资源。例如,应用可以暂停动画效果,或从精确位置更新切换到粗略位置更新。使用onStop()而非onPause()可确保与界面相关的工作继续进行,即使用户在多窗口模式下查看Activity也能如此。

我们还应使用onStop()执行CPU相对密集的关闭操作。例如,如果无法找到更合适的时机来将信息保存到数据库,可以在onStop()期间执行此操作。

当Activity进入“已停止”状态时,Activity对象会继续驻留在内存中:该对象将维护所有状态和成员信息,但不会附加到窗口管理器。Activity恢复后,Activity会重新调用这些信息。无需重新初始化在任何回调方法导致Activity进入“已恢复”状态期间创建的组件。系统还会追踪布局中每个View对象的当前状态,如果用户在EditText微件中输入文本,系统将保留文本内容,因此无需保存和恢复文本。

【注意】Activity停止后,如果系统需要恢复内存,可能会销毁包含该Activity的进程。即使系统在Activity停止后销毁相应进程,系统仍会保留Bundle(键值对的blob)中View对象(例如EditText微件中的文本)的状态,并在用户返回Activity时恢复这些对象。

进入“已停止”状态后,Activity要么返回与用户互动,要么结束运行并消失。如果Activity返回,系统将调用onRestart()。如果Activity结束运行,系统将调用onDestroy()。下一部分将介绍onDestroy()回调。

3.2.6 onDestroy()

销毁Activity之前,系统会先调用onDestroy(),系统调用此回调的原因如下:

  • Activity即将结束(由于用户彻底关闭Activity或由于系统为Activity调用finish());

  • 由于配置变更(例如设备旋转或多窗口模式),系统暂时销毁Activity。

当Activity进入已销毁状态时,与Activity生命周期相关联的所有生命周期感知型组件都将收到ON_DESTROY事件。这时,生命周期组件可以在Activity被销毁之前清理所需的任何数据。

我们应使用ViewModel对象来包含Activity的相关视图数据,而不是在Activity中加入逻辑来确定Activity被销毁的原因。如果因配置变更而重新创建Activity,ViewModel不必执行任何操作,因为系统将保留ViewModel并将其提供给下一个Activity实例。如果不重新创建Activity,ViewModel将调用onCleared()方法,以便在Activity被销毁前清除所需的任何数据。

我们可以使用isFinishing()方法区分这两种情况。

如果Activity即将结束,onDestroy()是Activity收到的最后一个生命周期回调。如果由于配置变更而调用onDestroy(),系统会立即新建Activity实例,然后在新配置中为新实例调用onCreate()

onDestroy()回调应释放先前的回调(例如onStop())尚未释放的所有资源。

3.3 Activity状态和从内存中弹出

系统会在需要释放RAM时终止进程;系统终止给定进程的可能性取决于当时进程的状态。反之,进程状态取决于在进程中运行的Activity的状态。表1展示了进程状态、Activity状态以及系统终止进程的可能性之间的关系。

表1 进程生命周期和Activity状态之间的关系

系统永远不会直接终止Activity以释放内存,而是会终止Activity所在的进程。系统不仅会销毁Activity,还会销毁在该进程中运行的所有其他内容。

用户还可以使用“设置”下的“应用管理器”来终止进程,以终止相应的应用。

3.4 Activity生命周期示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.activitydemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

public static final String TAG = "Lifecycle";
private void showMsg(String msg){
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Log.d(TAG,"onCreate");
showMsg("onCreate");
}

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

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

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

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

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

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

【注】真机测试建议使用弹Toast方式更为直观,模拟器测试查看Logcat比较直观。

4 Activity生存期、启动模式和跳转方式

4.1 三种生存期

以上七个方法除了onRestart()方法,其他都是两两相对的,从而又可以将活动分为三种生存期:

  1. 完整生存期:活动在onCreate()方法和onDestroy()方法之间所经历的,就是完整生存期

  2. 可见生存期:活动在onStart()方法和onStop()之间所经历的,在可见生命期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。

  3. 前台生存期:活动在onResume()方法和onPause()方法之间所经历的,这个生存期内,活动总是处于运行状态,此时的活动是可以和用户进行相互的,我们平时看到和接触最多的也是这个状态下的活动。

Activity生命周期示意图如图3所示

图3 Activity生命周期

4.2 四种启动模式

Activity的启动模式决定了激活Activity时,是否创建新的对象,进而将影响到任务栈也叫回退栈

在AndroidManifest.xml文件中,可以为每个Activity节点配置android:launchMode属性,以决定该Activity的启动模式,该属性的值有:

  • Standard模式:(默认)标准模式:每次激活Activity时,都会创建新的Activity对象。standard模式是android的默认启动模式,在这种模式下,Activity可以有多个实例,每次启动Activity,无论任务栈中是否已经存在这个Activity的实例,系统都会创建一个新的Activity实例。即在这种模式下,Activity默认会进入启动它的Activity所属的任务栈中。注意:在非Activity类型的context(如ApplicationContext)并没有所谓的任务栈,所以不能通过ApplicationContext去启动standard模式的Activity。

  • SingleTop模式:栈顶模式,也叫栈顶复用模式。当一个singleTop模式的Activity 已经位于栈顶位置时,再去启动它时,不会再创建实例,即每次只是激活但并不会创建新的Activity对象,如果不在栈顶,就会创建实例。(如果新Activity位于任务栈的栈顶的时候,Activity不会被重新创建,同时它的onNewIntent方法会被回调。注意:这个Activity的onCreate,onStart,onResume不会被回调,因为他们并没有发生改变。)

  • SingleTask模式:单任务模式,也叫栈内复用模式。如果启动的这个Activity已经存在于任务栈中,则会将该Activity移动到栈顶,并将该Activity上面的所有Activity出栈,否则创建新的实例。(只要Activity在一个栈中存在,那么多次启动此Activity不会被重新创建单例,系统会回调onNewIntent。比如activityA,系统首先会寻找是否存在A想要的任务栈,如果没有则创建一个新的任务栈,然后把activityA压入栈,如果存在任务栈,然后再看看有没有activityA的实例,如果实例存在,那么就会把A调到栈顶并调用它的onNewIntent方法,如果不存在则把它压入栈。)

  • SingleInstance模式:单实例模式,一个Activity一个栈,即Activity只能单独地位于一个任务栈中。(实例(对象)唯一,确保该Activity的对象一定只有1个,被设置为singleInstance的Activity将被置于一个专门的任务栈中,且该任务栈中有且仅有一个Activity。)

4.3 三种跳转方式

显示启动:Intrent内部直接声明要启动的Activity所对应的的class

1
2
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);

隐式启动:进行三个匹配,一个是Activity,一个是category,一个是data,全部或者部分匹配,应用于广播原理

清单文件中里配置Activity属性,Activity的名字要和跳转内容一样

1
2
3
4
5
6
7
8
<activity 
android:name="com.example.android.test.secondActivity"
android:label = @string/title>
<intent=filter>
<action android:name="com.example.android.test.secondActivity/>
<category android:name="android.intent.category.DEFAULT"/>
<intent-filter/>
</activity>

在需要跳转的地方

1
2
Intent intent = new Intent("com.example.android.test.secondActivity");
startActivity(intent);

跳转后再返回,能获取返回值

1
2
3
Intent in = new Intent(MainActivity.this, OtehrActivity.class);
in.putExtra("a", a);
startActivityForResult(in, 1000);

在OTherActivity中设置返回值

1
2
3
4
Intent int = new Intent();
int.putExtra("c", c);
setResult(1001, int);
finish();

在MainActivity中获取返回值

1
2
3
4
5
6
7
8
9
@Override
protected void onActivityResult(int requestCode, int resultCode ,Intent data) {
super.onActivityResult(requestCode,resultCode,data);
if(requestCode == 1000){
if(resultCode == 1001){
int c = data.getExtra("c",0);
}
}
}

5 参考文献


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,可以邮件至 xingshuaikun@163.com。

×

喜欢就点赞,疼爱就打赏