Android Activity 详解

887 阅读9分钟

这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战

文章目录

Activity介绍

介绍
Activity 是 Android 系统的核心组件(系统的核心组件是由系统进行管理和维护的),用于处理 UI 相关的业务

如何创建 Activity
1、自定义类,继承自 Activity
2、注册,在 AndroidMenifest.xml 文件中,在<application>节点下,创建新的<activity>节点,并指定android:name属性,取值为自定义Activity类的包名和类名。如果想作为启动的第一个Activity,则在节点下增加<intent-filter>,修改标题栏的名称可以增加label

启动 Activity

Intetnt i = new Intent(当前窗口对象,目标窗口对象);
startActivity(i);

举例1:打开新的 Activity

现在我们在 MainActivity 中打开一个新窗口 SecondActivity,然后点击 SecondActivity 中按钮再打开 MainActivity

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="主窗口"
        android:textSize="20sp"
        android:textColor="#222222"
        android:gravity="center"/>

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="启动第二个窗口"
        android:layout_marginTop="20dp"/>

</LinearLayout>

MainActivity

/*
* 应用启动时,会启动主窗口
* 系统会新建主窗口对象
* 然后自动调用onCreate()方法
* */
public class MainActivity extends AppCompatActivity {
    private Button button;

    /*
     * 在窗口父类 Activity 中定义的方法
     * 按照安卓开发规范,在子类中,必须重写
     * 必须调用父类的 onCreate()方法:super.onCreate(savedInstanceState);
     * */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //从父类继承的方法
        //在当前窗口中显示指定界面
        setContentView(R.layout.activity_main);

        //界面显示后,从当前显示的界面上获得控件
        button = findViewById(R.id.button1);
        //给按钮设置监听器
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //使用外部类对象的this
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }
}

activity_second.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="第二个窗口"
        android:textColor="#222222"
        android:textSize="20sp" />

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="启动主窗口" />

</LinearLayout>

SecondActivity

public class SecondActivity extends AppCompatActivity {
    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        button = findViewById(R.id.button2);
        //这里使用了 lambda 表达式
        button.setOnClickListener(view -> {
            Intent intent = new Intent(SecondActivity.this, MainActivity.class);
            startActivity(intent);
        });
    }
}

AndroidManifest.xml中进行注册

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testapplication">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.NoActionBar">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

运行程序:
在这里插入图片描述

停止Activity
上边的例子中,从第二个窗口跳转主窗口(SecondActivity 跳转 MainActivity)时,是又打开了新的 MainActivity ,其实我们直接把第二个窗口(SecondActivity)关掉就回到了主窗口(MainActivity)。

在 Activity 内部调用 finish() 方法,即可停止/销毁当前 Activity。

修改 SecondActivity 中按钮点击事件

button.setOnClickListener(view -> {
            finish();
        });

运行结果:
在这里插入图片描述

调用 finish() 方法的效果等同于按下设备上的 Back 键,导致 Activity 的生命周期方法执行:onPause()->onStop()->onDestroy()(生命周期方法的介绍在下面)

停止当前应用中所有Activity
可以使用 List 集合记录下每一个激活的 Activity 对象,当需要全部停止时,遍历该集合,依次调用各 Activity 对象的 finish()方法

记录 Activity 对象:在每一个 Activity 的onCreate()中,将当前 Activity 对象添加到List 集合,在每一个 Activity 的onDestory()中,将当前的 Activity 对象从 List 集合中移除

生命周期

Activity 的生命周期表现为会执行一系列的方法,包括:

  • onCreate()//创建时
  • onStart()
  • onRestart()
  • onResume()
  • onPause()
  • onStop()
  • onDestroy()//销毁时

我们可以在刚才的两个 Activity 中重写以上方法,并进行打印,来观察2个 Activity 切换时的生命周期

从第一个 Activity 跳转到第二个 Activity 时(简写 A -> B):

D/A: onCreate()
D/A: onStart()
D/A: onResume()       
D/A: onPause()

D/B: onCreate()
D/B: onStart()
D/B: onResume()

D/A: onStop()

此时显示在 B,现在直接按返回键,来销毁 B

D/B: onPause()

D/A: onRestart()
D/A: onStart()
D/A: onResume()

D/B: onStop()
D/B: onDestroy()

如果 SecondActivity 中按钮点击依旧跳转新的 MainActivity

D/SecondActivity: onPause()
D/MainActivity: onCreate()
D/MainActivity: onStart()
D/MainActivity: onResume()
D/SecondActivity: onStop()

