本文将会从活动的生命周期、启动模式、Intent数据传输、最佳实践等多维度来讲解Activity,希望对你有用
生命周期
- 深入理解活动的生命周期,可以帮助我们更加流畅地编程,并在管理系统资源方面更加游刃有余
活动状态
每个活动在生命周期中最多有运行、暂停、停止、销毁四种状态
- 1)运行状态:当一个活动在返回栈的栈顶时,该活动就处于运行状态,如果回收处于运行状态的活动,会带来极差的用户体验
- 2)暂停状态 :当活动不再处于栈顶,但
仍然可见
时,则活动进入了暂停状态(并非每个活动都会占满屏幕);处于暂停状态的活动属于完全存活;只有在内存非常紧张的情况下,系统才会考虑回收该状态的活动 - 3)停止状态 :当活动不再处于栈顶,且
完全不可见
时,会进入停止状态;系统仍然会为该状态的活动保存相应的状态和成员变量;当其他地方需要内存时,系统可能会回收该状态的活动 - 4)销毁状态:当活动从返回栈中移除后就变成了销毁状态,系统会最为倾向于回收处于该状态的活动,以保证系统的内存充足
生存期 & 回调方法
Activity有如下7个回调函数,覆盖活动生命周期的每个环节:
- onCreate() :
在活动第一次被创建时调用,用于完成活动的初始化操作
,如:布局加载、绑定事件 - onStart():在活动由不可见变为可见时调用
- onResume() :在活动准备好和用户交互时调用,
该活动会处于返回栈的栈顶,且活动处于运行状态
- onPause() :
在系统准备去启动或恢复其他活动时调用
,通常会在该方法中释放比较消耗CPU的资源,以及某些关键数据
- onStop() :
在活动完全不可见时调用
,和onPause()方法的主要区别在于:如果启动的新活动是对话框类的活动,onPause()方法会执行,而onStop()方法并不会执行 - onDestory() :
在活动被销毁前调用,之后活动的状态将变为销毁状态
,常用于完成内存释放等操作
- onRestart():在活动由停止变为运行状态前调用,即让活动重新启动
以上,除了onRestart(),其他方法都是两两相对的,所以,活动可分为三种生存期:
完整生存期
:活动在onCreate()方法和onDestory()方法之间所经历的,即为完整的生存期可见生存期
:活动在onStart()方法和onStop()方法之间所经历的,为可见生存期,即活动对用户总是可见的,即便可能无法和用户交互
;可通过onStart()和onStop()方法来合理地管理对用户可见的资源 ,以保证处于停止状态的活动不会占用过多内存前台生存期
:活动在onResume()方法和onPause()方法之间所经历的,即为前台生存期 =》活动总是处于运行状态,活动可以和用户交互
启动模式(launchMode)
- Activity由任务(Task)栈管理,一个任务就是一组存放在栈中活动的集合,该栈也被称为
返回栈
(Back Stack) - 默认情况下,每当启动一个新的Activity,该Activity就会被加入到返回栈中,并处于栈顶的位置;当按下Back键或调用finish()方法区销毁活动时,处于栈顶的Activity机会出栈(系统总是会显示栈顶的Activity给用户)
- 启动模式一共有4种,分别为standard、singleTop、singleTask和singleInstance,可在AndroidManifest.xml中通过给标签指定
android:launchMode
属性来指定启动模式
1)standard(标准模式)
- standard是默认的启动模式,即标准模式,在不显示指定启动模式的情况下,所有的活动都默认使用该启动模式;每启动一个Activity,都会创建一个新的实例
- 如果目标活动FirstActivity已经在栈顶,再次启动活动,还会再创建一个新的活动实例并压入栈中
- 应用场景:适用于不希望保留状态 或者每次启动都需要创建新实例的场景,例如,一个浏览器的多个页面,每次打开新的页面都会创建一个新的实例
java
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.example.myapplication1.R;
import com.example.myapplication1.intent.SecondActivity;
public class OneActivity extends AppCompatActivity {
private Button oneBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_one);
oneBtn = findViewById(R.id.oneBtn);
oneBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(OneActivity.this, OneActivity.class)); // 验证standard和singleTop启动模式
// startActivity(new Intent(OneActivity.this, SecondActivity.class));
}
});
}
}
- 修改AndroidManifest.xml,添加launchMode属性:
xml
<activity
android:name=".launch.OneActivity"
android:exported="true"
android:launchMode="standard" />
- xml布局:
xml
<Button
android:id="@+id/oneBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="this is OneActivity"
android:gravity="center"
android:textAllCaps="false"/>
-
在OneActivity上启动OneActivity显得有些奇怪,这里只是为便于我们理解、验证,一般不会这么写
-
多次点击按钮,从logcat过滤出的日志可看出,每次点击按钮都会创建一个新的Activity入栈,同时,要多次点击返回键才能退出程序
-
触发点击事件后,从OneActivity跳转到TwoActivity时,修改代码:
java
startActivity(new Intent(OneActivity.this, SecondActivity.class));
输出结果如下,如果目标Activity不在栈顶,就会新建Activity并入栈:
2)singleTop(栈顶复用模式)
- singleTop:当要启动的目标Activity已经处于栈顶时,不会创建新的实例,会复用栈顶的Activity,并且其
onNewIntent()
方法会被调用;如果目标Activity不在栈顶,则跟standard一样会重复创建新的Activity实例 - 应用场景:适合需要频繁打开和关闭的场景,例如:通知栏点击后打开的页面;一个推送通知的详情页面,用户可能会频繁打开和关闭它,使用SingleTop模式可以避免创建多个实例
- 将前面用到OneActivity的launchMode属性修改为
singleTop
,重新运行OneActivity,多次点击按钮,只会创建一个Activity,并一直复用
3)singleTask(栈内复用模式)
- singleTask:每次启动活动时,系统会首先检查任务栈中是否存在该Activity的实例,如果发现存在,则直接复用该实例(目标Activity会调用onRestart()方法重新启动),在该目标Activity上的Activity都会调用onDestroy()方法清除(被弹出栈);如果任务栈中没有该Activity,则创建新的实例
- 应用场景:适合作为应用的主页或者一个任务的开始页面,例如:邮箱应用的主页面;一个邮箱应用的主Activity,用户从任何地方返回到主页面时,都应该看到同一个Activity实例,而不是创建新的实例
- TwoActivity、OneActivity重写onRestart()和onDestroy()方法
java
@Override
protected void onRestart() {
super.onRestart();
Log.d("OneActivity","---onRestart---");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d("OneActivity","---onDestroy---");
}
- 重新运行OneActivity,点击按钮,OneActivity跳转到TwoActivity,在TwoActivity中重新启动OneActivity,则会发现返回栈中已经有OneActivity实例了,于是TwoActivity出栈【onDestroy()方法被调用】,OneActivity来到栈顶【onRestart()方法被调用】
4)singleInstance(全局单例模式)
- singleInstance:全局复用,不管哪个Task栈,只要存在目标Activity,就复用;每个Activity占用一个新的Task栈
- 如果应用中的活动要允许其他程序调用,且其他应用和我们的应用可共享该活动实例,前面三种启动模式无法做到(同一个活动在不同的返回栈中入栈需要创建新的实例),而使用singleInstance模式就可以解决该问题,实现只有一个单独的返回栈来管理该活动,不管是哪个应用来访问该活动,都共用同一个返回栈,如此可解决共享活动实例的问题
- 应用场景:适合需要与程序分离开的页面,如拨打电话、系统通讯录等;一个来电页面,它应该总是单独存在,不受其他Activity的影响。
- 修改launchMode属性
singleInstance
,并在Activity中输出返回栈的唯一标识:taskId
java
| | |
|--|--|
| | |
Log.d("OneActivity","task id is " + getTaskId());
- 输出结果如下,三个Activity都分别放在不同的返回栈中
基本使用
下面我用一个复杂的案例重新串下Activity相关的知识点:
- 第一个Activity:
java
import androidx.appcompat.app.AppCompatActivity;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.myapplication1.R;
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
// 输出日志,便于测试Activity的启动模式
Log.d("FirstActivity","---onCreate---");
Log.d("FirstActivity","taskId ="+getTaskId() + ", hasCode =" + hashCode());
printTaskLog();
TextView view = findViewById(R.id.firstView);
view.setText("this is first activity");
Button detailBtn = findViewById(R.id.detail);
detailBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建Intent对象的三种方式
// 1. Intent(Context packageContext, Class<?> cls)
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
// 2. Intent intent = new Intent();
// intent.setClass(FirstActivity.this, SecondActivity.class);
// 3. intent.setComponent(new ComponentName(FirstActivity.this, SecondActivity.class));
startActivity(intent);
}
});
}
private void printTaskLog() {
try {
ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
Log.d("current task name", activityInfo.taskAffinity);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
- 页面布局activity_first.xml:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".intent.FirstActivity">
<TextView
android:id="@+id/firstView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello,Android developer!" />
<Button
android:id="@+id/detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="查看详情"/>
</LinearLayout>
- 第一个Activity跳转来到的第二个Activity:
java
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.example.myapplication1.R;
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Log.d("SecondActivity","---onCreate---");
Log.d("SecondActivity","taskId ="+getTaskId() + ", hasCode =" + hashCode());
printTaskLog();
Button button = findViewById(R.id.second);
button.setText("this is second activity");
button.setTextColor(Color.RED);
button.setBackgroundColor(Color.BLUE);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(SecondActivity.this,"SecondActivity向ThirdActivity传递数据",Toast.LENGTH_LONG).show();
Intent intent = new Intent(SecondActivity.this, ThirdActivity.class);
startActivity(intent);
}
});
}
private void printTaskLog() {
try {
ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
Log.d("current task name", activityInfo.taskAffinity);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
- 页面布局activity_second.xml:
xml
<?xml version="1.0" encoding="utf-8"?>
<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=".intent.SecondActivity">
<Button
android:id="@+id/second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/detail"/>
</LinearLayout>
- 第二个Activity跳转来到的第三个Activity:
java
import androidx.appcompat.app.AppCompatActivity;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.example.myapplication1.R;
public class ThirdActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
Log.d("ThirdActivity","---onCreate---");
Log.d("ThirdActivity","taskId ="+getTaskId() + ", hasCode =" + hashCode());
printTaskLog();
TextView view = findViewById(R.id.third);
view.setText("this is third activity");
// this: ThirdActivity对象
view.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.third){
// 结束当前的活动页面
finish();
}
}
private void printTaskLog() {
try {
ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
Log.d("current task name", activityInfo.taskAffinity);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
- 页面布局activity_third.xml:
xml
<?xml version="1.0" encoding="utf-8"?>
<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"
tools:context=".intent.ThirdActivity">
<Button
android:id="@+id/third"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/thirdText"/>
</LinearLayout>
- 控件触发事件的两种常用写法(以Click点击事件举例):
1)控件调用set开头的监听事件方法
View.java:
java
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
2)实现监听事件的接口View.OnClickListener,重写事件的onClick(View v)处理方法:
- AndroidManifest.xml清单文件中加入三个Activity的声明,没有指定launchMode属性,则默认为standard:
xml
<activity
android:name=".intent.ThirdActivity"
android:exported="true" />
<activity
android:name=".intent.SecondActivity"
android:exported="true" />
<activity
android:name=".intent.FirstActivity"
android:exported="true"
android:launchMode="standard"/>
运行结果说明:
- 运行FirstActivity,依次点击FirstActivity、SecondActivity、ThirdActivity中的Button按钮,输出结果如下图所示,三个Activity被放入id为83,名称为com.example.myapplication1的任务栈中,又hash值不同,则可认为每启动一个Activity,都会重新创建一个Activity栈帧压入任务栈中
- 当跳转到ThirdActivity时,点击Button,触发点击事件,调用finish()方法,会结束ThirdActivity,跳转到SecondActivity
- 启动模式使用singleInstance,修改AndroidManifest.xml中Activity的launchMode属性:
android:launchMode="singleInstance"
如下图可见,任务栈的唯一标识taskId不同,即存放每个Activity的Task栈不同,每创建一个Activity,都会生成一个新的任务栈来存放Activity
最佳实践
随时随地退出程序
- 当跳转的页面足够多后,退出程序需要连按多次Back键才行,Home键只是将程序挂起,并没有退出程序
- 如果程序需要一个注销或退出的功能,则必须要实现随时随地能退出程序
- 实现思路:用集合类管理所有的活动,代码如下:
java
import android.app.Activity;
import android.os.Build;
import java.util.ArrayList;
import java.util.List;
public class ActivityManager {
private static List<Activity> activityList = new ArrayList<>();
// 将当前创建的活动添加到管理器中
public static void add(Activity activity){
activityList.add(activity);
}
// 将要销毁的活动从管理器中移除
public static void remove(Activity activity){
activityList.remove(activity);
}
// 完成管理器中的所有活动,保证能随时随地退出程序
public static void finishAll(){
for (Activity activity : activityList) {
if (!activity.isFinishing()){
activity.finish();
}
}
}
public static List<Activity> getAll(){
return activityList;
}
}
- 编写Activity测试:
java
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.example.myapplication1.R;
public class ManagerActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_manager);
Log.d("ManagerActivity","current activity: " + getClass().getSimpleName());
ActivityManager.add(this);
findViewById(R.id.managerBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(ManagerActivity.this,LogoutActivity.class));
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityManager.remove(this);
}
}
java
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.example.myapplication1.R;
public class LogoutActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_logout);
ActivityManager.add(this);
Log.d("logout before",ActivityManager.getAll().toString());
findViewById(R.id.logoutBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 完全退出应用程序
ActivityManager.finishAll();
// Log.d("logout going",ActivityManager.getAll().toString());
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityManager.remove(this);
Log.d("logout after",ActivityManager.getAll().toString());
}
}
- 为能明显看出效果,可以多跳转几个页面,最后来到LogoutActivity,点击Button,直接退出应用程序,来到桌面
注意:需要在跳转Activity中重写onDestroy()方法并在管理器中移除当前Activity,在onCreate()方法中,触发点击事件前加入ActivityManager.add(this);
java
@Override
protected void onDestroy() {
super.onDestroy();
ActivityManager.remove(this);
}
- 活动管理器中的活动在执行finishAll()前后的变化:
参考
- 郭霖《第一行代码》第二版
- Android开发者文档