Android开发学习笔记(四) —— 重拾Activity

一、什么是Activity

Activity是Android四大组件之一,用于显示用户界面,能够让我们进行相关操作,比如打电话、发短信等。Activity需要在AndroidManifest.xml配置文件中进行注册,否则系统无法识别该Activity。

Activity在代码中的体现是:继承AppCompatActivity才叫Activity。

二、Activity的生命周期

2.1 Activity的生命周期方法

  • onCreate():在Activity创建时调用,通常做一些初始化设置,不可见
  • onStart():在Activity由不可见变为可见时调用,可见,此时用户可以看到界面,但没有获取到焦点,用户不能进行操作。
  • onResume():此时Activity获取到焦点,能够与用户交互,可见。此方法是在与用户进行交互时调用。
  • onPause():在当前Activity被其它Activity覆盖或锁屏时调用,可见,但失去焦点。此时会对状态信息和数据进行保存。
  • onStop():在Activity不可见时调用,不可见,Activity进入到了后台,且Activity对象仍在内存中。
  • **onDestroy()**:在Activity销毁时调用。
  • **onRestart()**:当处于Stopped状态的活动需要再次展现给用户时调用。

在上述图片中,可以将内容归纳为三个周期:

  1. 完整生命周期:onCreate() → onStart() → onResume() → Activity running → onPause() → onStop() → onDestory()
  2. 可见生命周期:onStart() → onResume() → Activity running → onPause() → onStop() → onRestart()
  3. 前台生命周期:onResume() → Activity running → onPause()

2.2 体验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
public class MainActivity extends AppCompatActivity {

private static final String TAG = "aaa";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
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 onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}

//启动另一个Activity
public void startAnotherActivity(View view) {
startActivity(new Intent(this,SecondaryActivity.class));
}

}

根据Logcat中的信息,我们可以知道:

  1. 启动Activity:onCreate() -> onStart() -> onResume()
  2. 基于1,按下home键:onPause() -> onStop()
  3. 基于2,再次让Activity占据屏幕:onRestart() -> onStart() -> onResume()
  4. 被其他Activity完全覆盖:onPause() -> onStop()
  5. 直接退出应用:onPause() -> onStop() -> onDestroy()

三、Activity的基本用法

3.1 手动创建Activity

在工程中,其实我们可以直接new出一套Activity(即对应的类、xml文件并在AndroidManifest.xml中注册),但在这里,我们手动创建一下来体会Activity的创建过程。

首先需要创建Activity对应的布局文件,随便写一写就行.

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--activity_secondary.xml-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我将原来的主页面变成另一个页面了"/>

</LinearLayout>

然后需要编写继承AppCompatActivity的Activity类,只需要重写onCreate()方法就行。

1
2
3
4
5
6
7
8
public class SecondaryActivity extends AppCompatActivity {

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_secondary);
}
}

接下来最为关键,就是要在AndroidManifest.xml中声明,将下面这段加入到<application>标签中。

1
2
3
4
5
6
7
8
9
<!--自己编写的Activity-->
<activity
android:name=".SecondaryActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

其中<activity>标签中的android:name指定要被注册的活动(内容是我们编写的Acitivity类所在的全限定类名的缩写)。android:exported指示该Activity是否可以被其它应用程序的组件启动。 <action><category>这两个标签的内容共同指定了该活动为应用运行后启动的第一个活动。

3.2 Activity跳转

案例:基于2.1,实现通过按钮来跳转到另一个Activity。

首先在activity_main.xml中编写按钮来实现点击按钮跳转Activity,随便写一写就行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

<!--activity_main.xml-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:context=".MainActivity">

<android.widget.Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="start another activity"
android:onClick="startAnotherActivity"
/>

</LinearLayout>

MainActivity.java中实现该点击事件处理方法。

1
2
3
4
public void startAnotherActivity(View view) {
//通过startActivity启动另一个Activity
startActivity(new Intent(this,SecondaryActivity.class));
}

启动的是我们手动创建的活动,但需要将该活动在AndroidManifest.xml中的配置改为:

1
<activity android:name=".SecondaryActivity"/>

3.2 启动外部应用

