程序活动单元Activity

Activity在安卓中负责与用户交互的逐渐,每个安卓应用都会用Activity来显示界面及处理界面上的一些控件的事件。

Activity你可以抽象的理解为一个页面,假如说这个页面有个按钮点击就可以跳转到另外一个页面,是不是像浏览网页一样?一个Activity就相当于一个页面;

Activity的生命周期状态

Activity有它的生命周期,就是从创建到将它销毁的这个过程就被成为生命周期,它在此过程中有5种状态,分别是启动、运行、暂停、停止这几个状态

1.启动状态

启动暂停很短暂,启动后就会进入运行状态

2.运行状态

Activity在此时就是界面的最前端,它是可见的,有焦点的,而且还可以与用户进行交互,点击界面等操作

如果 Activity进入这个状态,就会尽可能的保持这个状态,即使出现内存不足的情况,安卓也会先销毁栈底的 Activity来确保当前的运行

3.暂停状态

Activity处于暂停状态,但是 Activity仍然可见,但是无法获取焦点,也无法进行操作,就比如弹出一个弹窗你必须先将弹窗关闭,然后才可以继续操作 Activity

4.停止状态

Activity处于不可见的状态时,他就处于停止状态

5.销毁状态

Activity处于销毁状态时,它会被清理出内存

启动和销毁状态都是过度状态,Activity不会在这两个状态停留

Activity生命周期方法

Activity的生命周期包括创建、可见、获取焦点、失去焦点、不可见、重修可见、销毁等环节,且每个环节都定义了响应的方法供调用

生命周期阶段 方法 描述
创建 onCreate() 当Activity首次被创建时调用。用于进行基本的初始化工作,如设置布局、绑定事件监听器、初始化成员变量等。接收一个 Bundle参数,可能包含之前保存的状态信息。
启动 onStart() 在Activity变得对用户可见(但尚未获得焦点)时调用。
运行 onResume() 当Activity开始与用户交互并获得焦点时调用。此时Activity处于前台,可接受用户输入。
暂停 onPause() 当系统准备启动或恢复另一个Activity(或其他UI组件)而导致当前Activity失去焦点时调用。应在此处保存关键数据,释放系统资源(如摄像头、传感器等),因为接下来Activity可能会被停止或销毁。
停止 onStop() 当Activity不再对用户可见(可能由于被另一个Activity完全覆盖或设备屏幕关闭)时调用。应在此处保存所有持久化数据,因为Activity可能被系统回收以释放内存。
重启 onRestart() 当已停止的Activity即将被重新启动并进入运行状态时调用。这是从停止状态返回到运行状态之间的过渡点,通常紧随 onStop()之后。
销毁 onDestroy() 在Activity被销毁前最后调用。用于清理所有未释放的资源,如关闭数据库连接、注销广播接收器等。此方法并非每次都会被调用,例如当系统资源紧张直接回收Activity时可能跳过。

注意:生命周期方法的调用顺序遵循一定的逻辑链,即 onCreate()onStart()onResume() → (onPause()onStop() | onRestart()onStart()onResume()) → onPause()onStop()onDestroy()。括号内表示当Activity被暂停后,如果它随后被重新启动(如用户按“返回”键回到该Activity),则会经历 onRestart()onResume()的调用;否则,如果Activity被直接停止并可能进一步销毁,则会经历 onStop()onDestroy()的调用。

此外,横竖屏切换等配置更改可能导致Activity重建,若未在 AndroidManifest.xml中指定相应的 configChanges属性,那么会按照 onPause()onSaveInstanceState()onStop()onDestroy()onCreate()onStart()onRestoreInstanceState()onResume()的顺序调用生命周期方法。如果指定了适当的 configChanges属性以处理旋转等事件,Activity可能不会被销毁重建,此时只会调用 onConfigurationChanged()方法。

⭐️ 测试生命周期代码

运行这段代码在logcat中就会清楚的看到程序在调用的过程

package fun.tanc.testactivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        Log.i("MainActivity","调用onCreate()");
    }
    @Override
    protected void onStart(){
        super.onStart();
        Log.i("MainActivity","调用onStart()");
    }
    @Override
    protected void onResume(){
        super.onResume();
        Log.i("MainActivity","调用onResume()");
    }
    @Override
    protected void onPause(){
        super.onPause();
        Log.i("MainActivity","调用onPause()");
    }
    @Override
    protected void onStop(){
        super.onStop();
        Log.i("MainActivity","调用onStop()");
    }
    @Override
    protected void onDestroy(){
        super.onDestroy();
        Log.i("MainActivity","调用onDestroy()");
    }
    @Override
    protected void onRestart(){
        super.onRestart();
        Log.i("MainActivity","调用onStop()");
    }
}

image-20240402200531262