现在跳转的 MainActivity 已经不是原来的 Activity 了,我们可以根据 hashCode 判断是不是相同 Activity,可在 onCreate() 中打印验证下

this.hashCode()

总结生命周期执行顺序

  • 当 Activity 第一次激活时,onCreate()->onStart()->onResume()
  • Activity 被置于后台时(例如回到桌面时),onPause()->onStop()
  • Activity回到前台时(例如从桌面切回Activity时),onRestart()->onStart()->onResume()
  • 退出Activity时,onPause()->onStop()->onDestroy()
  • 查看 Activity 是否是同一个,可以根据this.hashCode()来区分

根据上边可以总结出Activity的状态

  1. 运行态:表现为 Activity 处于前台,可见,可控,生命周期停留在onResume()
  2. 暂停态:表现为 Activity 局部可见,但不可控,生命周期方法停留在onPause()方法,通常是被另一个非全屏的Activity遮挡导致
  3. 停止态:表现为该 Activity 被置于后台,完全不可见,生命周期停留在onStop()
  4. 终止态:表现为该 Activity 已经被回调了onDestroy()方法,即已经销毁

在停止状态前,会短暂的表现为暂停态,onPause()是 Activity 被遮挡时调用的方法,真正被置于后台时调用的方法是onStop()

Activity的启动模式

Activity 的启动模式决定了 Activity 实例/对象的数量。在默认情况下,每次激活Activity,都会创建新的实例/对象。

这里首先需要了解 任务栈/回退栈 的相关概念

任务栈/回退栈
记录了同一个任务中,各个已经激活的 Activity 的先后顺序,以至于用户在点击设备上的 Back 键时,系统能知道需要将曾经打开的哪个 Activity 恢复到前台

压栈
激活新的 Activity 时,默认新的 Activity 会执行压栈,压栈后,该 Activity 将处于栈顶位置,即该 Activity 显示在前台,栈内其它 Activity 均被置于后台

弹栈
当前 Activity 被销毁时,栈内其他 Activity 均向上弹起,原栈顶的 Activity 被销毁的同时,原排名第二的 Activity 将获得栈顶位置

如何配置启动模式
在 AndroidManifest.mxl 中,为<activity>节点配置android:launchMode属性即可配置 Activiy 的启动模式

启动模式
standard:(默认)标准模式,每次激活 Activity,都会创建新的实例/对象

singleTop:栈顶唯一,表现为当 Activity 处于栈顶位置时,反复激活并不会创建新的实例,也不会出现压栈操作,反之,当 Activity 不处于栈顶位置时,激活时会创建新的实例,并压栈

singleTask:任务栈内唯一,表现为当任务栈中没有当前 Activity 时,会创建新的实例,并压栈,反之,当任务栈中已经存在当前 Activity 时,不会创建新的实例,原本在栈内处于该 Activity 偏上方的其他 Activity 将全部会被强制出栈,使得该被激活的 Activity 获得栈顶位置

singleInstance:实例唯一,该 Activity 的实例最多只存在1个,该 Activity 将强制独占1个新的任务栈,且该任务栈中有且仅有1个 Activity

测试SingleTop

修改 activity_main.xml,增加一个显示 hashcode 的 TextView 和 一个重复激活 MainActivity 的按钮

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="MainActvity"
        android:textColor="#222222"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/tv_hash_code"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textColor="#222222"
        android:textSize="20sp" />

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="激活MainActivity" />

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="激活SecondActivity" />

</LinearLayout>

修改 MainActivity,让 button2 重复激活 MainActivtity,让 TextView 显示 hashCode

public class MainActivity extends AppCompatActivity {
    private Button button1;
    private Button button2;
    private TextView tv_hash_code;

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

        tv_hash_code = findViewById(R.id.tv_hash_code);
        tv_hash_code.setText(this.hashCode()+"");

        button1 = findViewById(R.id.button1);
        button1.setOnClickListener(view -> {
            Intent intent = new Intent(MainActivity.this, MainActivity.class);
            startActivity(intent);
        });

        button2 = findViewById(R.id.button2);
        button2.setOnClickListener(view -> {
            Intent intent = new Intent(MainActivity.this, SecondActivity.class);
            startActivity(intent);
        });
    }
}

修改 AndroidManifest.xml 中 MainActivity 启动模式为 singleTop

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testapplication">

    <application
        ......>
        <activity android:name=".MainActivity"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

运行结果:
在这里插入图片描述
点击激活 MainActivity 按钮,页面中的 hashCode 并没有改变,也就说明并没有启动新的Activity

