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_width
和android: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>
显示的活动如下:
- 加粗的是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.java
和 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">
</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 含蓄了许多。并不明确指出要启动哪一个活动,而是指定一系列更为抽象的 action
和 category
等信息,然后交由系统去分析这个 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
中指定的 action
和 category
时,这个活动才能响应该 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>
action
是Intent.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同上
- 主App占满全屏。
onPause
,onStop
- 此时重新回去
onDestroy
,onCreate
,onStart
,onResume
- 主App占满全屏。
- 重新回去
- 长按Back 强制退出,执行
onPause
,效果是真的没了,onDestroy
代码都不执行…… - 关闭屏幕:
onPause
,onStop
- 亮屏
onRestart
,onStart
,onResume
- 等好长一会(10min)……
onDestroy
- 亮屏
活动被回收了怎么办
场景: App中有一个活动A,用户在活动A的基础上启动了活动B,活动A就进入了Stop状态,这时候由于系统内存不足,将活动A回收掉了,用户返回A的时候,会正常启动活动A, 但不会执行
onRestart()
, 而是onCreate()
方法,会被重新创建一次
问题: 活动A的临时数据和状态会丢失,如何解决?
Solution:onSaveInstanceState()
回调方法:保证在活动之前一定会被调用,解决活动被回收时临时数据得不到保存的问题
@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 跟 它们不同
- 设计:App有三个Activity,SecondActivity指定启动模式为此模式,其他的默认
可以在 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