Android正向开发(一) Activity

半原创声明

  • Android 正向开发 系列博文,主体内容摘自 《第一行代码》第二版,保留核心内容的基础进行了条理化,并在此基础上加入自己理解。
  • 主要目的是自己记录学习过程,并非系列教程。如果你想系统学习,建议移步其他平台。
  • 若需转载,请注明出处,并注明原作者。

活动(Activity)是什么

简单说:可以包含用户界面的组件,主要用于和用户交互。

再简单一点:你看到的界面都是活动~~(不过分吧……)

Activity的基本用法

创建活动

右击项目根目录,如 com.example.activitytest

New → \rightarrow Activity

创建活动

手动创建 Activity

可以了解诸多细节

在上图中选择 Empty Activity,在对话框填入<Activity名称> FirstActivity

注意:

  • 不要勾选 Generate Layout File
    • 该选项会自动创建FirstActivity 对应的布局文件
  • 不要勾选 Launcher Activity
    • 该选项会自动将 创建的第一个活动 FirstActivity设置为 当前项目主活动

点击 Finish 后完成创建

效果:AS自动生成如下代码

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);
    }
}

界面相关

创建布局,添加按钮

  • 布局文件目录:app/src/main/res/layout,如果没有 layout 需要新建
  • 布局文件创建:创建布局文件
    默认显示生成如下布局文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">

</androidx.constraintlayout.widget.ConstraintLayout>

tips: 可以调节 text模式和 design模式 以及 split

添加一个按钮,最终 xml 文件如下:

<?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"
    android:orientation="vertical">

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

</LinearLayout>

说明(Layout 章节会详解):

  • android:id : 给当前的元素定义一个 唯一标识符,可以在代码中对这个元素进行操作。其中 @+id/button_1 语法说明如下
    • + 表示定义,否则表示引用该 id
  • android:layout_widthandroid:layout_height: 指定当前元素的宽度和高度
    • "match_parent" 与父元素一样宽
    • "wrap_content" 当前元素高度刚好包含里边的内容
  • android:text: 元素中 显示的文字内容

加载布局

要在哪个 Activity加载,就在 Activity 的 onCreate() 方法中 加入:

// 为当前活动加载布局
setContentView(R.layout.first_layout);

传入布局文件的 id,这里体现为 xml 文件名

项目中添加的任何都会在 R 文件中生成一个 相应的资源 id

  • 刚刚创建的 xxx.xml 文件AS已经帮我们添加到 R 文件
  • 在代码中引用布局

补:定义&引用资源的方式

以字符串为例:

/res/values/string.xml

<resources>
    <string name="app_name">ActivityTest</string>
</resources>
  • 在代码中通过 R.string.<name>
  • xml 中 通过 @string/<name>

string 部分可以替换为:

  • 图片资源: drawable
  • App图标: mipmap
  • 布局文件:layout
  • 控件:id,代码效果:R.id.button_1

添加按钮响应事件

// 获取到布局文件中定义的元素,得到按钮的 instance -> View对象,向下转型为 Button
Button btn1 = (Button)findViewById(R.id.button_1);
btn1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(
                FirstActivity.this, // Activity 本身就是 Content
                "You clicked Button 1", // 显示的文本内容
                Toast.LENGTH_SHORT // 显示的时长
        ).show();
        // 构造 Intent
        Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
        // 执行 intent:打开SecondActivity活动
        startActivity(intent);
        //finish();
    }
});

逆向代码研究

此处的 R.layout.first_layout 会被替换为 一个整数,为 R文件中为 xml 分配的 id,其他的后续研究。

使用Menu

创建资源文件
res 目录下新建一个 menu 目录,新建 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>

这里创建了两个菜单项,<item> 用来创建具体的某一个菜单项。

  • android:id 给这个菜单项指定唯一的标识符
  • android:title 给这个菜单指定名称

Activity中使用Menu