在应用程序A启动应用程序B,可以使用显示Intent和隐式Intent,两者的区别在于前者指定了ComponentName,可以直接启动目标程序,而后者没有。后者通过 Action、Category 或 Data 等信息来筛选出合适的应用程序,再由使用者挑选(如果存在多个的话)。

详细Intent介绍可见[Android Intent](https://gal2xy.github.io/2023/07/04/Android Intent/)。

3.2.1 显示Intent启动

1
2
3
4
5
6
public void startAnotherActivity(View view) {
//显式Intent
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.example.myapplication","com.example.myapplication.MainActivity"));
startActivity(intent);
}

这里通过ComponentName指定了要打开的应用的主Activity。

3.2.2 隐式Intent启动

1
2
3
4
5
6
7
public void startAnotherActivity(View view) {
Intent intent = new Intent();
intent.setData(Uri.parse("https://baidu.com"));
intent.setAction(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_BROWSABLE);
startActivity(intent);
}

这里使用隐式Intent来打开一个链接,其中Intent.ACTION_VIEW表示查看指定数据(如打开网址、显示地图等)。Intent.CATEGORY_BROWSABLE表示用浏览器来查看。

3.3 Activity的状态保存

当系统资源不足时,处于onPause状态的Activity可能会被系统销毁,或者当设备发生横竖屏切换时,系统也会销毁当前Activity。此时系统调用 onSaveInstanceState() 方法来保存该Activity 的状态,等到该Activity重新创建时(即调用onCreate()方法)会进行状态的恢复。当然状态保存和恢复的代码需要我们重写来实现。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("key", "data_temp");
}

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (savedInstanceState != null){
String msg1 = savedInstanceState.getString("key");
Log.d(TAG, "onCreate" + msg1);
}

}

3.4 手动销毁Activity

使用finish()方法,触发的生命周期方法为onPause() -> onStop() -> onDestroy()。

比如在跳转到另一个Activity并销毁当前Activity,代码如下:

1
2
startActivity(new Intent(this,SecondaryActivity.class));
finish();

3.5 随时关闭所有Activity

其实创建一个Activity管理类来管理这些Activity的create和destory。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ActivityCollector {

public static List<Activity> activityList = new ArrayList<>();

public static void addActivity(Activity activity){
activityList.add(activity);
}

public static void removeActivity(Activity activity){
activityList.remove(activity);
}

public static void finishAll(){

for (Activity each:activityList) {
if (!each.isFinishing()){
each.finish();
}
}

}

}

然后在编写一个BaseActivity类作为所有Activity的父类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BaseActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
}

@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}

这样一来,只需要让Activity的创建都继承BaseActivity就可以实现自动添加和移除了。如果要关闭所有活动,使用removeAllActivity()方法即可。

3.6 完全退出App

1
2
3
4
5
6
7
8
9
10
11
12
/** 
* 退出应用程序
*/
public void AppExit(Context context) {
try {
ActivityManager activityMgr = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
activityMgr.killBackgroundProcesses(context.getPackageName());
System.exit(0);
} catch (Exception ignored) {}
}

3.7 双击back键退出App

像有的App中,按下一次back键并不会退出App,而是会提醒你再按一次退出,接下来及时按下第二次back键就会退出App。

实现这种情况只需要重写Activity的onKeyDown()方法,通过两次点击back键的时间差来决定是否退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean onKeyDown(int keyCode, KeyEvent event) {
//是否按下返回键
if (keyCode == KeyEvent.KEYCODE_BACK){
//时差大于2s就不断提示
if(System.currentTimeMillis() - exitTime > 2000){
Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
exitTime = System.currentTimeMillis();
Log.d("aaa", "onKeyDown: " + exitTime);
} else {//时差小于2s就退出
exit(0);
}
return false;
}
return super.onKeyDown(keyCode, event);
}

3.8 知晓当前是在哪一个活动

在别人的代码上添加自己的功能时,比如修改某个界面,那么知道该界面所属哪个Activity是很有必要的。

其实实现上述想法很简单,我们让所有Activity都继承自己写的BaseActivity类,在该类的onCreate()方法中实现打印当前Activity的名字。当然BaseActivity类必然是继承于AppCompatActivity类。

1
2
3
4
5
6
7
8
public class BaseActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
Log.d("BaseActivity", getClass().getSimpleName());
super.onCreate(savedInstanceState);
}
}

