02_四大应用组件之Activity
1. 理论概述
1.1 Activity的理解
Servlet的回顾理解
-
狭义:Servlet是一个interface,我们的Servlet类都必须是此接口的实现类
-
广义:Servlet是一种服务器端的组件,用来处理客户端提交的请求,并返回一个响应界面
组件的特点
-
它的类必须实现特定接口或继承特定类
-
需要在配置文件中配置其全类名
-
它的对象不是通过New来创建的,而是系统自动创建的
-
它的对象具有一定的生命周期,它的类中有对应的生命周期回调方法
Activity的定义
-
Activity,直译为活动,它是Android定义的四大应用组件之一,也是最重要用的最多的
-
Activity用来提供一个能让用户操作并与之交互的界面
-
一个应用有多个界面,也就是包含多个activity
-
打电话,发短信,拍照,发邮件等功能都是通过Activity来做的
类比Activity和Servlet
Servlet | Activity | |
---|---|---|
组件 | 服务器端组件 | Android客户端组件 |
规范定义的接口或类 | Servlet接口 | Activity类 |
注册 | web.xml | AndroidManifest.xml |
生命周期方法 | init() 、service() 、doGet() 、doPost() 、destroy() |
onCreate() 、onStart() 、onResume ...onDestroy() |
请求的发出源 | 浏览器 / 移动设备 | 手机屏幕 |
1.2 Intent 和 IndentFilter 的理解
Intent的理解
-
Intent,直译为意图
-
Intent是Activity,Service和BroadcastReceiver这三个应用组件之间进行通信的信使
-
例如:我要在Activity中启动另一个Activity,就必须使用Intent对象
-
意图对象还可以携带数据
-
注意:Intent不是Android四大组件之一
Intent的分类
-
显式意图:明确指定的目标组件的意图
-
创建对象:Intent(Context context, Class class)
-
何时使用:当操作当前自己应用的组件时使用
-
-
隐式意图:没有明确目标组件的意图
-
创建对象:Intent(String action)
-
何时使用:当操作其他应用的组件时使用
-
IntentFilter的理解
-
在配置Activity时,可以为Activity指定一个IntentFilter的配置
-
如果你的Activity希望其他应用能访问到,需要配置
<intent-filter>
-
如果你想启动其他应用的界面,你必须用隐式Intent,且目标页面Activity配置了
<intent-filter>
-
它的作用类似于web中的为Servlet配置的
<url-pattern>
<!-- AndroidManifest.xml -->
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <!-- android.intent.action.MAIN即为隐式意图的action -->
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
1.3 相关API
Intent
Intent(Context packageContext, Class<?> cls)
:用于创建显式意图对象
Intent(String action)
:用于创建隐式意图对象
putExtra(String name, Xxx value)
:保存额外数据
Xxx getXxxExtra(String name)
:获取额外数据
setData(Uri data)
:设置有特定格式的uri数据
Activity
startActivity(Intent intent)
:一般启动activity
startActivityForResult(int reqCode, Indent data)
:带回调启动Activity(已弃用)
onActivityResult(int reqCode, int resultCode, Intent data)
:回调方法
setResult(int resultCode, Intent data)
:设置要返回的结果
finish()
:结束当前Activity
getIntent()
:得到启动Activity的意图
来自2022的补丁:
2020已弃用,代替方法startActivityForResult(int reqCode, Indent data)
registerForActivityResult(ActivityResultContract, ActivityResultCallback)
,在本章<2. Activity和Intent使用测试>展开。
Activity生命周期相关方法
Oncreate()
☆
onStart()
onResume()
onPause()
onRestart()
onStop()
onDestroy()
☆
View:代表视图的根基类
setOnClickListener(OnClickListener listener)
☆:设置点击监听
setOnLongClickListener(OnLongListener listener)
☆:设置长按监听
SmsManager:发送短信的工具类
static SmsManager getDefault()
:得到当前对象
sendTextMessage(...)
:发送短信
设置点击监听的两种方式
-
方式一:Activity中添加监听
View.setOnClickListener(OnClickListener listener);
实现方式有new、this、成员变量。后面用例都会出现。 -
方式二:布局添加监听
-
layout中:
android:onclick="方法名"
-
activity中:
public void 方法名(View v) {}
-
来自2022的补丁:
方式二已被弃用,我找到的比较好的解释为How exactly does the android:onClick XML attribute differ from setOnClickListener?
概括一下:使用方法二,Android将只会在当前activity寻找调用的方法。但是如果你在使用fragments,Android将不会在添加这个xml的fragment的.java文件中寻找调用方法。所以极端情况下拥有多个fragments的单活动应用,onClick方法都堆在activity中,由一个activity监听众多事件,将会十分混乱。
不过目前学习项目没啥功能用用也没事,
android:onClick="back1"
后加上tools:ignore="UsingOnClickInXml"
可以取消警告。
Fragment (片段)
代表了应用UI中可重用的部分。片段定义和管理自己的布局,有自己的生命周期,可以处理自己的输入事件。fragment不能独立存在——它们必须由一个活动或另一个片段托管。片段的视图层次结构成为宿主视图层次结构的一部分,或者附加到宿主视图层次结构上。例如:平板应用,左边显示列表,右边显示内容详情。
2. Activity和Intent使用测试
测试用例
功能说明
-
在界面1点击“一般启动”:启动界面2,并显示界面1中输入的数据
-
在界面2点击“一般返回”:返回到界面1
-
在界面1点击“带回调启动”:启动界面2,并显示界面1输入的数据
-
在界面2点击“带结果返回”:返回界面1,并显示界面2输入的数据
实现顺序
-
界面布局
-
实现Activity的功能
1)定义所有需要操作的视图对象并初始化
2)给视图设置监听
3)在回调方法中实现逻辑
-
实现一般启动
1)定义好界面二(在
java/com/example/helloandroid
文件夹右键新建一个activity,选择empty activity即可)1)布局
2)定义Activity类
3)配置
4)重写Oncreate(),并加载布局
2)启动界面二
1)创建Intent对象(显式)
2)通过Intent携带额外数据
3)启动Activity
4)通过Intent读取额外数据
5) 显示到EditText
-
实现一般返回
1)在显示Second界面时,Main界面还在,只是被盖住了
2)关闭当前页面:finish()
带回调启动和带结果返回详情看下一小节startActivityForResult 和 registerForActivityResult
。
完整代码
需要在AndroidManifest.xml注册界面2
activity_main.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=".MainActivity"
android:id="@+id/linearLayout">
<EditText
android:layout_width="333dp"
android:layout_height="51dp"
android:inputType="textPersonName"
android:hint="@string/inputHint"
android:ems="10"
android:id="@+id/et_main_msg"
android:importantForAutofill="no"
android:textColorHint="#78909C"
android:minHeight="48dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="28dp"
android:layout_marginStart="16dp" />
<Button
android:text="@string/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/start1"
app:layout_constraintTop_toBottomOf="@+id/et_main_msg"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="20dp"
android:layout_marginStart="16dp" />
<Button
android:text="@string/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/start2"
app:layout_constraintTop_toBottomOf="@+id/start1"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
activity_main2.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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity2">
<EditText
android:layout_width="355dp"
android:layout_height="48dp"
android:inputType="textPersonName"
android:hint="@string/inputHint"
android:ems="10"
android:id="@+id/et_main_msg2"
android:importantForAutofill="no"
android:textColorHint="#78909C"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="36dp"
android:layout_marginStart="16dp" />
<!-- 此处Button绑定监听为方式二 -->
<Button
android:text="@string/btn_return1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/return1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_main_msg2"
android:layout_marginTop="24dp"
android:layout_marginStart="16dp"
android:onClick="back1"
tools:ignore="UsingOnClickInXml" />
<Button
android:text="@string/btn_return2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/return2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/return1"
android:layout_marginTop="20dp"
android:layout_marginStart="16dp"
android:onClick="back2"
tools:ignore="UsingOnClickInXml" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private EditText et_main_msg;
private Button btn_start1;
private Button btn_start2;
private ActivityResultLauncher<Intent> resultLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_main_msg = (EditText) findViewById(R.id.et_main_msg);
btn_start1 = (Button) findViewById(R.id.start1);
btn_start2 = (Button) findViewById(R.id.start2);
// 之前实现方式
/*
btn_download.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 如何得到外部类的对象:外部类名.this
Toast.makeText(MainActivity.this,"开始下载...",Toast.LENGTH_SHORT).show();
btn_download.setText("正在下载中...");
}
});
*/
// class implements View.OnClickListener 需要实现点击回调函数
// 这里this指向找到onClick函数
btn_start1.setOnClickListener(this);
btn_start2.setOnClickListener(this);
resultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
Intent data = result.getData();
int resultCode = result.getResultCode();
et_main_msg.setText(data.getStringExtra("RES"));
Log.i("!!!",resultCode + "");
});
}
@Override
public void onClick(View view) {
if(view==btn_start1) {
// 1)创建显式Intent对象
Intent intent = new Intent(this, MainActivity2.class);
intent.putExtra("MSG", et_main_msg.getText().toString());
// 2) 启动activity
startActivity(intent);
} else if(view==btn_start2) {
Intent intent = new Intent(this, MainActivity2.class);
intent.putExtra("MSG", et_main_msg.getText().toString());
resultLauncher.launch(intent);
}
}
}
MainActivity2.java
public class MainActivity2 extends AppCompatActivity {
private EditText et_main_msg2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
et_main_msg2 = (EditText) findViewById(R.id.et_main_msg2);
Intent intent = getIntent();
String msg = intent.getStringExtra("MSG");
et_main_msg2.setText(msg);
}
// 方式二绑定点击触发函数
public void back1(View v) {
// 关闭当前界面
finish();
}
public void back2(View v) {
int resultCode = 3;
Intent intent = new Intent();
intent.putExtra("RES", et_main_msg2.getText().toString());
setResult(resultCode, intent);
finish();
}
}
startActivityForResult 和 registerForActivityResult
startActivityForResult 使用
定义
/*
这个方法只能在定义了返回结果的Intent协议中使用。
如果发现没有Activity运行给定的Intent,这个方法会抛出android.content.ActivityNotFoundException。
*/
public void startActivityForResult(@SuppressLint("UnknownNullness") Intent intent,
int requestCode) {
super.startActivityForResult(intent, requestCode);
}
MainActivity.java
主界面
@Override
public void onClick(View view) { // 点击某按钮触发函数
// 1)创建显式Intent对象,MainActivity2为另一个activity
Intent intent = new Intent(this, MainActivity2.class);
// intent携带信息,et_main_msg为输入框视图对象,获取它的输入值
intent.putExtra("MSG", et_main_msg.getText().toString());
// 设置请求码
int requestCode = 2;
// 带回调启动activity2,当activity2结束的时候希望activity2能返回一个结果
startActivityForResult(intent, requestCode);
}
// 当activity2结束时触发
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 判断code
if(requestCode==2 && resultCode == 3) {
// 取出数据
String result = data.getStringExtra("RES");
et_main_msg.setText(result);
}
}
MainActivity2.java
界面2
@Override
protected void onCreate(Bundle savedInstanceState) { // 生命周期函数,被创建触发
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
...
Intent intent = getIntent();
String msg = intent.getStringExtra("MSG"); // 获取主界面传递的数据
et_main_msg2.setText(msg); // 将数据显示到界面2的输入框
}
// “带结果返回”按钮,监听点击事件触发函数,绑定方式为方式二
public void back2(View v) {
// 设置结果码
int resultCode = 3;
// 创建携带数据的Intent对象
Intent intent = new Intent();
intent.putExtra("RES", et_main_msg2.getText().toString());
// 设置返回的结果
setResult(resultCode, intent);
// 关闭当前activity
finish();
}
-
主界面startActivityForResult启动界面2
-
界面2 Oncreate 读取主界面传递信息
-
界面2设置返回结果,结束自己
-
主界面运行回调onActivityResult,显示界面2信息
registerForActivityResult 使用
registerForActivityResult 定义
启动一个activity需要一个laucher,这个laucher由registerForActivityResult
返回,这个方法需要两个参数,一个参数为一个抽象类——ActivityResultContract<I, O>
的实现,另一个参数是一个函数式接口的实现,内部只有一个抽象方法onActivityResult()
,(可以用一个lambda表达式来代替)。
@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull ActivityResultContract<I, O> contract,
@NonNull ActivityResultCallback<O> callback) {
return registerForActivityResult(contract, mActivityResultRegistry, callback);
}
ActivityResultContract 需要实现两个方法
/** Create an intent that can be used for {@link Activity#startActivityForResult} */
public abstract @NonNull Intent createIntent(@NonNull Context context,
@SuppressLint("UnknownNullness") I input);
/** Convert result obtained from {@link Activity#onActivityResult} to O */
@SuppressLint("UnknownNullness")
public abstract O parseResult(int resultCode, @Nullable Intent intent);
使用示例(Kotlin):
//常规带回调启动Activity
val launcher = registerForActivityResult(object : ActivityResultContract<String, String>() {
override fun createIntent(context: Context, input: String?): Intent {
//创建启动页面所需的Intent对象,传入需要传递的参数
return Intent(this@MainActivity, SecondActivity::class.java).apply {
putExtra("key", input)
}
}
override fun parseResult(resultCode: Int, intent: Intent?): String {
//页面回传的数据解析,相当于原onActivityResult方法
val data = intent?.getStringExtra("result") ?: ""
return if (resultCode == RESULT_OK) data else ""
}
}) {
//获取parseResult解析的数据
Log.e(TAG, "data:$it")
}
创建一个contract太麻烦了,官方为我们提供了很多预定义的contract:
查看继承关系:
-
StartActivityForResult: 通用的Contract,不做任何转换,Intent作为输入,ActivityResult作为输出,这也是最常用的一个协定。(和原来相比,不需要管理请求码)
-
RequestMultiplePermissions: 用于请求一组权限
-
RequestPermission: 用于请求单个权限
-
TakePicturePreview::调用MediaStore.ACTION_IMAGE_CAPTURE拍照,返回值为Bitmap图片
-
TakePicture: 调用MediaStore.ACTION_IMAGE_CAPTURE拍照,并将图片保存到给定的Uri地址,返回true表示保存成功。
-
TakeVideo:调用MediaStore.ACTION_VIDEO_CAPTURE 拍摄视频,保存到给定的Uri地址,返回一张缩略图。
-
PickContact: 从通讯录APP获取联系人
-
GetContent: 提示用选择一条内容,返回一个通过ContentResolver#openInputStream(Uri)访问原生数据的Uri地址(content://形式) 。默认情况下,它增加了Intent#CATEGORY_OPENABLE, 返回可以表示流的内容。
-
CreateDocument: 提示用户选择一个文档,返回一个(file:/http:/content:)开头的Uri。
-
OpenMultipleDocuments:提示用户选择文档(可以选择多个),分别返回它们的Uri,以List的形式。
-
OpenDocumentTree: 提示用户选择一个目录,并返回用户选择的作为一个Uri返回,应用程序可以完全管理返回目录中的文档。
使用StartActivityForResult示例(Java):
private ActivityResultLauncher resultLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// 初始化 launcher
resultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
Intent data = result.getData();
int resultCode = result.getResultCode();
et_main_msg.setText(data.getStringExtra("RES"));
Log.i("!!!",resultCode + "");
}
});
}
@Override
public void onClick(View view) {
Intent intent = new Intent(this, MainActivity2.class);
intent.putExtra("MSG", et_main_msg.getText().toString());
// 启动 launcher
resultLauncher.launch(intent);
}
使用lambda表达式创建launcher
resultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
Intent data = result.getData();
int resultCode = result.getResultCode();
et_main_msg.setText(data.getStringExtra("RES"));
Log.i("!!!",resultCode + "");
});
完整使用示例
MainActivity.java
主界面
private ActivityResultLauncher resultLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
// registerForActivityResult 只能在onCreate中注册
resultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
Intent data = result.getData();
int resultCode = result.getResultCode();
et_main_msg.setText(data.getStringExtra("RES"));
Log.i("!!!",resultCode + "");
});
}
@Override
public void onClick(View view) {
Intent intent = new Intent(this, MainActivity2.class);
intent.putExtra("MSG", et_main_msg.getText().toString());
// 启动 launcher
resultLauncher.launch(intent);
}
MainActivity2.java
界面2 这里和使用startActivityForResult()方法时写的一样
@Override
protected void onCreate(Bundle savedInstanceState) { // 生命周期函数,被创建触发
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
...
Intent intent = getIntent();
String msg = intent.getStringExtra("MSG"); // 获取主界面传递的数据
et_main_msg2.setText(msg); // 将数据显示到界面2的输入框
}
// “带结果返回”按钮,监听点击事件触发函数,绑定方式为方式二
public void back2(View v) {
// 设置结果码
int resultCode = 3;
// 创建携带数据的Intent对象
Intent intent = new Intent();
intent.putExtra("RES", et_main_msg2.getText().toString());
// 设置返回的结果
setResult(resultCode, intent);
// 关闭当前activity
finish();
}
3. Activity开发
3.1 Activity的使用
编写Activity的基本步骤
-
定义 Activity 类的子类 SecondActivity
-
在AndroidManifest.xml配置定义的组件
-
定义布局文件activity_second.xml
-
重写Activity的onCreate(),加载布局文件
AS中,在项目java文件夹的包下直接右键新建activity可以完成上述所有操作
启动一个Activity的流程图
图中两个Intent对象为同一个。
3.2 Activty的生命周期
Activity界面的四种状态
-
运行状态:可见也可操作
-
暂停状态:可见但不可操作
-
停止状态:不可见,但对象存在
-
死亡状态:对象不存在
Activity的生命周期回调方法
-
onCreate()
-
onStart()
-
onResume()
-
onPause()
-
onRestart()
-
onStop()
-
onDestroy()
Activity的生命周期图
测试用例
-
界面从“死亡”-->“运行”
创建对象--> onCreate() --> onStart() --> onResume()
-
界面从“运行”-->“死亡”
onPause --> onStop() --> onDestroy
-
界面从“运行”-->“停止”
onPause --> onStop()
-
界面从“停止”-->“运行”
onRestart() --> onStart() --> onResume
-
界面从“运行”-->“暂停”
onPause()
-
界面从“暂停”-->“运行”
onResume()
3.3 Activity的任务和返回堆栈
任务是用户在执行某项工作时与之互动的一系列 Activity 的集合。这些 Activity 按照每个 Activity 打开的顺序排列在一个返回堆栈中。例如,电子邮件应用可能有一个 Activity 来显示新邮件列表。当用户选择一封邮件时,系统会打开一个新的 Activity 来显示该邮件。这个新的 Activity 会添加到返回堆栈中。如果用户按返回按钮,这个新的 Activity 即会完成并从堆栈中退出。
图 1. 有关任务中的每个新 Activity 如何添加到返回堆栈的图示。当用户按返回按钮时,当前 Activity 会销毁,上一个 Activity 将恢复。
任务是一个整体单元,当用户开始一个新任务或通过主屏幕按钮进入主屏幕时,任务可移至“后台”。在后台时,任务中的所有 Activity 都会停止,但任务的返回堆栈会保持不变,当其他任务启动时,当前任务只是失去了焦点,如图 2 所示。这样一来,任务就可以返回到“前台”,以便用户可以从他们离开的地方继续操作。
图 2. 两个任务:任务 B 在前台接收用户互动,任务 A 在后台等待恢复。
注意:多个任务可以同时在后台进行。但是,如果用户同时运行很多后台任务,系统可能会为了恢复内存而开始销毁后台 Activity,导致 Activity 状态丢失。
Activity 和任务的默认行为总结如下:
-
当 Activity A 启动 Activity B 时,Activity A 会停止,但系统会保留其状态(例如滚动位置和输入到表单中的文本)。如果用户在 Activity B 中按返回按钮,系统会恢复 Activity A 及其状态。
-
当用户通过按主屏幕按钮离开任务时,当前 Activity 会停止,其任务会转到后台。系统会保留任务中每个 Activity 的状态。如果用户稍后通过点按该任务的启动器图标来恢复该任务,该任务会进入前台并恢复堆栈顶部的 Activity。
-
如果用户按返回按钮,当前 Activity 将从堆栈中退出并销毁。堆栈中的上一个 Activity 将恢复。Activity 被销毁后,系统不会保留该 Activity 的状态。
-
Activity 可以多次实例化,甚至是从其他任务对其进行实例化。
3.4 Activity的launchMode
说明:在Android中,启动一个Activity有时需要总是创建一个新对象,有时需要复用已有的对象。当在你的manifest文件中声明一个activity时,你可以使用
<activity>
元素的launchMode
属性来指定activity应该如何与一个task关联。
launchMode属性值(5个)为:
-
standard,默认启动模式,每次新建实例。
-
singleTop,若栈顶不是该类型的Activity,新建实例。否则,onNewIntent。
-
singleTask,若回退栈中没有该类型的Activity,新建实例,否则,onNewIntent+ClearTop。
-
singleInstance,共享的独立任务栈,栈中只有这一个Activity,没有其他Activity。
-
singleInstancePerTask,一个任务中只能有该 Activity 的一个实例,并且为根Activity。如果回退栈中有该 Activity实例,onNewIntent+ClearTop。
详细解释:
-
stardard(默认模式)
:系统在启动该 Activity 的任务中创建 Activity 的新实例,并将 intent 传送给该实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例。 -
singleTop
:如果当前任务的顶部已存在 Activity 的实例,则系统会通过调用其onNewIntent()
方法来将 intent 转送给该实例,而不是创建 Activity 的新实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例(但前提是返回堆栈顶部的 Activity 不是该 Activity 的现有实例)。例如,假设任务的返回堆栈包含根 Activity A 以及 Activity B、C 和位于顶部的 D(堆栈为 A-B-C-D;D 位于顶部)。收到以 D 类型 Activity 为目标的 intent。如果 D 采用默认的
"standard"
启动模式,则会启动该类的新实例,并且堆栈将变为 A-B-C-D-D。但是,如果 D 的启动模式为"singleTop"
,则 D 的现有实例会通过onNewIntent()
接收 intent,因为它位于堆栈顶部,堆栈仍为 A-B-C-D。但是,如果收到以 B 类型 Activity 为目标的 intent,则会在堆栈中添加 B 的新实例,即使其启动模式为"singleTop"
也是如此。注意:创建 Activity 的新实例后,用户可以按返回按钮返回到上一个 Activity。但是,当由 Activity 的现有实例处理新 intent 时,用户将无法通过按返回按钮返回到
onNewIntent()
收到新 intent 之前的 Activity 状态。 -
singleTask
:系统会创建新任务,并实例化新任务的根 Activity。但是,如果已有任务中已存在该 Activity 的实例,则系统会通过调用其onNewIntent()
方法将 intent 转送到该现有实例,而不是创建新实例。同时,在它之上的所有activity都会被销毁。注意:虽然 Activity 在新任务中启动,但用户按返回按钮仍会返回到上一个 Activity。
-
singleInstance
:与"singleTask"
相似,唯一不同的是系统不会将任何其他 Activity 启动到包含该实例的任务中。该 Activity 始终是其任务唯一的成员;由该 Activity 启动的任何 Activity 都会在其他的任务中打开。 -
singleInstancePerTask
:该 Activity 只能作为任务的根 Activity 运行,即创建该任务的第一个 Activity 。因此一个任务中只能有该 Activity 的一个实例。与singleTask
启动模式相比,如果设置了FLAG_ACTIVITY_MULTIPLE_TASK
或FLAG_ACTIVITY_NEW_DOCUMENT
标志,该 Activity 可以在不同任务的多个实例中启动。注意:"
singleTask
"和"singleInstancePerTask
"会从 task 中移除所有在启动 activity 之上的 activity。例如,假设一个任务由根活动A和活动B、C组成(任务是A-B-C;C在上面)。如果A的启动模式是"singleTask
"或"singleInstancePerTask
", A的现有实例通过onNewIntent()
来接收这个intent。B和C都完成了,现在任务只有A。
再举个例子,Android 浏览器应用在<activity>
元素中指定 singleTask
启动模式,由此声明网络浏览器 Activity 应始终在它自己的任务中打开。这意味着,如果您的应用发出打开 Android 浏览器的 intent,系统不会将其 Activity 置于您的应用所在的任务中,而是会为浏览器启动一个新任务,如果浏览器已经有任务在后台运行,则会将该任务转到前台来处理新 intent。
无论 Activity 是在新任务中启动的,还是在和启动它的 Activity 相同的任务中启动,用户按返回按钮都会回到上一个 Activity。但是,如果您启动了指定 singleTask
启动模式的 Activity,而后台任务中已存在该 Activity 的实例,则系统会将该后台任务整个转到前台运行。此时,返回堆栈包含了转到前台的任务中的所有 Activity,这些 Activity 都位于堆栈的顶部。图 4 展示了具体的情景。
图 3. 采用“singleTask”启动模式的 Activity 添加到返回堆栈的过程图示。如果 Activity 已经存在于某个具有自己的返回堆栈的后台任务中,那么整个返回堆栈也会转到前台,覆盖当前任务。
注意:为 Activity 指定
launchMode
属性的行为可以被启动 Activity 的 intent 中包含的标志所覆盖。
代码测试
三个activity布局文件,此处只显示界面1 。界面2、3可以类推。可以添加跳转自己的按钮体验singleTop
。
<!--activity_main.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=".MainActivity"
android:id="@+id/linearLayout">
<!-- tools:context=".MainActivity" 另外两个文件需要更改对应activity -->
<!-- text和绑定函数名自行修改 -->
<TextView
android:text="界面111111"
android:layout_width="261dp"
android:layout_height="48dp"
android:id="@+id/textView"
android:textSize="30sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="28dp"
android:layout_marginStart="32dp" />
<Button
android:text="前往界面222"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button"
app:layout_constraintTop_toBottomOf="@+id/textView"
android:layout_marginTop="36dp"
app:layout_constraintStart_toStartOf="@+id/textView"
android:onClick="goto2"/>
<Button
android:text="前往界面3333"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button2"
app:layout_constraintStart_toStartOf="@+id/button"
app:layout_constraintTop_toBottomOf="@+id/button"
android:layout_marginTop="44dp"
android:onClick="goto3"/>
</androidx.constraintlayout.widget.ConstraintLayout>
// MainActivity.java
public class MainActivity extends AppCompatActivity {
public MainActivity() { // 构造函数,创建时会自动调用,以此判断是否新建实例
Log.e("TAG", "create MainActivity");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void goto2(View v) {
startActivity(new Intent(this,MainActivity2.class));
}
public void goto3(View v) {
startActivity(new Intent(this,MainActivity3.class));
}
}
清单文件 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"
package="com.example.helloandroid">
<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:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HelloAndroid"
tools:targetApi="32">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask">
<!--一次可以只修改一个activity的launchMode属性-->
<!--自行更改体会-->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity2"
android:exported="false"
android:launchMode="singleTask"/>
<activity
android:name=".MainActivity3"
android:exported="false"
android:launchMode="singleTask"/>
</application>
</manifest>
4. 功能练习:打电话与发短信
功能描述
-
点击“打电话”:进入拨号界面
-
长按“打电话”:直接拨打电话
-
点击“发短信”:进入编辑短信界面
-
长按“发短信”:直接将短信发出去
技术点
-
布局的设计
-
点击事件和长按事件监听的添加
-
使用隐式意图拨打电话,进入拨号界面,进入发短信界面
-
使用SmsMessager发短信
-
权限的声明(如打电话,发短信)
直接将短信发过去需要启动另一个模拟器。在DDMS->device中查看模拟器的电话号码。
代码
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"
package="com.example.helloandroid">
<!--打电话的权限 AS不支持提示 自行网上搜索-->
<uses-permission android:name="android.permission.CALL_PHONE" />
<!--发短信的权限-->
<uses-permission android:name="android.permission.SEND_SMS" />
<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:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HelloAndroid"
tools:targetApi="32">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
activity_main.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=".MainActivity"
android:id="@+id/linearLayout">
<TextView
android:text="电话号码"
android:layout_width="95dp"
android:layout_height="45dp"
android:id="@+id/textView2"
android:textSize="16sp"
android:gravity="center"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="44dp"
android:layout_marginStart="4dp" />
<EditText
android:layout_width="253dp"
android:layout_height="49dp"
android:inputType="phone"
android:hint="请输入号码"
android:textSize="16sp"
android:ems="10"
android:id="@+id/edit_phone"
app:layout_constraintTop_toTopOf="@+id/textView2"
app:layout_constraintStart_toEndOf="@+id/textView2"
android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="@+id/textView2"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:text="短信内容"
android:layout_width="95dp"
android:layout_height="47dp"
android:id="@+id/textView3"
android:gravity="center"
app:layout_constraintStart_toStartOf="@+id/textView2"
app:layout_constraintTop_toBottomOf="@+id/textView2"
android:layout_marginTop="44dp" />
<EditText
android:layout_width="257dp"
android:layout_height="48dp"
android:inputType="text"
android:hint="请输入短信"
android:ems="10"
android:id="@+id/edit_msg"
app:layout_constraintStart_toEndOf="@+id/textView3"
android:layout_marginStart="4dp"
app:layout_constraintTop_toTopOf="@+id/textView3"
app:layout_constraintBottom_toBottomOf="@+id/textView3"
app:layout_constraintVertical_bias="1.0" />
<Button
android:text="打电话"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/call"
app:layout_constraintStart_toStartOf="@+id/textView3"
app:layout_constraintTop_toBottomOf="@+id/textView3"
android:layout_marginTop="52dp"
android:layout_marginStart="4dp" />
<Button
android:text="发短信"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/message"
app:layout_constraintStart_toEndOf="@+id/call"
app:layout_constraintTop_toTopOf="@+id/call"
android:layout_marginStart="44dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package com.example.helloandroid;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity {
private EditText edit_phone;
private EditText edit_msg;
private Button call;
private Button message;
private final View.OnClickListener onClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v == call) {
String action = "android.intent.action.DIAL"; // 先进入拨号页面,在DDMS查看日志获取包名,然后在系统源码文件夹找到包,查看配置清单得到action
action = Intent.ACTION_DIAL; // 和上面android.intent.action.DIAL一样
Intent intent = new Intent(action);
String number = edit_phone.getText().toString();
intent.setData(Uri.parse("tel:" + number)); // <data android:scheme="tel" /> 功能清单文件查看
startActivity(intent);
} else if (v == message) {
Intent intent = new Intent(Intent.ACTION_SENDTO);
String number = edit_phone.getText().toString();
String content = edit_msg.getText().toString();
intent.setData(Uri.parse("smsto:" + number));
intent.putExtra("sms_body", content); // 信息来自源码,找到类的源码
startActivity(intent);
}
}
};
private final View.OnLongClickListener onLongClickListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (v == call) {
String action = "android.intent.action.CALL";
action = Intent.ACTION_CALL; // 和上面一样
Intent intent = new Intent(action);
String number = edit_phone.getText().toString();
intent.setData(Uri.parse("tel:" + number)); // <data android:scheme="tel" />
startActivity(intent);
} else if (v == message) {
String number = edit_phone.getText().toString();
String content = edit_msg.getText().toString();
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(number, null, content, null, null);
}
//return false;
return true; // 此事件已经被消费了,不会再触发点击事件
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit_phone = findViewById(R.id.edit_phone);
edit_msg = findViewById(R.id.edit_msg);
call = findViewById(R.id.call);
message = findViewById(R.id.message);
call.setOnClickListener(onClickListener);
call.setOnLongClickListener(onLongClickListener);
message.setOnClickListener(onClickListener);
message.setOnLongClickListener(onLongClickListener);
}
}
系统源码文件夹 需要自己获取。
5. 总结
1. Activity 的理解
1)活动,四大应用组件之一
2)作用:提供能让用户操作并与之交互的界面
3)组件的特点:
-
它的类必须实现特定接口或继承特定类
-
需要在配置文件中配置其全类名
-
它的对象不是通过new来创建的,而是系统自动创建的
-
它的对象具有一定的生命周期,它的类中有对应的生命周期回调方法
4)哪些地方用到反射技术(Android)
-
配置文本中配置全类名
-
布局文件定义标签
-
显式意图:Intent(Context context, Class c)
2. Intent的理解
见 1.2
3. Intent的使用
1)创建:
-
显式:
Intent(Context packageContext, Class activityCls)
-
隐式:
Intent(String action)
action与<intent-filter>
的action匹配
2)携带数据:
-
额外:
putExtra(String key, Xxx value)
内部用map容器保存 -
有特定数据:
setData(Uri data)
// tel:123123 smsto:3432
3)读取数据:
-
额外:
Xxx getXxxExtra(String key)
-
有特定数据:
Uri getData()
4. Activity的使用
1)定义
-
定义一个类 extends Activity,并重写生命周期方法
-
在功能清单文件中使用
<activity>
注册
2)启动
-
一般:
startActivity(Intent intent)
-
带回调启动:
registerForActivityResult
重写
onActivityResult(ActivityResult result)
3)结束
-
一般:
finish()
-
带结果的:
setResult(int resultCode, Intent data); finish();
5. Activity的生命周期
见 3.2
6. TaskStack 和 launchMode
1)TaskStack
-
在Android中,系统用Task Stack(Back Stack)结构来存储管理启动的Activity对象
-
一个应用启动,系统就会为其创建一个对应的Task Stack来存储并管理该应用的activity对象
-
只有最上面的任务栈的栈顶的Activity才能显示在窗口中
2)launchMode
见 3.4
标签:02,layout,Intent,Activity,组件,intent,android,id From: https://www.cnblogs.com/culciful/p/16784579.html