程序运行到 onResume()时就不会在继续向下执行,等等与用户交互,点击模拟器的返回按钮可以看到下面的状态

image-20240402201000778

为什么会没有调用 onRestart()方法呢,因为此时 Activity还只有一个,多个切换就需要了

⚠️安卓设备在横竖屏切换时,Activity的生命周期行为取决于应用程序对 AndroidManifest.xmlActivity元素的 android:configChanges属性的设置。以下是两种常见情况下的生命周期变化:

1️⃣未设置或未指定 orientationandroid:configChanges

在这种情况下,当设备从竖屏切换到横屏或从横屏切换到竖屏时,系统默认会销毁当前的Activity,并重新创建一个新的Activity实例来适应新的屏幕方向。此时Activity的生命周期会经历如下过程:

  1. 保存状态

    • onSaveInstanceState(Bundle outState):在Activity被销毁前,系统会调用此方法让应用有机会保存其瞬态状态。传递的 Bundle对象可用于存储需要保留的数据。
  2. 销毁旧Activity

    • onPause():活动失去焦点,即将转入后台。
    • onStop():活动对用户不可见。
    • onDestroy():活动被销毁,资源被释放。
  3. 创建新Activity

    • onCreate(Bundle savedInstanceState):新Activity实例被创建。savedInstanceState参数包含了之前保存的状态信息。
    • onStart():新Activity对用户可见。
    • onRestoreInstanceState(Bundle savedInstanceState)(可选):如果在 onCreate()中有需要,可以重写此方法来处理恢复特定UI状态。参数同样包含之前保存的状态。
    • onResume():新Activity获得焦点,开始与用户交互。

2️⃣设置了 orientationandroid:configChanges

如果在 AndroidManifest.xml中为Activity指定了 android:configChanges="orientation"(或包含 orientation在内的多个配置项),则告诉系统当屏幕方向改变时,由应用程序自己处理配置变更,而非重新创建Activity。此时,横竖屏切换时Activity的生命周期变化如下:

  1. 配置变更通知
    • onConfigurationChanged(Configuration newConfig):当屏幕方向改变时,系统会调用此方法。开发者可以在该方法中根据新的配置信息更新UI布局或资源。

注意: 在这种情况下,上述销毁和创建Activity的整个过程被省略,Activity保持原有的实例,不会经历 onSaveInstanceState()onPause()onStop()onDestroy()onCreate()onStart()onRestoreInstanceState()这一系列生命周期方法的调用。只有 onConfigurationChanged()会被触发,使得应用能够在不重新创建Activity的情况下适应新的屏幕方向。

总结来说,是否处理横竖屏切换导致的Activity重建,取决于开发者是否选择在 android:configChanges中声明 orientation。如果不声明,系统默认销毁并重新创建Activity,完整走一遍生命周期;如果声明了,那么系统仅调用 onConfigurationChanged(),由开发者自行处理布局变化,避免了Activity的重建。

配置文件:

        <activity
            android:name=".MainActivity"
            android:configChanges="orientation|keyboardHidden"
            android:exported="true">

如果你只希望某一个界面一直处于竖屏或者横屏状态,不随着手机方向改变就可以配置

android:screenOrientation="portrait" //竖
android:screenOrientation="landscape" //横

Activity的创建、配置、启动和关闭

创建Activity

创建的方法有两种:

1️⃣ 在存放Activity的文件夹下右键选择,这个创建就自动给你配置好 xml布局文件Manifset.xml文件了

image-20240402202216886

可以看到 Activity名和 xml布局文件名

image-20240402202411085

创建好自动创建的代码

package fun.tanc.testactivity;

import android.os.Bundle;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class MainActivity2 extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main2);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
    }
}

2️⃣ 第二种方法就是自己创建一个普通的 java类,这个就需要自己配置和创建布局文件

这是创建好的样子

public class ActivityTwo {
}

其他部分和默认创建的MainActivity差不多,可以直接套用

package fun.tanc.testactivity;

import android.app.Activity;
import android.os.Bundle;

import androidx.annotation.Nullable;


public class ActivityTwo extends Activity { //继承了 Activity
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) { //需要一个创建方法
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activityTwo_main); //这里会报错是因为没有创建xml布局文件
    }
}

随后配置 AndroidManifest

//新增加在application中  
        <activity
            android:name=".ActivityTwo" //这里配置Activity的名字
            android:exported="true" /> //表示是否可以跳转到其他界面

还需要自己创建布局文件, 点击存储xml布局文件的文件夹创建

image-20240402203846252

image-20240402203950124

创建好的代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</LinearLayout>

启动Activity

这里启动和关闭只是提一嘴,到后面才会具体实现,现在了解了解就好了

1️⃣ 启动

需要使用 startActivity()来启动创建的 Activity,使用方法如下

