《第一行代码》 第二章:探究活动Activity

一,活动的基本用法

简单的理解,活动就是你能看到的页面,通常一个页面一个活动。

1,手动创建一个项目

新建一个新项目,选择add NoActivity。这时候生成的项目,java下的包内是没有活动的。于是需要新建活动。
右键com.example.activitytest包-New-Activity-Empty Activity,会弹出一个创建活动的对话框,我们将活动命名为 FirstActivity,并且不要勾选Generate Layout File和 Launcher Activity这两个选项。
勾选Generate Layout File表示会自动为 FirstActivity创建一个对应的布局文件,勾选 LauncherActivity 表示会自动将 FirstActivity 设置为当前项目的主活动,这里由于你是第一次手动创建活动,这些自动生成的东西暂时都不要勾选,下面我们将会一个个手动来完成。
在这里插入图片描述

public class FirstActivity extends AppCompatActivity {
    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
    }
}

项目中的任何活动,都应该重写Activity的onCreate()方法,而目前新建的这个他已经为我们默认写了。

2,创建和加载布局

前面我们说过,Android 程序的设计讲究逻辑和视图分离,最好每一个活动都能对应一个布局,布局就是用来显示界面内容的,因此我们现在就来手动创建一个布局文件。
右击app/src/main/res目录-New一Directory,会弹出一个新建目录的窗口,这里先创建一个名为layout的目录。然后对着 layout 目录右键一Layoutresource file,又会弹出一个新建布局资源文件的窗口,我们将这个布局文件命名为 first laout,根元素就默认。
然后书写button代码:

<Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="测试按钮"/>
android:id 是给当前的元素定义一个唯一标识符,之后可以在代码中对这个元素进行操作。你可能会对@+id/button 1这种语法感到陌生,但如果把加号去掉,变成@id/button 1,这样你就会觉得有些熟悉了吧,这不就是在 XML中引用资源的语法吗?只不过是把 string 替换成了id。是的,如果你需要在XML中引用一个id,就使用@id/id name 这种语法,而如果你需要在XML中定义一个id,则要使用@+id/id name 这种语法。

随后android:layout width 指定了当前元素的宽度,这里使用match parent 表示让当前元素和父元素一样宽。android:layout height 指定了当前元素的高度,这里使用 wrap content 表示当前元素的高度只要能刚好包含里面的内容就行。android:text 指定了元素中显示的文字内容

这样布局文件就书写好了,接下来需要在活动中加载布局

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

然后再在AndroidManifest文件中注册这个活动:

    <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.MyApplicationTest">
        <activity
            android:name=".FirstActivity"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

可以看到activity中已经注册。但是缺少intent-filter标签内的两行代码。之前说过,这两行代码表示FirstActivity是这个项目的主活动,在手机上点击应用图标,首先启动的就是这个活动。
这样写完之后运行项目,就可以看到效果了:
在这里插入图片描述
于是流程如下:

1,创建新的活动
2,创建新的layout布局
3,在活动中引入创建的layout
4,在manifext文件中注册活动。

3,在活动中使用toast

上文已经写了一个按钮,这时候,就可以获取到这按钮,然后给它绑定事件。

setContentView(R.layout.first_layout);
Button button1=(Button) findViewById(R.id.button);
button1.setOnClickListener(new View.OnClickListener() {
    
    
    @Override
    public void onClick(View view) {
    
    
        Toast.makeText(FirstActivity.this, "测试toast", Toast.LENGTH_SHORT).show();
    }
});

可以看到这里findViewById返回的是view对象,向下转型成button对象,然后调用setOnClickListener方法为按钮注册一个监听器。点击按钮时就会执行监听器中的onClick方法。
而这里就需要我们自定义onClick方法的内容。
Toast的用法非常简单通过静态方法 makeText()创建出一个Toat 对象,然后调用 show()将 Toast显示出来就可以了。这里需要注意的是,makeText()方法需要传入3 个参数。

第一个参数是 Context,也就是 Toast要求的上下文,由于活动本身就是一个 Context 对象,因此这里直接传人 FirstActivity.this 即可。
第二个参数是 Toast 显示的文本内容。
第三个参数是 Toast显示的时长,有两个内置常量可以选择 Toast.LENGTH SHORT和 Tast.LENGTH LONG