3.9 启动活动的最佳写法

在以上所有案例中,我们启动Activity的方法基本上都是在调用者活动中写下如下代码:

1
2
3
4
Intent intent = new Intent(this, xxxActivity.class);
Intent.putExtra("key1", data1);
Intent.putExtra("key2", data2);
startActivity(intent);

以上写法当然没问题。但是如果我们不知道启动这个Activity要传递哪些数据呢(负责别人的项目时),那么只能问问相关人员或者自己看源码咯。那如果我们将启动Activity的代码包装成方法,参数就是启动这个Activity所需要的数据,那之后不就很清楚的知道这个Activity需要哪些参数以及类型分别是什么了。

1
2
3
4
5
6
public static void StartxxxActivity(Context context, String data1, String data2){
Intent intent = new Intent(context, xxxActivity.class);
Intent.putExtra("key1", data1);
Intent.putExtra("key2", data2);
context.startActivity(intent);
}

四、Activity之间的数据传递

4.1 向下一个Activity传递数据

Intent不仅可以用来启动Activity,也可以用来传递数据,因为Intent有Extra属性。通过putExtra()putExtras()方法来存储要传递的数据,然后在另一个活动中通过getIntent()获得传递过来的Intent,继而进行相关get操作。具体使用可参考下图(来自菜鸟教程):

案例:登入界面,登入成功后提示”欢迎您,XXX“。

xml的配置过于简单就不展示了,直接展示java代码。

登入界面:

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
public class MainActivity extends AppCompatActivity {

private TextView AccountText;
private TextView PasswordText;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取两个输入框控件
initView();
}

private void initView() {
AccountText = findViewById(R.id.id_account);
PasswordText = findViewById(R.id.id_password);
}

public void login(View view) {

Intent intent = new Intent(this,SecondaryActivity.class);
Bundle bundle = new Bundle();
bundle.putString("account",AccountText.getText().toString());
bundle.putString("password",PasswordText.getText().toString());
intent.putExtras(bundle);
startActivity(intent);
}
}

响应界面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SecondaryActivity extends AppCompatActivity {

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_secondary);
initView();
}

private void initView() {
TextView loginText = findViewById(R.id.id_login_success);
Intent intent = getIntent();
Bundle extras = intent.getExtras();
String account = extras.getString("account");
String password = extras.getString("password");
if(account.equals("admin") && password.equals("123456")){
loginText.setText("欢迎您,用户" + account);
}else{
loginText.setText("密码或账号错误");
}
}

}

4.2 向上一个Activity传递数据(数据回传)

这个情况比较特殊。因为如果我们要返回上一个Activity,按back键就可以了,但是这样的话,数据是不会传递过来的,因为back键只是让Activity栈栈顶活动发生变化,并没有进行数据的传递(如Intent, bundle等通信)。

其实Activity提供了另一种启动其他Activity的方法:startActivityForResult(Intent intent, int requestCode)。具体使用方法如下图所示(来自菜鸟教程):

  1. 在活动A使用startActivityForResult()方法来启动另一个活动B。
  2. 在活动A中重写onActivityResult()方法来对传回的数据进行处理。
  3. 在活动B中通过setResult()方法设置回传的数据,之后必须调用finish()方法来结束当前活动,否则无法进行数据回传。

在这些方法中,requestCode是请求码,可以自己设定,用于区分onActivityResult()方法是被谁触发的。resultCode是结果码,也可以自己设定,用于区分结果(比如成功和失败等)。

数据回传的情况多出现在充值场景,我们拿这个做个简单实操。

首先是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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!--现有金额显示界面-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:context=".MainActivity"
android:orientation="vertical">
<TextView android:id="@+id/id_money"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="0"
android:padding="20dp"
android:textAlignment="center"
android:background="#FF00FFFF"/>
<android.widget.Button android:id="@+id/id_recharge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="充值"
android:onClick="recharge"/>
</LinearLayout>
<!--充值金额界面-->
<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">
<EditText android:id="@+id/id_edt_rechargeMoney"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="0"/>
<android.widget.Button android:id="@+id/id_recharge_success"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="确认"
android:onClick="rechargesuccess"/>
<android.widget.Button android:id="@+id/id_recharge_fail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="取消"
android:onClick="rechargefail"/>
</LinearLayout>