使用了Intent意图,意图是Android应用程序直接各个组件之间通信的桥梁,现在只需要了解一下就好了

第一个表示当前的 Activity

第二个参数表示你要启动哪个 Activity

Intent intent = new Intent();
intent.setClass(MainActivity.this,SecondActivity.class);
startActivity(intent);
 
 //或者
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);

2️⃣关闭

关闭需要使用到 finish(),直接调用即可,以下代码演示了点击关闭的方法

Button btn = (Button)findViewById(R.id.btn1)
btn.setOnClickListener(new View.OnClickListener(){
	@Override
	public void onClick(View v){
		finish();
	}
});

Intent与IntentFilter

现在只需要了解这个东西,后面案例做一下就明白了
在一个应用程序中一般都是由很多个

Activity组成,每个 Activity也表示了不同的功能,如果用户需要在两个Activity中切换,就必须使用到 Intent,它用于同一个或者不同一个应用程序间的绑定;

Intent就是意图的意思,是程序中各组件之间进行交互的一种重要方式,它不仅仅可以指定当前组件要执行的动作,还可以在不同组件之间进行数据传递。
它分为两种模式

1️⃣ 显式Intent

用于明确自己需要跳转的目标的组件,可以直接指定需要跳转的

Activity如下

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

⭐️ 案列

1.创建一个Activity和布局文件,还需要设置 Manifest文件

package fun.tanc.testactivity;

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

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;

public class SecondActivity extends AppCompatActivity {
    public final  String TAG = "SecondActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_second);
    }
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SecondActivity">
    <TextView
        android:id="@+id/second"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="第二个界面"
        android:textSize="40dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
        <activity
            android:name=".SecondActivity"
            android:exported="true" />

2.编写主 Activity的布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="点击进入"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

3.编写主 Activity的java代码

public class MainActivity extends AppCompatActivity {
    Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        btn = findViewById(R.id.btn);
        //监听button控件
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //点击跳转
                Intent intent = new Intent();
                intent.setClass(MainActivity.this,SecondActivity.class);
                startActivity(intent);
            }
        });
    }

启动点击按钮就可以跳转到第二个界面了

2️⃣ 隐式意图

隐式意图不会明确的指定目标组件,它广泛的应用在不同的应用程序之间进行消息传递,它和

IntentFilter搭配使用使用它来匹配相应的组件

以下表格列出了 IntentFilter匹配时涉及的主要属性及其用途:

属性 描述
Action 字符串标识,表示Intent所要执行的操作类型。可以是系统预定义的动作(如 ACTION_VIEWACTION_SEND等)或自定义动作。一个 Intent可以包含一个动作,而 IntentFilter可以声明多个动作,只要 Intent中的动作与 IntentFilter中任意一个动作完全匹配即可。
Category 字符串标签,用来进一步描述 Intent的上下文或目的。例如,CATEGORY_BROWSABLE表示Intent可以通过浏览器访问。Intent可以包含零个或多个类别,而 IntentFilter中声明的所有类别都必须与 Intent中对应的类别完全匹配。如果 Intent没有指定类别,系统会默认添加 CATEGORY_DEFAULT
Data 描述了 Intent所操作的数据。包括以下子属性:
  • Scheme:URI的协议部分,如 httphttpscontentfile等。
  • Host:对于某些scheme有意义,如网络地址中的域名。
  • Port:与host相关联的端口号。
  • Path:URI路径部分,用于定位特定资源。
  • MimeType:指定数据类型的MIME字符串,如 text/plainimage/jpeg等。表明数据的内容格式。

IntentFilter可以指定以上数据子属性的一个或多个组合,作为数据匹配条件。Intent中的数据需与 IntentFilter中指定的全部数据条件匹配(除非某些条件在 IntentFilter中未指定)。

| Extra | 可选的附加信息,以键值对形式存在,通常用于传递额外的数据给目标组件。虽然 IntentFilter本身不直接匹配 Extra,但目标组件在接收到 Intent后可以通过检查 Extra来确定是否满足特定业务逻辑要求。 |

综上所述,IntentFilter匹配主要关注 ActionCategoryData属性。在隐式Intent的场景下,只有当这些属性全部匹配成功时,系统才会认为 Intent与某个 IntentFilter相匹配,进而启动相应的目标组件(如Activity、Service或BroadcastReceiver)。Extra虽然不影响 IntentFilter的匹配过程,但它承载的具体数据对于目标组件接收并正确处理Intent至关重要。

⭐️ 这里直接上案列

修改 Manifest文件

        <activity
            android:name=".SecondActivity"
            android:exported="true" >
            <intent-filter>
                <action android:name="fun.tanc.START" /> //设置actio和category
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

修改监听代码

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //点击跳转
                Intent intent = new Intent();
                intent.setAction("fun.tanc.START");
                startActivity(intent);
            }
        });

未完待续....