若想使用这个 Menu,可以在 Activity 对应的 类中,复写 onCreateOptionsMenu

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

tips: 可以使用 Ctrl + O 辅助重写(override)

程序说明:

  • getMenuInflater() 得到 MenuInflater 对象,调用它的 inflate() 方法可以给当前 Activity 创菜单
  • R.menu.main 为菜单资源的引用方式
  • 返回 true 表示允许创建的菜单显示出来,否则不予显示

为菜单添加添加响应事件
在Activity中 重写 onOptionsItemSelected

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

引用菜单 item 的方式 R.id.<xxx>

上述代码易懂,不解释了

AndroidManifest 中 Activity配置

注册活动

所有的活动都要在 AndroidManifest.xml 中进行注册才能有效,实际上Android Studio 已经自动完成 FirstActivity 了的注册:

<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/AppTheme">
		<activity
		    android:name=".FirstActivity">
		</activity>
</application>

Activity 注册的声明 要放在 <application> 内,通过 <activity> 进行注册。

  • android:name 具体注册哪一个活动
    • 这是由于 最外层的 <manifest> 中已经通过 package 属性指定了 程序的包名,注册活动时这一部分可以忽略
    • . 包含了 com.example.activitytest.

Eclipse 创建活动 或 其他系统组件 时,很多人忘记要去注册一下,从而导致程序崩溃,Android Studio 这方面做得很人性化。

配置主活动

注册了活动,程序仍然不能运行,还需要为程序 配置 主活动(程序运行起来首先启动的活动),方法如下:

<activity> 内部加入 <intent-filter>

<activity
    android:name=".FirstActivity"
    android:label="This is FirstActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

两句声明是固定写法,留个坑

android:label 说明:

  • 指定了活动标题栏(显示在活动最顶部)的内容
  • 如果是主活动,还会成为 启动器(Launcher)中 应用程序显示的名称

注:App可以没有主活动,这样App也可以安装,只是无法在启动器中看到或者打开这个程序。这种程序一般都是作为第三方服务供其他应用在内部进行调用的,如:支付宝快捷支付服务

补充:查看App所有活动

通过 QuickShortcutMaker 可以显示App内所有活动,并直接启动活动,如声明:

<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
<activity
    android:name=".FirstActivity"
    android:label="This is FirstActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

显示的活动如下:

Activity label

  • 加粗的是App名称
  • 下边一行是 前边提到的 android:label 指定的名称,如果没有则显示 App名称
  • 其他的部分是 Activity所在的 包名

销毁一个活动

方式一:按一下 Back 键就可以销毁

方式二:在需要的位置,调用 finish(),默认的 this 为 当前 Activity 所在的类

Activity 之间穿梭 (Intent)

App启动时只会进入到 该应用的 主活动,本节探讨如何在 不同 Activity 之间穿梭自如。

准备工作

首先再创建一个活动,命名为 SecondActivity,并勾选 Generate Layout File,布局文件起名为 second_layout,但不要勾选 Launcher Activity 选项,点击 Finish 完成创建,Android Studio会自动生成 SecondActivity.javasecond_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">

</androidx.constraintlayout.widget.ConstraintLayout>

对初学者来说有些复杂,替换成如下的内容:

<?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"
    android:orientation="vertical">

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

</LinearLayout>

自动生成的 SecondActivity 中的代码保持默认,另外注册的任务AS已经自动完成。

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

由于SecondActivity 不是主活动,不需要配置 <intent-filter>中的内容

初步认识Intent

  • Intent 是 Android 程序中各组件之间进行交互的一种重要方式,不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。
  • Intent 一般可被用于启动活动、启动服务以及发送广播等场景。这里先讨论启动活动。
  • Intent 大致可以分为两种:显式 Intent 和 隐式 Intent

显式 Intent

Intent 有多个构造函数的重载,其中一个是Intent(Context packageContext, Class<?> cls)

  • Context 要求提供一个启动活动的上下文
  • Class 指定想要启动的目标活动