对于现有金额显示界面,一个文本框显示金额,一个充值按钮就够了。

对于充值金额界面,一个输入框输入充值金额数,一个确认按钮,一个取消按钮就够了。

以上所有按钮均绑定了点击事件处理方法。

然后就是现有金额显示界面的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
public class MainActivity extends AppCompatActivity {

private TextView MoneyText;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取控件
initView();
}

private void initView() {
MoneyText = findViewById(R.id.id_money);
}
//充值按钮的点击方法
public void recharge(View view) {
Intent intent = new Intent(this, SecondaryActivity.class);
//使用startActivityForResult来启动另一个活动
startActivityForResult(intent,1);
}
//重写onActivityResult方法
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//根据请求码判断是不是按钮触发的充值事件
if (requestCode == 1) {
//根据结果码判断是否充值成功
if (resultCode == 1) {
if (data != null) {
String rechargemoney = data.getStringExtra("rechargemoney");
String nowmoney = MoneyText.getText().toString();
Integer total = Integer.parseInt(nowmoney) + Integer.parseInt(rechargemoney);
MoneyText.setText(total.toString());
Toast.makeText(this, "充值成功", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "充值取消", Toast.LENGTH_SHORT).show();
}
}

}
}

接着是充值金额界面的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
public class SecondaryActivity extends AppCompatActivity {

private EditText rechargeMoneyText;
private Button successButton;
private Button failButton;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_secondary);
//获取控件
initView();
}

private void initView() {
rechargeMoneyText = findViewById(R.id.id_edt_rechargeMoney);
successButton = findViewById(R.id.id_recharge_success);
failButton = findViewById(R.id.id_recharge_fail);
}
//确认按钮的点击方法
public void rechargesuccess(View view){
String money = rechargeMoneyText.getText().toString();
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("rechargemoney",money);
setResult(1,intent);
finish();
}
//取消按钮的点击方法
public void rechargefail(View view){
Intent intent = new Intent(this, MainActivity.class);
setResult(0,intent);
finish();
}

}

通过设置不同的结果码来区分是否充值成功。

如果也想让back键也有这样的效果,只需要重写onBackPressed()方法即可。

五、Activity回退栈和Task

5.1 Task

Task是Activity的集合,是一个概念,实际使用的Activity回退栈来存储Activity。

举个例子,应用A尝试开启摄像头,那么从这个应用启动到摄像头被开启的过程中,所有被启动的Activity组成一个Task,他们的管理由该应用的Activity回退栈管理。

一般来说每个应用的启动都会创建一个Task(具体情况视Activity启动模式决定)。因此系统中可以有多个Task,但是同一时刻只有一个Task在前台(能与之交互的),其他的都在后台。

5.2 Activity回退栈

Android系统采用栈结构来管理应用程序运行过程中所启动的Activity,即Activity回退栈。

如上图所示,一开始是Activity1处于栈顶,当Activity2被启动后,该Activity会被压入栈中,成为栈顶。同理Activity3类似。当Activity3被销毁后(也就是出栈),那么紧随其后的Activity2就会成为新的栈顶。

六、Activity的启动模式

  1. Standard标准模式

    Activity的默认启动模式。在该模式下,同一个Activity可以被多次实例化(即使存在且位于栈顶)。

  2. singleTop栈顶单例模式

    如果需要启动的Activity存在且位于栈顶,则不会重新创建,而是使用栈顶的Activity。但是,如果需要启动的Activity存在但不位于栈顶,那么仍创建新的Activity。

  3. singleTask栈内单例模式

    只允许同一个Activity在同一个Task中拥有一个Activity实例。如果系统中已经有了一个实例但不在栈顶,则会移除该Activity上的其他Activity,从而让其位于栈顶。 如果没有,则会创建一个新的Activity并将其压入栈顶。

  4. singleInstance全局单例模式

    只允许同一个Activity在系统中拥有一个Activity实例。如果启动的Activity不存在,会先创建一个新的Task,然后在创建Activity实例加入栈顶。如果已经存在,则将其移至栈顶。


Android开发学习笔记(四) —— 重拾Activity
http://example.com/2023/08/12/Android安全/Android开发学习笔记(四) —— 重拾Activity/
作者
gla2xy
发布于
2023年8月12日
许可协议