4,在活动中使用menu

首先在 res 目录下新建一个 menu 文件夹,击 res 目录一New一Directory,输人文件夹名menu,点击 OK。接着在这个文件夹下再新建一个名叫 main 的菜单文件,右击 menu 文件夹一New一Menu resource file,新增如下代码:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
    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>

也就是说res里面全是资源文件,这里写好了菜单的布局内容,还需要挂载在对应的活动中才能显示出来,接着重新回到 FirstActivity 中来重写onCreateOptionsMenu()方法,重写方法可以使用Ctrl+O快捷键(Mac 系统是 control + 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 中重写on0ptionsItemSelected()方法。

@Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
    
    
        switch (item.getItemId()){
    
    
            case R.id.add_item:
                Toast.makeText(this, "增加按钮被点击", Toast.LENGTH_SHORT).show();
                break;
            case R.id.remove_item:
                Toast.makeText(this, "移除按钮被点击", Toast.LENGTH_SHORT).show();
                break;
            default:
        }
        return true;
    }

在on0ptionsItemSelected()方法中,通过调用 item.getItemId()来判断我们点击的是哪一个菜单项,然后给每个菜单项加人自己的逻辑处理。因为上文说过, android:id="@+id/add_item"会增加一个id为add_item,所以这里R.id.add_item就能取到它的id。于是menu就创建完毕。

5,销毁一个活动

在界面上,我们按一下back键就可以销毁当前的活动。不过如果想代码实现,则可以使用Activity类提供的一个finish()方法,我们在活动中调用一下这个方法就可以销毁当前活动了。

button1.setOnClickListener(new View.OnClickListener() {
    
    
      @Override
      public void onClick(View view) {
    
    
          finish();
      }
  });

这样点击按钮就销毁当前的活动了。

二,使用Intent在活动之间穿梭

上文创建了一个活动,但是一个项目不可能只有一个活动,那多个活动之间如何跳转呢?这就需要使用Intent来完成。

1,构建第二个活动

右键活动,新建enpty activity,并且勾选Generate Layout File,给布局文件起名为second_layout,但不要勾选Launcher Activity选项。
点击Finish完成创建Android Studio 会为我们自动生成 SecondActivity,java和 second layoutxml 这两个文件。不过自动生成的布局代码目前对你来说可能有些复杂,这里我们仍然还是使用最熟悉的 LinearLayout,编辑second layout.xml:

<?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="第二个按钮" />
</androidx.constraintlayout.widget.ConstraintLayout>

然后看secondActivity中,编辑器中已经为我们写好了引用这个layout:

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

接下来在manifext文件中注册这个活动,可以看到已经自动注册好了,值得注意的是,因为它不是主活动,所以不需要intent-filter标签。

<activity
    android:name=".FirstActivity"
    android:exported="true" >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity
    android:name=".SecondActivity"
    android:exported="true" />

第二个活动到这里已经创建完成,接下来的问题就是怎么启动这个活动。需要使用Intent。

2,使用显式Intent完成活动的切换

Intent是 Android 程序中各组件之间进行交的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent 一般可被用于启动活动启动服务以及发送广播等场景。这里就只先讲启动活动。
Intent 大致可以分为两种:显式 intent 和隐式intent,我们先来看一下显式Intent 如何使用。

Intent有多个构造函数的重载,其中一个是Intent(Context packageContext,Class<?>cls)。这个构造函数接收两个参数。
第一个参数 Context 要求提供一个启动活动的上下文。
第二个参数 class 则是指定想要启动的目标活动,通过这个构造函数就可以构建出 Intent 的“意图”。
 Activity类中提供了一个startActivity()方法,这个方法是专门用于启动活动的,它接收一个 Intent 参数,这里我们将构建好的 Intent 传入startActivity()方法就可以启动目标活动了。

具体代码如下:

 Button button1=(Button) findViewById(R.id.button);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View view) {
    
    
                Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
                //这里这两个参数,指定从当前活动跳转目标活动。
                startActivity(intent);
            }
        });

这样点击按钮后,就能切换到第二个页面。值得注意的是,第一个页面活动并没有被销毁掉,可以理解为新的活动盖在旧的活动上层了。因为我在第二个页面销毁当前活动,就会到第一个活动了。