通过这个构造函数,可以构建出 Intent 的 意图

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);

可用 Activity 类中提供的startActivity()方法启动目标活动

startActivity(intent);

返回到上一个活动:按下 Back 键会 销毁当前 Activity,回到上一个 Activity

使用隐式 Intent

隐式 Intent 含蓄了许多。并不明确指出要启动哪一个活动,而是指定一系列更为抽象的 actioncategory 等信息,然后交由系统去分析这个 Intent,并帮我们找出合适的活动去启动。

合适的活动:可以响应 隐式 Intent 的活动

<activity>中修改如下:

<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />  <!--这条不能省略-->
        <category android:name="android.intent.category.MY_CATEGORY" />
    </intent-filter>
</activity>
  • <action> 指明当前活动可以响应 com.example.activitytest.ACTION_START 这个 action
  • <category>包含了一些附加信息,更精确地指明了当前的活动能响应的 Intent 中还可能带有 category

只有 两个标签 中的内容同时能够匹配 Intent 中指定的 actioncategory 时,这个活动才能响应该 Intent

// 隐式 Intent, 传入 action 字符串,只能接收一个 action
Intent intent_1 = new Intent("com.example.activitytest.ACTION_START");
// 可以指定多个 category
intent_1.addCategory("android.intent.category.MY_CATEGORY");
startActivity(intent_1);

如果找不到合适的 活动 响应,程序会直接崩溃,请保证 两个 参数的正确性。

更多隐式 Intent 的用法

调用系统浏览器
还可以用来 启动其他程序的活动,使得 Android 多个 App 之间的功能共享成为了可能。e.g. 需要展示网页不需要自己实现,直接调用其他的浏览器即可。

// 隐式 Intent,启动其他App
Intent intent_2 = new Intent(
	Intent.ACTION_VIEW  // Android 内置操作
);
// 传递数据:Uri对象
intent_2.setData(Uri.parse("https://httpbin.org/get"));
startActivity(intent_2);

自定义Activity如何被隐式调用
再新建一个 Activity,注册如下:

<activity android:name=".ThirdActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="https"/>
    </intent-filter>
</activity>
  • actionIntent.ACTION_VIEW 表示当前可以响应的常量值
  • data 中 通过 android:scheme 指定了数据的协议必须是 https 协议
    • 还可以指定其他协议类型,如 tel, geo

效果如下:

在这里插入图片描述
如果选择 ActivityTest 会进入 刚刚创建的 Activity3,但并没有加载并显示网页的功能,会误导用户。

数据传递(Intent)

向下一个活动传递数据

启动活动时,设置传递的数据:

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
// 数据传递
String data = "Hello SecondActivity! from FirstActivity";
intent.putExtra(
     "extra_data", // key:用于取值
     data // value: 真正要传递的数据
);
// 执行 intent:打开SecondActivity活动
startActivity(intent);

在目标活动,取出数据,并打印

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.second_layout);
    // 接收 Intent 传递的数据
    Intent intent = getIntent();
    String data = intent.getStringExtra("extra_data");
    Log.d("SecondActivity", data);
}

获取数据相关的 API:

  • getStringExtra()
  • getIntExtra()
  • getBooleanExtra()

返回数据给上一个活动

启动方式

需要借助 startActivityForResult() 方法启动,这个方法期望 在启动的活动销毁的时候能够返回一个结果给上一个活动。

Intent intent_3 = new Intent(FirstActivity.this, SecondActivity.class);
startActivityForResult(intent_3, 1);
  • 请求码只要是一个唯一值就可以,用于区分启动的不同活动。

返回数据给上一个活动

关键代码: setResult()

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_layout);
        // 接收 Intent 传递的数据
        Intent intent = getIntent();
        String data = intent.getStringExtra("extra_data");
        Log.d("SecondActivity", data);
        Button button2 = (Button) findViewById(R.id.button_2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = getIntent();
                // 仅仅用于传递数据,没有任何的 "意图"
                intent.putExtra("data_return", "Hello,FirstActivity!!!");
                setResult(RESULT_OK, intent);
                finish();
            }
        });
    }
}