打开 SecondActivity,然后点击 SecondActivity 中的打开 MainActivity 按钮,发现hashCode 已经跟第一次打开 MainActivity 不同了,所以这是打开了一个新的 MainActivity

测试singleTask

现在修改两个 Activity 的启动模式如下

<activity android:name=".MainActivity"
            android:launchMode="standard">
            ......
</activity>

<activity android:name=".SecondActivity"
            android:launchMode="singleTask"/>

运行程序:

启动应用打开了 MainActivity,hashCode 为 313
然后点击页面上的激活 MainActivity,hashcode 为 393
再点击激活 SecondActivity,hashcode 为 209
点击 SecondActivity 中的按钮激活 MainActivity,hashcode 为 100
再点击 MainActivity 中按钮激活新 MainActivity,hashcode 为 977
在这里插入图片描述
此时任务栈的任务如下:
在这里插入图片描述
此时在打开的 MainActivity 中打开 SecondActivity,hashCode为 209,跟第一次打开的相同
在这里插入图片描述

现在 SecondActivity 处于栈顶,而之前处于它上面的两个 MainActivity 已经被销毁了
在这里插入图片描述
我们可以验证一下,现在按 Back 键,或者手势返回,MainActivity 的 hashcode 应该是 393
在这里插入图片描述
测试singleInstance
指定为 singleInstance 模式的 Activity 会启用一个新的返回栈来管理这个 Activity。

使用情景如下:假设我们程序中有一个 Activity,是允许其他程序调用的,如果想实现其他程序和我们程序共享这个 Activity 实例,在这种模式下,会有一个单独的返回栈来管理这个 Activity,不管哪个应用程序访问这个 Activity,都公用一个返回栈,也解决了共享 Activity 实例的问题

将 SecondActivity改为 singleInstance

另外当Activity的启动模式被配置为 singleTask 或 singleInstance 时,执行效果可能受到android:taskAffinity(亲属关系)的影响

Activity的最佳实践(拓展学习)

以下是 《第一行代码 Android 第3版》中的例子,由于是 kotlin 语言,所以做为拓展来学

知晓当前是在哪一个 Activity

open class BaseActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("BaseActivity",javaClass.simpleName)
    }
}

javaClass表示获取当前实例的 Class 对象,相当于在 Java 中调用 getClass 方法,然后再调用 simpleName 获取当前实例的类名

现在让 BaseActivity 成为项目所有 Activity 的父类,为了使其能够被继承,在类名前加上 open关键字

然后让其他 Activity 继承 BaseActivity 即可

class MainActivity : BaseActivity() {
    ......
}

再次运行程序,每当我们进入一个 Activity 该 Activity 类名就会被打印出来
在这里插入图片描述

随时随地退出程序

新建一个单例类 ActivityCollector 作为 Activity 的集合

object ActivityCollector {
    private val activities = ArrayList<Activity>()
    
    fun addActivity(activity: Activity){
        activities.add(activity)
    }
    
    fun removeActivity(activity: Activity){
        activities.remove(activity)
    }
    
    fun finishAll(){
        for(activity in activities){
            if(!activity.isFinishing){
                activity.finish()
            }
        }
        activities.clear()
        //可以加上杀掉进程的代码,保证程序完全退出
        //android.os.Process.killProcess(android.os.Process.myPid())
    }
}

我们调用 activity.isFinishing 来判断 Activity 是否正在销毁中,因为 Activity 还可能通过按下 Back 键等方式被销毁。如果 Activity 没有正在销毁中,我们再去调用它的 finish() 方法来销毁它

接下来修改 BaseActivity 中代码

open class BaseActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("BaseActivity",javaClass.simpleName)
        ActivityCollector.addActivity(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        ActivityCollector.removeActivity(this)
    }
}

在任何页面想退出程序只需调用 ActivityCollector.finishAll()

启动 Activity 的最佳写法

从 FirstActivity 启动 SecondActivity 需要传递哪些数据,你还要去阅读代码很麻烦,换种写法就可以轻松解决

class SecondActivity : BaseActivity() {
    ......
    companion object{
        fun actionStart(context: Context,data1:String,data2:String){
            val intent = Intent(context,SecondActivity::class.java)
            intent.putExtra("param1",data1)
            intent.putExtra("param2",data2)
            context.startActivity(intent)
        }
    }
}

这样写的好处是 SecondActivity 所需数据在方法参数中全部体现了,另外还简化了启动 Activity 的代码

button.setOnClickListener {
	SecondActivity.actionStart(this,"data1","data2")
}