3,使用隐式Intent的用法

相比于显式 Intent,隐式 Intent 则含蓄了许多它不明确指出我们想要启动哪一个活动而是指定了一系列更为抽象的 action 和 category 等信息,然后交由系统去分析这个 Intent.并帮我们找出合适的活动去启动。
通过在标签下配置<intent-filter的内容,可以指定当前活动能够响应的action 和 category,打开AndroidManifest.xml,添加如下代码:

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

在标签中我们指明了当前活动可以响应 com.example.activitytest.ACTIONSTART 这个action,而标签则包含了一些附加信息,更精确地指明了当前的活动能够响应的Intent中还可能带有的 category。只有和中的内容同时能够匹配上Intent中指定的action 和 category 时,这个活动才能响应该Intent。
修改FirstActivity 中按钮的点击事件,代码如下所示:

        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View view) {
    
    
                Intent intent=new Intent("com.example.myapplicationtest.ACTION_START");
                startActivity(intent);
            }
        });

可以看到,我们使用了 Intent的另一个构造函数,直接将 action 的字符串传了进去,表明我们想要启动能够响应com.example.activitytest.ACTION START 这个action 的活动。那前面不是说要和同时匹配上才能响应的吗?怎么没看到哪里有指定category 呢?这是因为android.intent,category.DEFAULT 是一种默认的category,在调用startActivity()方法的时候会自动将这个 category 添加到Intent中。于是就能找到对应的活动,从而启动。
每个Intent中只能指定一个action,但却能指定多个 category。目前我们的Intent中只有一个默认的 category,那么现在再来增加一个吧。
修改FirstActivity 中按钮的点击事件,代码如下所示:

        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View view) {
    
    
                Intent intent=new Intent("com.example.myapplicationtest.ACTION_START");
                intent.addCategory("com.example.myapplicationtest.MY_CATEGORY");
                startActivity(intent);
            }
        });

这里新增后,点击按钮了还不能找到对应的category,为了让有对应的活动让它响应,还需要在menifext中新增category声明:

            <intent-filter>
                <action android:name="com.example.myapplicationtest.ACTION_START" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="com.example.myapplicationtest.MY_CATEGORY"/>
            </intent-filter>

4,使用隐式Intent启动其他程序的活动

使用隐式 Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得Android多个应用程序之间的功能共享成为了可能。比如说你的应用程序中需要展示一个网页这时你没有必要自己去实现一个浏览器(事实上也不太可能 ,而是只需要调用系统的浏览器来打开这个网页就行了。
修改FirstActivity中按钮点击事件的代码:

        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View view) {
    
    
                Intent intent=new Intent(Intent.ACTION_VIEW);
                intent.setData(Uri.parse("http://www.baidu.com"));
                startActivity(intent);
            }
        });

然后修改menifest中的这行代码,加个ignore(不改的话会报错):

            <intent-filter  tools:ignore = "AppLinkUrlError">
                <action  android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="http"/>
            </intent-filter>

这样点击按钮会出现弹窗,让你选择一个活动,是打开活动3还是打开默认浏览器。
这里就是因为按钮的事件,匹配上了多个活动。所以需要弹窗让用户选择。
实现的效果:
在这里插入图片描述
接下来,再来看看打开系统的电话本:

  button1.setOnClickListener(new View.OnClickListener() {
    
    
      @Override
      public void onClick(View view) {
    
    
          Intent intent=new Intent(Intent.ACTION_DIAL);
          intent.setData(Uri.parse("100861"));
          startActivity(intent);
      }
  });

5,向下一个活动传递数据

用前端的话说,就是路由跳转时如何传递参数。

传递参数:intent.putExtra("extra_data",data);
接收参数: 	Intentintent=getIntent();
			String data=intent.getStringExtra("extra_data");
 			Log.d("EecondActivity",data);

在第一个活动中传递数据:

        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View view) {
    
    
                String data="这是要传递的数据";
                Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
                intent.putExtra("extra_data",data);
                startActivity(intent);
            }
        });

在第二个活动中接收参数:

 Intentintent=getIntent();
 String data=intent.getStringExtra("extra_data");
 Log.d("EecondActivity",data);

6,返回数据给上一个活动

无论是back键还是代码finish()销毁当前活动,我们都能够在销毁活动时给上一个活动传递数据。这就需要使用到,Activity中startActivityForResult()方法,它也是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动。

startActivityForResult()方法接收两个参数,
第一个参数还是 Intent,
第二个参数是请求码,用于在之后的回调中判断数据的来源。

在第一个活动的点击事件中写 :

       button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View view) {
    
    
                String data="这是要传递的数据";
                Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
                startActivityForResult(intent,1);//请求码传入唯一值用来明确是它启动的活动即可,这里用1
            }
        });

然后再在第二个活动中给按钮注册点击事件,并在点击事件中添加返回数据的逻辑:

 public void onClick(View view) {
    
    
    Intent intent=getIntent();
    intent.putExtra("data_return","返回给上一个活动的信息");
    setResult(RESULT_OK,intent);//这个方法专门用于向上一个活动返回数据
    //第一个参数:用于返回处理结果,RESULT_OK是内置常量-1
    //第二个参数:带有参数的Intent
    finish();
}

由于我们是使用startActivityForResult()方法来启动 SecondActivity的,在SecondActivit被销毁之后会回调上一个活动的onActivityResult()方法,因此我们需要在 FirstActivity中重写这个方法来得到返回的数据,如下所示:

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    
    
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode){
    
    
            //。由于在一个活动中有可能调用startActivityForResult()方法去启动很多不同的活动
            // 每一个活动返回的数据都会回调到onActivityResult()这个方法中,
            // 因此我们首先要做的就是通过检查 requestCode 的值来判断数据来源。
            // 确定数据是从SecondActivity返回的之后,我们再通过 resultCode 的值来判断处理结果是否成功。
            case 1:
                if(resultCode==RESULT_OK){
    
    
                    String returnedData=data.getStringExtra("data_return");
                    Log.d("FirstActivity",returnedData);
                }
                break;
            default:
        }
    }

这样之后,因为在活动1中打开活动2是用的startActivityForResult,进入活动2,点击按钮销毁活动,就能被活动1的onActivityResult方法监听到,执行内部的代码,根据requestCode识别是哪个活动返回触发的,然后执行对应的代码。
但是如果是Back销毁第二个活动,明显不会执行回调,那又要如何解决呢?
我们可以在第二个活动中重写onBackPressed()方法来解决这个问题。

    @Override
    public void onBackPressed() {
    
    
        Intent intent=getIntent();
        intent.putExtra("data_return","返回给上一个活动的信息");
        setResult(RESULT_OK,intent);//这个方法专门用于向上一个活动返回数据
        //第一个参数:用于返回处理结果,RESULT_OK是内置常量-1
        //第二个参数:带有参数的Intent
        finish();
    }

三,活动的生命周期

1,返回栈的概念

在这里插入图片描述
也就是每个活动就是栈中的一层,用户从上往下看,就和ps的图层概念一样。

2,活动状态

每个活动的生命周期会有4种状态。
运行状态:

就是处在栈顶的活动

暂停状态

不处于栈顶,但是用户仍然可见的活动(顶层活动不覆盖全页,就会让底下的人活动可见了)

停止状态

当一个活动不处于顶层,并且不可见的时候。这时候系统仍然保留相应的状态和成员变量,但可能被回收。

销毁状态

当活动从栈中移除的时候就变成了销毁状态。

3,活动的生命周期

onCreate()。它会在活动第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如说加载布局、绑定事件等。【完整】
口 onstart()。这个方法在活动由不可见变为可见的时候调用。【可见】
口 onResume()。这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。【前台】
口 onPause()。这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗 CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。【前台】
口onstop()。这个方法在活动完全不可见的时候调用。它和 onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么 onPause()方法会得到执行,而onstop()方法并不会执行。【可见】
口 onDestroy()。这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。【完整】
口 onRestart()。这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。

4,体验活动的生命周期

在这里插入图片描述
新建几个活动,主活动的layout:

<?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:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="启动活动页1"/>

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="启动弹窗页2"/>

</LinearLayout>

主活动:

package com.example.activitylifecycletest;

import androidx.annotation.LongDef;
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;