这里采用的是 点击按钮 手动结束活动,如果是 按下 Back 回到 上一个活动,数据无法返回,可以重写 onBackPressed()解决这个问题,当用户按下 Back 就会执行 其中 的代码。

在第一个活动接收返回的数据
使用 startActivityForResult() 启动的活动,被销毁后会回调上一个活动的 onActivityResult(),重写这个方法可以获得返回的数据。

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

onActivityResult() 参数说明:

  • requestCode 启动活动时传入的请求码
    • 用于区别数据来源,因为当前Activity可能会启动多个不同的 Activity
  • resultCode 返回数据时传入的 处理结果, 如 RESULT_OK
  • data 携带着返回数据的 Intent

*活动的生命周期

深入理解活动的生命周期后可以写出更加连贯流畅的程序,并在如何合理管理应用资源方面发挥得游刃有余。

返回栈

Android 使用 任务(Task) 管理活动,一个任务就是一组存放在栈(返回栈)里的活动的集合。默认情况下:

  • 启动了一个新的活动,会 入栈,并处于栈顶
  • 按下 Back 或 调用 finish() 去销毁一个活动,处于栈顶的活动会 出栈,前一个入栈的活动会处于 栈顶

系统总是会显示处于 栈顶 的活动给用户

活动状态

在返回栈的位置是否可见系统回收其他
运行状态栈顶最不愿意回收
暂停状态非栈顶内存极低的情况
才会考虑回收
并不是每个活动都会占满整个屏幕
停止状态非栈顶完全不可见其他地方需要内存时
有可能回收
系统仍会保存相应的状态和成员变量
销毁状态被移除-最倾向回收保证手机的内存充足

活动的生存期

7个回调方法

  • onCreate() 活动第一次被创建时调用,完成活动的初始化操作,如
    • 加载布局
    • 绑定事件
  • onStart() 活动由 不可见 → \rightarrow 可见
    • 加载可见资源
  • onResume() 活动准备好和用户进行交互的时候调用,此时活动 一定位于栈顶,处于运行状态
  • onPause() 系统准备去 启动或者恢复 另一个活动的时候调用,但执行速度一定要快,不然会影响新的栈顶活动的使用。主要作用:
    • 将一些消耗CPU的资源释放掉
    • 保存一些关键数据
  • onStop() 活动完全不可见的时候调用
    • 一般用于 释放可见资源
    • onPause()区别在于 如果启动 对话框式的 Activity,onPause()会执行,onStop()并不会执行。
  • onDestory() 活动被销毁之前调用,稍后活动状态将变为 销毁状态
    • 完成释放内存操作
  • onRestart() 活动由 停止状态 → \rightarrow 运行状态 之前,也就是活动重新启动

Android官方提供的生命活动周期示意图如下:

在这里插入图片描述
图片来源:https://developer.android.google.cn/guide/components/activities/activity-lifecycle

3种生存期

除了 onRestart() 外两两相对,从而把活动分为3种生存期

  • 完整生存期:onCreate()onDestroy() 之间
  • 可见生存期:onStart()onStop() 之间
    • 活动对用户总是可见
    • 通过两个回调,合理管理对用户可见的资源
  • 前台生存期:onResume()onPause() 之间
    • 活动总是运行状态,可以和用户进行交互

体验活动的生命周期

新建一个项目,用HelloWorld项目的过程创建默认的主活动,再新建两个Empty活动:

  • NormalActivity
  • DialogActivity

布局中显示所在的活动,如

<?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"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This is a normal activity"
        />

</LinearLayout>

其中 DialogActivity 需要在 注册的时候 声明一下:

<activity android:name=".DialogActivity"
	<!-- theme属性指定主题,有很多内置主题,也可以自己定制 -->
    android:theme="@style/Theme.AppCompat.Dialog">
</activity>

MainActivity.java 实现上述7个回调方法。代码如下

public class MainActivity extends AppCompatActivity {
    public static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
        setContentView(R.layout.activity_main);
        Button startNormalActivity = findViewById(R.id.start_normal_activity);
        Button startDialogActivity = findViewById(R.id.start_dialog_activity);
        startNormalActivity.setOnClickListener((v)->{
            Intent intent = new Intent(MainActivity.this, NormalActivity.class);
            startActivity(intent);
        });
        startDialogActivity.setOnClickListener((v)->{
            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");
    }
}

体会如下:

  • 打开App时,onCreate, onStart, onResume
  • Back 退出后,onPause, onStop, onDestroy
  • Start NormalActivity,onPause, onStop
    • 返回,onRestart, onStart, onResume
  • Start DialogActivity,onPause
    • 返回或者按其他区域 onResume
  • Home, onPause, onStop(有时候不会出现)
    • 重新回去 onRestart, onStart, onResume
  • 跳到其他App onPause, onStop
  • 按 多任务 按钮,onPause, onStop
    • 重新回去 onRestart, onStart, onResume
    • 划走 onDestroy
    • 放入分屏模式 onDestroy, onCreate, onStart, onResume
      • 操作另外一个App,并退出,onPause
      • 打开另一个App,会不定时的 onResume
      • 调整分屏比例:onPause, onStop, onDestroy, onCreate, onStart, onResume
    • 放入分屏模式子App同上
      • 主App占满全屏。onPause, onStop
      • 此时重新回去 onDestroy, onCreate, onStart, onResume
  • 长按Back 强制退出,执行onPause,效果是真的没了,onDestroy代码都不执行……
  • 关闭屏幕:onPause, onStop
    • 亮屏 onRestart, onStart, onResume
    • 等好长一会(10min)…… onDestroy

活动被回收了怎么办

场景: App中有一个活动A,用户在活动A的基础上启动了活动B,活动A就进入了Stop状态,这时候由于系统内存不足,将活动A回收掉了,用户返回A的时候,会正常启动活动A, 但不会执行 onRestart(), 而是onCreate()方法,会被重新创建一次

问题: 活动A的临时数据和状态会丢失,如何解决?

SolutiononSaveInstanceState() 回调方法:保证在活动之前一定会被调用,解决活动被回收时临时数据得不到保存的问题

@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    String tempData = "Something you missed just now";
    outState.putString("data_key", tempData);
}

Bundle类型参数同 Intent 一样,可以用于保存数据,如 putString() 保存字符串:

  • 第一个参数 指定 key,用于后面从Bundle中取值
  • 第二个参数 是 真正要保存的内容

onCreate() 有个 Bundle 类型的 savedInstanceState 参数,一般情况下是 null,但如果在活动被系统回收之前有通过 onSaveInstanceState() 保存数据,这个参数就会带有之前所保存的全部数据。

恢复的代码如下:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "onCreate");
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null){
        String tempData = savedInstanceState.getString("data_key");
        Log.d(TAG, tempData);
    }
    // ...
}

通过相应的取值方法将数据取出即可。

Intent 还可以结合 Bundle 一起用于传递数据,首先可以把需要传递是数据都保存在 Bundle 对象中,将其放在 Intent 里,到了目标活动之后先从 Intent 中取出 Bundle,再从 Bundle 中一一取出数据。

活动的启动模式

在实际项目中需要根据 特定的需求 为每个活动 指定 恰当的启动模式。

有4种启动模式:

  • standard
    • 默认的启动模式
    • 启动活动时 不在乎 这个活动是否已经在 返回栈中,每次启动都会创建该活动的一个新的实例
  • singleTop
    • 启动活动时,如果发现 栈顶已经是该活动则认为可以直接使用它,不会再创建新的实例
    • 可以很好的解决重复创建 栈顶活动 的问题
    • 如果活动并没有处在 栈顶 位置,还是会创建多个活动实例
  • singleTask
    • 每次启动活动时,系统首先在 栈中 检查是否存在该活动的实例
      • 如果发现已经存在则
        • 直接使用该实例
        • 并把在这个活动之上的所有活动 都出栈 (会调用 onDestroy())
      • 如果没有发现
        • 创建一个新的活动实例
    • 如果指定了 tasgAffinity 会在活动启动时,启用一个新的返回栈来管理这个活动
  • singleInstance
    • 活动启动时,会启用一个新的返回栈来管理这个活动
    • 场景:一个活动是允许其他程序调用的,想实现其他程序和我们的程序共享这个活动实例
      • 由于 每个App 都会有自己的 返回栈,同一个活动在不同的返回栈中,入栈时 必然是 创建了新的实例
      • 使用该模式可以解决这个问题
      • 这种模式下会有单独的返回栈管理这个活动,不管是哪个App来访问这个活动,都共用的同一个返回栈
    • 同时解决了共享活动实例的问题
    • 例子:
      • 设计:App有三个Activity,SecondActivity指定启动模式为此模式,其他的默认
        • 1启动2
        • 2启动3
      • 效果:依次按 Back:3 → \rightarrow 1 → \rightarrow 2 → \rightarrow done
      • 1和3的 Task id 相同,2 跟 它们不同

可以在 AndroidManifest.xml 通过 <activity> 指定 android:LaunchMode 属性来选择启动模式。

活动的最佳实践

知晓当前是在哪一个活动——活动管理器

新建普通的 Java类 —— BaseActivity, 在活动创建使打印 类名。

public class BaseActivity extends AppCompatActivity {
    private static final String TAG = "BaseActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 打印当前实例的类名
        Log.d(TAG, getClass().getSimpleName());
        // 将当前创建的活动添加到 活动管理器
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 将马上要销毁的活动从活动管理器里移除
        ActivityCollector.removeActivity(this);
    }
}

随时随地退出程序

新建普通的Java类 —— ActivityCollector, 用 List 管理所有活动。

public class ActivityCollector {
    /**
     * 活动管理器
     */
    public static List<Activity> activities = new ArrayList<>(); // 通过List管理活动
    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();
            }
        }
    }
}

启动活动最佳写法

场景:假设 SecondActivity 中需要两个非常重要的字符串参数,启动时必须要传递过来,那么我们很容易写出如下代码:

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("param1", "data1");
intent.putExtra("param2", "data2");
startActivity(intent);

写法完成正确,但如果是在项目开发中经常会有对象的问题:

  • SecondActivity 并不是由你开发的
  • 但现在你负责需要有 启动 SecondActivity 这个功能
  • 而你却不清楚这个活动需要传递哪些数据

直观来看,两种办法:

  • 阅读 SecondActivity 中的代码
  • 询问编写 SecondActivity 的同时

以上会比较麻烦,只需要换一种写法,可以轻松解决上面的窘境

修改 SecondActivity.java

  • 添加 actionStart() 方法
    public static void actionStart(Context context, String data1, String data2) {
        Intent intent = new Intent(context, SecondActivity.class);
        intent.putExtra("param1", data1);
        intent.putExtra("param2", data2);
        context.startActivity(intent);
    }
    
    • 完成了 Intent 的构建
    • 所有 SecondActivity 中需要的数据都是通过 该方法的参数传递过来的,
    • 然后存储到 Intent
    • 最后调用 startActivity() 启动 SecondActivity
  • 好处:
    • 一目了然,所需要的数据在方法参数中全部体现出来了
    • 简化了启动活动的代码
      SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
      

小结

  • 活动的基本用法
  • 启动活动和数据传递
  • 活动的声明周期
  • 活动的启动 模式
  • 活动的最佳实践技巧

初步提交时刻:2020年9月14日00:41:06

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值