一文带你精通Android中的Activity

本文将会从活动的生命周期、启动模式、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()前后的变化:

参考

相关推荐
Eastsea.Chen1 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年8 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿11 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神12 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛12 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法13 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter14 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快15 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl15 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
麦田里的守望者江16 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin