Android开发学习笔记(2)——探究活动

第二章——探究活动

1、活动是什么

活动(Activity)是最容易吸引用户的地方,它是一种可以包含用户界面的组件,主要用于和用户进行交互,一个应用程序中可以包含0个或者多个活动,但是不包含任何活动的应用程序很少见。

2、活动的基本用法

2.1、手动创建活动

上一章是系统帮我们创建的activity,这章我们先自己创建一个activity。

首先我们选择一个No Activity,相关配置如下

此时可以看到,虽然目录和我们上一章的项目差不多,但是app/src/main/java/com.example.activitytest目录是空的,现在右键com.example.activitytest创建一个activity,New->Activity->Empty Activity

这里两个都不用勾选,勾选上表示自动为FirstActivity创建一个对应的布局文件,勾选Launcher Activity表示会自动将FirstActivity设置为当前项目的主活动。目前我们不需要设置,后面我们会手动设置。

package com.example.activitytest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class FirstActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

帮我们写好了模版,onCreate比较简单,就是调用了父类的onCreate。

2.2、创建和加载布局

Android程序设计讲究的是逻辑和视图分离,每个活动最好都能对应一个布局,而布局就是用来显示界面内容的,因此我们现在手动创建一个布局文件。

首先创建一个layout目录

创建布局资源文件

![image-20230331142740515](/Users/whitebird/Library/Application Support/typora-user-images/image-20230331142740515.png)

Design是当前的可视化布局编辑器,在这里可以预览当前的布局,也可以通过拖放的方式编辑布局。而Code里面存放的是XML方式来编辑布局。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <Button
        android:id="@+id/button1"/>
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button 1"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

android:id定义了一个唯一标识符,@+id/id_name 是用来定义一个id。

android:layout_width指定当前button的宽度,match_parent是让它和父元素一样宽。

android:layout_height指定当前button的高度,wrap_content表示button高度只要能刚好包含里面的内容就行。

android:text指定button中显示的文字内容。

接下来我们要做的就是在活动中加载这个布局。

package com.example.activitytest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class FirstActivity extends AppCompatActivity {

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

setContentView方法用来给当前活动加载一个布局,而它的参数是一个布局文件的id。

2.3、在AndroidManifest.xml中注册

所有的活动都要在AndroidManifest.xml中进行注册才能生效,而实际上FirstActivity已经在AndroidManifest.xml中注册过了,我们打开AndroidManifest.xml看看

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

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.ActivityTest"
        tools:targetApi="31">
        <activity
            android:name=".FirstActivity"
            android:exported="false" />
    </application>

</manifest>

可以看到活动的注册声明要放在application里面,这里通过标签来对活动进行注册的,这个注册环节是Android Studio自动帮我们完成的。

android:name指定具体注册哪一个活动,,.FirstActivity是com.example.activitytest.FirstActivity的缩写。

可是目前程序还是不能跑起来的,因为我们还没有指定主活动,也就是说程序运行起来时,不知道首先启动哪个活动。

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

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.ActivityTest"
        tools:targetApi="31">
        <activity
            android:name=".FirstActivity"
            android:exported="true" 
            android:label="This is my FirstActivity"
       >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
            
        </activity>
    </application>

</manifest>

在activity标签中添加这两行就行了

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>

启动看看效果

android:label显示在左上角,中间就是layout布局文件的效果

2.4、在活动中使用Toast

Toast是Android系统中提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,并且不占用任何屏幕空间。

![image-20230331222819489](/Users/whitebird/Library/Application Support/typora-user-images/image-20230331222819489.png)

package com.example.activitytest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class FirstActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button button1=(Button)findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(FirstActivity.this, "You Click The Button!", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

我们首先通过findViewById获得按钮的实例,findViewById返回值是一个View对象,需要我们向下转型为Button对象。在得到实例后,通过调用setOnClickListener为按钮注册一个监听器,点击按钮时会执行监听器中的onClick方法,因此Toast的弹出需要在onClick方法中编写。

2.5、在活动中使用Menu

由于手机的屏幕空间十分有限,因此要充分利用屏幕空间在手机界面设计中就显得非常重要了,可能有时候大量的菜单就占了屏幕1/3的空间,不过安卓给我们提供了一种方式,不仅可以让菜单都得到展示的同时,还不用占用任何屏幕空间。

首先在res目录下新建一个menu文件夹

![image-20230331230028251](/Users/whitebird/Library/Application Support/typora-user-images/image-20230331230028251.png)

接着在这个文件夹下新建一个名叫main的菜单文件

![image-20230331230111689](/Users/whitebird/Library/Application Support/typora-user-images/image-20230331230111689.png)

接下来我们需要在main.xml中添加以下代码

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
    android:id="@+id/add_item"
    android:title="Add"
    />
<item
    android:id="@+id/remove_item"
    android:title="Remove" />
</menu>

这里我们创建了两个菜单项,其中标签就是用来创建具体的某一个菜单项,然后通过android:id给这个菜单项指定一个唯一的标识符,通过android:title给这个菜单项指定一个名称。

接下来回到FirstActivity中来重写onCreateOptionsMenu()方法,快捷键Ctrl+O

@Override
   public boolean onCreateOptionsMenu(Menu menu) {
       getMenuInflater().inflate(R.menu.main,menu);
       return true;
   }

通过getMenuInflater方法能够得到MenuInflater对象,再调用它的inflate方法就可以给当前活动创建菜单了。

inflate有两个参数,第一个参数用于指定我们通过哪一个资源文件来创建菜单,这个传入R.menu.main,第二个参数用于指定我们的菜单项将添加到哪一个Menu对象中,这里直接用onCreateOptionsMenu传入的menu,然后返回true,表示允许创建的菜单显示出来,如果返回了false,创建的菜单将无法显示。

当然,仅仅让菜单显示出来肯定还不够,我们需要让菜单真正能用才行,因此还要再定义菜单响应事件,在FirstActivity中重写onOptionsItemSelected方法

package com.example.activitytest;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class FirstActivity extends AppCompatActivity {
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main,menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()){
            case R.id.add_item:
                Toast.makeText(this, "You click Add", Toast.LENGTH_SHORT).show();
                break;
            case R.id.remove_item:
                Toast.makeText(this, "You click Remove", Toast.LENGTH_SHORT).show();
                break;
            default:
        }
        return true;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button button1=findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(FirstActivity.this, "You Click The Button!", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

![A](/Users/whitebird/Library/Application Support/typora-user-images/image-20230331232530636.png)

点击测试

![image-20230331232551878](/Users/whitebird/Library/Application Support/typora-user-images/image-20230331232551878.png)

![image-20230331232605686](/Users/whitebird/Library/Application Support/typora-user-images/image-20230331232605686.png)

2.6、销毁一个活动

方法finish()可以实现用代码结束一个活动

3、使用Intent在活动之间穿梭

只有一个活动的应用太简单了,我们的追求应该更高点,不管我们创建多少个活动,做法都是一样的,但是有个问题就是,我们在启动器中点击应用图标只会进入到应用的主活动,怎么才能从主活动跳转到其他的活动呢?

3.1、使用显示Intent

首先按照之前的方法再创建一个activity,具体的参数配置看图,勾选上自动生成布局文件

![image-20230401000535865](/Users/whitebird/Library/Application Support/typora-user-images/image-20230401000535865.png)

安装之前的方法,我们添加一个button2

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SecondActivity">
    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button 2"
        />
</androidx.constraintlayout.widget.ConstraintLayout>

SecondActivity已经帮我们生成好了一部分代码,当然,要记得任何一个活动创建后都要在AndroidManifest.xml中注册,不过Android Studio已经帮我们自动完成了

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

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.ActivityTest"
        tools:targetApi="31">
        <activity
            android:name=".SecondActivity"
            android:exported="false" />
        <activity
            android:name=".FirstActivity"
            android:exported="true"
            android:label="This is my FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

由于SecondActivity不是主活动,所以不用 标签设置启动,现在我们只差一步了:如何启动第二个活动了,这里就需要Intent了。先看看Intent介绍:

Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于启动活动,启动服务以及发送广播等场景,现在我们的重点是活动。

Intent有多个构造函数的重载,其中有一个是Intent(Context packageContext,Class<?>cls)。这个函数接受两个参数,第一个参数是要求提供一个启动活动的上下文,第二个参数Class则是指定想要启动的目标活动,由于Intent只是指明当前组件想要执行的动作,属于意图,所以我们还得使用startActivity方法,这个方法是专门用于启动活动的,它的参数是intent,代码如下:

package com.example.activitytest;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class FirstActivity extends AppCompatActivity {
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main,menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()){
            case R.id.add_item:
                Toast.makeText(this, "You click Add", Toast.LENGTH_SHORT).show();
                break;
            case R.id.remove_item:
                Toast.makeText(this, "You click Remove", Toast.LENGTH_SHORT).show();
                break;
            default:
        }
        return true;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button button1=findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(FirstActivity.this, "You Click The Button!", Toast.LENGTH_SHORT).show();
                Intent intent =new Intent(FirstActivity.this,SecondActivity.class);
                startActivity(intent);
            }
        });
    }
}

当我们启动App后,点击Button1 会启动SecondActivity,当我们再按back键后,就会销毁SecondActivity,退回到FirstActivity。由于Intent的意图非常明显,所以我们称之为显式Intent。

3.2、使用隐式Intent

隐式Intent并不明确指出我们想要启动哪一个活动,而是指定了一系列更加抽象的action和category,然后由系统去分析这个Intent,并帮我们找出合适的活动去启动。

什么叫合适的活动?就是能够响应我们这个隐式Intent的活动,目前来说SecondActivity还响应不了什么,我们需要对其进行配置。

通过在标签下配置内容,可以指定当前的活动能够响应的action和category

<activity
    android:name=".SecondActivity"
    android:exported="false">
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

在这个标签中我们指明了当前的活动可以响应com.example.activitytest.ACTION_START这个action,而标签则包含了一些附加信息,更精确地指明了当前的活动能够响应的Intent中还可能带有的category。只有这两个标签的内容同时能够匹配上Intent中指定的action和category,这个活动才能响应该Intent。

@Override
 protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.first_layout);
     Button button1=findViewById(R.id.button1);
     button1.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
             Toast.makeText(FirstActivity.this, "You Click The Button!", Toast.LENGTH_SHORT).show();
             Intent intent =new Intent("com.example.activitytest.ACTION_START");
             startActivity(intent);

         }
     });
 }

我们使用了Intent的另外一个构造函数,直接传入了一个action的字符串,表明我们想要启动能够响应com.example.activitytest.ACTION_START的action所属的活动。这里我们都没有写category不是违背了前面所说的action和category要和Intent的一致么,这是因为android.intent.category.DEFAULT是一种默认的category,在调用startActivity这个方法时,会自动加入这个category到Intent中。然后重启应用,当我们点击Button 1之后就会跳到SecondActivity

每个Intent只能指定一个action,但是可以指定多个category,现在我们增加一个category。

public void onClick(View view) {
                Toast.makeText(FirstActivity.this, "You Click The Button!", Toast.LENGTH_SHORT).show();
                Intent intent =new Intent("com.example.activitytest.ACTION_START");
                intent.addCategory("com.example.activitytest.My_Category");
                startActivity(intent);
            }

通过调用addCategory方法指定了一个自定义的category:com.example.activitytest.My_Category,现在我们重新启动应用程序进行测试。你会发现当我们点击Button 1程序崩溃了,我们可以通过logcat查看错误日志。

No Activity found to handle Intent表示没有活动可以响应我们的Intent,这是因为我们在Intent中新加入了一个category,但是SecondActivity的中却没有声明可以响应这个category,我们尝试添加一个category的声明。

<activity
        android:name=".SecondActivity"
        android:exported="false">
        <intent-filter>
            <action android:name="com.example.activitytest.ACTION_START"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <category android:name="com.example.activitytest.My_Category"/>
        </intent-filter>
    </activity>

然后就正常了

3.3、更多的隐式Intent用法

使用隐式Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使Android多个应用程序之间的功能共享成为了可能,下面以从程序中展示一个网页为例:

protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.first_layout);
       Button button1=findViewById(R.id.button1);
       button1.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
               Toast.makeText(FirstActivity.this, "You Click The Button!", Toast.LENGTH_SHORT).show();
               Intent intent=new Intent(Intent.ACTION_VIEW);
               intent.setData(Uri.parse("http://www.baidu.com"));
               startActivity(intent);
           }
       });
   }

首先指定了intent的Action是Intent.ACTION_VIEW,这是一个安卓系统内置的动作,该Action表示该Intent将用于打开一个网页,然后通过Uri.parse方法,将一个网址字符串解析成一个Uri对象,再调用Intent的setData方法将Uri对象传递进去,setData用于指定当前Intent在操作的数据。当我们点击Button1后就跳到了浏览器,并成功打开了http://www.baidu.com。

我们可以在中再配置一个标签,用于更加精确地指定当前活动能够响应什么类型的数据。主要可以配置以下内容:

<data
    android:scheme="" 用于指定数据的协议部分,如http
    android:host=""   用于指定数据的主机名部分,如www.baidu.com
    android:port=""   用于指定数据的端口部分,一般紧跟在主机名之后
    android:path=""   用于指定主机名和端口之后的部分
    android:mimeType="" 用于指定可以处理的数据类型,允许使用通配符的方式
    />

只有标签中的指定的内容和Intent中携带的Data完全一致时,当前活动才会响应该Intent,不过一般中标签中都不会指定过多的内容,如上面浏览器实例中,其实只需要指定android:scheme=””为http,就可以响应所有的http协议的Intent了。

3.4、向下一个活动传递数据

我们利用Intent不仅可以启动一个活动,还可以在启动活动的时候传递数据。Intent中提供了putExtra方法,可以把我们想要传递的数据暂存在Intent中,启动了另外一个活动后,只需要把这些数据再从Intent中取出来就可以了。

首先在FirstActivity中传入数据,通过putExtra方法传入,第一个参数是键,第二个参数是值

protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.first_layout);
     Button button1=findViewById(R.id.button1);
     button1.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
             Toast.makeText(FirstActivity.this, "You Click The Button!", Toast.LENGTH_SHORT).show();
             String data="Hello SecondActivity";
             Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
             intent.putExtra("extra_data",data);
             startActivity(intent);
         }
     });
 }

在SecondActivity中接受数据,通过getIntent获得启动SecondActivity的Intent,然后调用getStringExtra方法获得传递的数据,如果传递的是整型数据,就用getIntExtra,如果传递的是布尔类型数据,则使用getBooleanExtra方法

package com.example.activitytest;

import androidx.appcompat.app.AppCompatActivity;

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

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_layout);
        Intent intent =getIntent();
        String data=intent.getStringExtra("extra_data");
        Log.d("SecondActivity",data);
    }
}

3.5、返回数据给上一个活动

我们可以传递数据给下一个活动,那么理论上也可以返回数据给上一个活动,但是有一个问题:我们的数据是通过Intent传递的,但是从现在的活动跳转到上一个活动,只要按Back键就行了,也就是不会用到Intent。不过Activity中有一个startActivityForResult方法也是用于启动活动的,但是这个方法期望在活动销毁时能够返回一个结果给上一个活动,这就是我们所需要的。

FirstActivity的关键代码

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.first_layout);
    Button button1=findViewById(R.id.button1);
    button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Toast.makeText(FirstActivity.this, "You Click The Button!", Toast.LENGTH_SHORT).show();
            Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
            startActivityForResult(intent,1);
        }
    });
}

通过startActivityForResult来启动SecondActivity,请求码只要是一个唯一值就行了,这里传入了1.

SecondActivity的关键代码

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_layout);
        Button button2=(Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent=new Intent();
                intent.putExtra("data_return","Hello FirstActivity");
                setResult(RESULT_OK,intent);
                finish();
            }
        });
    }
}

这里构造了一个Intent,但是它并不是用来启动Activity的,而是仅仅由于传递数据。通过putExtra放入数据,然后调用setResult,用于向上一个活动返回数据。它有两个参数,第一个参数用于向上一个活动返回处理结果,一般只使用RESULT_OK或RESULT_CANCELED,第二个参数是把带有数据的intent传递回去,最后调用finish销毁当前活动。

当我们调用startActivityForResult方法启动SecondActivity后,SecondActivity被销毁后会调用上一个活动的onActivityResult方法,因此我们需要重写这个方法来得到返回的数据。

onActivityResult有三个参数,第一个参数是requestCode,是我们在启动活动时传入的请求码,第二个参数是resultCode,是我们在返回数据时传入的处理结果,第三个参数是data,即携带着数据的intent。

protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
     super.onActivityResult(requestCode, resultCode, data);
     switch (requestCode) {
         case 1:
             if (resultCode == RESULT_OK) {
                 String retData = data.getStringExtra("data_return");
                 Log.d("data_return", retData);
             }
             break;
         default:
     }
 }

![image-20230403140006778](/Users/whitebird/Library/Application Support/typora-user-images/image-20230403140006778.png)

可能有人会问,如果用户不是点击Button2,而是直接通过back键返回怎么办,这个时候需要我们在SecondActivity中重写onBackPressed。

    public void onBackPressed() {

        Intent intent=new Intent();
        intent.putExtra("data_return","Hello FirstActivity2");
        setResult(RESULT_OK,intent);
        finish();
    }
}

4、活动的生命周期

Andriod中的活动是可以层叠的,我们每启动一个新的活动,就会覆盖在原活动上,然后点击back后就会销毁最上面的活动,下面的一个活动就会重新显示出来。

4.1、返回栈

Android使用任务(Task)来管理活动,一个任务就是一组存放在栈里的活动的集合,而这个栈也被称作返回栈。每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶。而当我们按下back键后,或者调用finish方法去销毁一个活动,处于栈顶的活动就会出栈,这时前一个入栈的活动就会重新处于栈顶的位置。系统总是显示处于栈顶的活动给用户。

4.2、活动状态

1、运行状态

当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验。

2、暂停状态

当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。你可能会觉得既然活动已经不在栈顶了,还怎么会可见呢?这是因为并不是每一个活动都会占满整个屏幕的,比如对话框形式的活动只会占用屏幕中间的部分区域,你很快就会在后面看到这种活动。处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只有在内存极低的情况下,系统才会去考虑回收这种活动。

3、停止状态

当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。

4、销毁状态

当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活动,从而保证手机的内存充足。

4.3、活动的生存期

Activity类中定义了7个回调方法,覆盖了活动生命周期的每一个环节。

1、onCreate

这个方法你已经看到过很多次了,每个活动中我们都重写了这个方法,它会在活动第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如说加载布局、绑定事件等。

2、onStart

这个方法在活动由不可见变为可见的时候调用。

3、onResume

这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。

4、onPause

这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。

5、onStop

这个方法在活动完全不可见的时候调用。它和onPause() 方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause() 方法会得到执行,而onStop() 方法并不会执行。

6、onDestory

这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。

7、onRestart

这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。

可以把活动分为3种生存期:

完整生存期:

活动在onCreate方法和onDestory方法之间所经历的,就是完整生存期。一般情况下活动会在onCreate方法中完成各种各样初始化操作,而在onDestory方法中完成释放内存的操作。

可见生存期

活动在onStart和onStop之间所经历的,就是可见生存期。在可见生存期内,活动对于用户来说总是可见的,即使有可能无法和用户进行交互。我们可以通过这两个方法合理地管理那些对用户可见的资源。比如onStart对资源进行加载,onStop时对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。

前台生存期

活动在onResume和onPause方法之间所经历的就是前台生存期。在前台生存期内,活动总是处于运行状态,此时的活动是可以和用户进行交互的,我们平时看到和接触最多的也就是这个状态下的活动。

4.4、活动被回收了怎么办

当一个活动进入停止状态,是有可能被系统回收的。如果A被回收了,这个时候并不会执行onRestart方法,而是会执行活动A的onCreate方法,A在这种情况下会被重新创建一次。看上去很正常,但是如果A中存在临时数据,那么重新创建之后数据就没了,所以我们必须想出一个解决办法:

Activity中提供了一个onSaveInstanceState回调方法,这个方法可以保证在活动被回收之前一定会被调用,因此我们可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。

onSaveInstanceState的参数是Bundle类型,它提供了一系列的方法用于保存数据,比如putString保存字符串,putInt保存整数数据。

public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    String data="Hello onSaveInstanceState";
    outState.putString("save_key",data);
}

这样数据就被保存下来了,我们应该在哪里恢复呢?其实我们用的onCreate里也有个bundle参数,不过这个参数一般都是null,如果在活动被系统回收之前通过onSaveInstanceState方法来保存数据的话,这个参数就会带有之前所保存的全部数据,我们只需要再通过相应的取值方法将数据取出就行了。

if (savedInstanceState!=null){
      String tempdata=savedInstanceState.getString("save_key");
      Log.d("save_data",tempdata);
  }

4.5、活动的启动模式

启动模式分为四种:standard、singleTop、singleTask和singleInstance

a、standard

standard是活动的默认启动模式,在不进行显式指定的情况下,所有活动都会自动使用这种启动模式。因此,到目前为止我们写过的所有活动都是使用的standard模式。在standard模式(即默认情况)下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。

startNormalActivity.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent=new Intent(MainActivity.this,MainActivity.class);
            startActivity(intent);
        }
    });

我们尝试在MainActivity的基础上再启动MainActivity,连续两次点击按钮

我们每点击一次按钮就会创建出一个新的MainActivity,此时的返回栈中有3个MainActivity的实例,也就意味着我们需要点击3次back才能退出程序。

原理示意图:

b、singleTop

使用singleTop模式。当活动的启动模式指定为singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。

c、singleTask

任务栈中不重复

当活动的启动模式指定为singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。

![image-20230405232045692](/Users/whitebird/Library/Application Support/typora-user-images/image-20230405232045692.png)

d、singleInstance

在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活
动,都共用的同一个返回栈

4.6、活动的最佳实践

1、了解当前界面对应活动

创建自己的Activity基类BaseActivity;并让所有activity继承该类

package com.example.activitylifecycletest;

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

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

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

2、随时随地退出程序

用一个专门的集合类对所有的活动进行管理

package com.example.activitylifecycletest;

import android.app.Activity;
import java.util.ArrayList;
import java.util.List;

public class ActivityCollector {
    public static List<Activity> activities=new ArrayList<>();
    public static void addActivity(Activity activity){
        activities.add(activity);
    }
    public static void RemoveActivity(Activity activity){
        activities.remove(activity);
    }
    public static void finishAll(){
        for (Activity activity:activities
             ) {
            if (!activity.isFinishing()){
                activity.finish();
            }
        }
        activities.clear();
    }
}

然后在BaseActivity中进行调用,在BaseActivity 的onCreate() 方法中调用了ActivityCollector 的addActivity() 方法,表明将当前正在创建的活动添加到活动管理器里。然后在BaseActivity 中重写onDestroy() 方法,并调用了ActivityCollector 的removeActivity()方法,表明将一个马上要销毁的活动从活动管理器里移除。

package com.example.activitylifecycletest;

import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity",getClass().getSimpleName());
        ActivityCollector.addActivity(this);
    }

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

3、启动活动的最佳写法

public static void actionStart(Context context, String data1, String data2) {
        Intent intent = new Intent(context, [current_activity.class]);
        intent.putExtra("param1", data1);
        intent.putExtra("param2", data2);
        context.startActivity(intent);
    }

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 767778848@qq.com