public class MainActivity extends AppCompatActivity {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("TAG", "onCreate: ");
        Button startNormalActivity=(Button) findViewById(R.id.button);
        Button startDialogActivity=(Button) findViewById(R.id.button2);
        startNormalActivity.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View view) {
    
    
                Intent intent=new Intent(MainActivity.this,NormalActivity.class);
                startActivity(intent);
            }
        });
        startDialogActivity.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View view) {
    
    
                Intent intent=new Intent(MainActivity.this,DialogActivity.class);
                startActivity(intent);
            }
        });
    }

    @Override
    protected void onStart() {
    
    
        super.onStart();
        Log.d("TAG", "onStart: ");
    }

    @Override
    protected void onResume() {
    
    
        super.onResume();
        Log.d("TAG", "onResume: ");
    }

    @Override
    protected void onPause() {
    
    
        super.onPause();
        Log.d("TAG", "onPause: ");
    }

    @Override
    protected void onStop() {
    
    
        super.onStop();
        Log.d("TAG", "onStop: ");
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        Log.d("TAG", "onDestroy: ");
    }

    @Override
    protected void onRestart() {
    
    
        super.onRestart();
        Log.d("TAG", "onRestart: ");
    }
}

5,活动回收前暂存活动数据

上文说过,活动不可见就进入停止状态,而这时候活动是可能被回收的。如果是表单等的页面,用户点击返回,就会发生刚刚输入的信息丢失的情况,为了避免这种情况。
我们想要能够监听该活动被回收。一旦该活动被回收,则触发我们的函数。把数据暂存起来。等返回这个活动时,再重新赋值。
Activity对象提供了这个api用来存数:

    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
    
    
        //当前活动被销毁前会执行这里的代码
        super.onSaveInstanceState(outState);
        String testData="这是存储的一些信息";
        //存储对应数据类型的数据
        outState.putString("data_key",testData);
    }

那如何取数呢?注意到活动的onCreate()方法会传入一个Bundle作为参数,也就是说,通过它取数:

 if(savedInstanceState!=null){
    
    
     String testData=savedInstanceState.getString("data_key");
     Log.d("取出来的数的值", testData);
 }

6,活动的启动模式

启动活动的方式有四种:standard、singleTop、singleTask、singleInstance。可以在AndroidManifest.xml中通过给标签指定android:launchMode属性来选择启动模式。
【standard】
standard是默认的活动启动方式,当启动一个活动的时候,是向任务栈顶压入一个活动,我们上文中创建的活动都是这种启动方式。
新开一个项目,写一个按钮,在活动中写如下代码:

    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d("新创建", "onCreate: ");
        setContentView(R.layout.first_layout);
        Button button1=(Button) findViewById(R.id.button);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View view) {
    
    
                Intent intent=new Intent(FirstActivity.this,FirstActivity.class);
                startActivity(intent);
            }
        });
    }

这时候,点击2次按钮,就会再创建出2个当前活动FirstActivity,需要按3次返回才能退出程序。
【singleTop】
当使用singleTop模式的时候,当启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,而不会再创建新的活动实例。
修改menifext文件:

 <activity
     android:name=".FirstActivity"
     android:launchMode="singleTop"
     android:exported="true">
     <intent-filter>
         <action android:name="android.intent.action.MAIN" />
         <category android:name="android.intent.category.LAUNCHER" />
     </intent-filter>
 </activity>

这时候再点击按钮,就不会创建新的活动。但是如果创建的是非栈顶活动,则还是会照常创建。
【singleTask】
singleTop解决的只是同栈顶活动不再重复创建,但是无法解决ABABACA这样重复创建A活动的问题。于是又有singleTask。它保证活动是唯一的,已经存在则不再创建,而是直接复用。把它从栈中直接移动到栈顶。
【singleInstance】
上文的三个,已经做到了每一个活动栈中一个活动只存在一个实例。为啥还有singleInstance呢?
这是因为可能有多个app,假设是abc那样的话,就会存在多个调用栈ABC。
现在有一个活动H,它会被abc三个程序都调用。那不是会进入三个活动栈嘛,也即是创建了三个实例。
解决这个问题,就可以用singleInstance,它的原理是新建一个活动栈。把当前活动放到这个新的活动栈中,让其他程序共享这个活动栈。
在这里插入图片描述
这时候我们可以新建三个活动,ABC,将AC设置成android:launchMode=“singleTop”,而B设置成singleInstance,然后A跳B再跳C,如下:

    <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.MyApplicationTest">
        <activity
            android:name=".ThirdActivity"
            android:launchMode="singleTop"
            android:exported="true" >
        </activity>
        <activity
            android:name=".FirstActivity"
            android:launchMode="singleTop"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".SecondActivity"
            android:launchMode="singleInstance"
            android:exported="true">
        </activity>
    </application>

然后在页面中写第一个活动,跳转第二个活动,再跳第三个活动。得到得活动栈将是两个,如下所示:
在这里插入图片描述
也就是:
activity中设置singleInstance的,该活动被创建之后是单独放置在共享活动栈中的。其他设置了singleTop的则是正常进入自己的活动栈。
实际使用时,有多个程序,则有多个活动栈,然后共用一个共享栈。
活动栈的返回是该栈返回空之后,才会到下一个活动栈。
即:如果这时候按回车,则是先从thirdActivity到firstActivity,然后才到共享栈中的SecondActivity。

7,活动的最佳实践

【知晓当前在哪个活动】
新建一个普通的Java类,右击com.example.myApplicationTest-new-Java class,然后新写一个类,让它继承AppCompatActivity,重写Oncreate,打印当前活动名:

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

然后让活动123都继承这个类。于是实现的效果就是,每个活动在刚打开的时候,都会打印出当前活动的名字。这里主要是super.onCreate(savedInstanceState);会执行父类的onCreate,所以才能打印出来。
【随时随地退出程序】
如上文的singleInstance所示,当我们在thirdActivity的时候,需要按三次返回键才退出程序,现在我们想直接关闭程序呢?
我们只要新建一个数组来管理所有的活动即可,等我们要直接退出程序时,遍历这个数组,把所有的活动以finish()的形式销毁掉。然后再杀掉当前进程即可。
新建ActivityCollector类:

public class ActivityCollector {
    
    
    //新建一个类型为List,内容为Activity类型的数组activities
    public static List<Activity> activities =new ArrayList<Activity>();
    //定义方法,往activities数组中添加值
    public static void addActivity(Activity activity){
    
    
        activities.add(activity);
    }
    //定义方法,从activities中移除值
    public static  void removeActivity(Activity activity){
    
    
        activities.remove(activity);
    }
    //移除所有活动
    public static void finishAll(){
    
    
        for (Activity activity : activities) {
    
    
            if(!activity.isFinishing()){
    
    
                activity.finish();
            }
        }
    }
}

然后在上一点中的BaseActivity修改代码,在每个活动开始时,往数组里添加这个活动。在每个活动销毁时,往数组中移除该类:

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

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

这样一来,当我们在哪里要直接关闭程序时,便可以直接调用ActivityCollector.finishAll(),销毁所有活动:

public class ThirdActivity extends BaseActivity {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.third_layout);
        Button button3=(Button) findViewById(R.id.button3);
        button3.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View view) {
    
    
                ActivityCollector.finishAll();
            }
        });
    }
}

【启动活动的最佳写法】
在上文说过,活动间可以传递数据,如果要传递多个参数,按照之前的,写出来是这样:

 public void onClick(View view) {
    
    
     Intent intent=new Intent(SecondActivity.this,ThirdActivity.class);
     intent.putExtra("param1","data1");
     intent.putExtra("param2","data2");
     startActivity(intent);
 }

但是这样写会更合理一些,更容易维护:

    //在SecondActivity中定义一个方法,启动活动传参专用
    public static void actionStart(Context context, String data1, String data2){
    
    
        Intent intent=new Intent(context,ThirdActivity.class);
        intent.putExtra("param1","data1");
        intent.putExtra("param2","data2");
        context.startActivity(intent);
    }

然后onCreate中直接一行搞定:

 public void onClick(View view) {
    
    
       //直接调用这个方法
       SecondActivity.actionStart(SecondActivity.this,"data1","data2");
   }

猜你喜欢

转载自blog.csdn.net/weixin_42349568/article/details/128806721
今日推荐