首页 > 其他分享 > 02_四大应用组件之Activity

02_四大应用组件之Activity

时间:2022-10-12 15:13:59浏览次数:83  
标签:02 layout Intent Activity 组件 intent android id

02_四大应用组件之Activity

1. 理论概述

1.1 Activity的理解

Servlet的回顾理解

  • 狭义:Servlet是一个interface,我们的Servlet类都必须是此接口的实现类

  • 广义:Servlet是一种服务器端的组件,用来处理客户端提交的请求,并返回一个响应界面

组件的特点

  • 它的类必须实现特定接口或继承特定类

  • 需要在配置文件中配置其全类名

  • 它的对象不是通过New来创建的,而是系统自动创建

  • 它的对象具有一定的生命周期,它的类中有对应的生命周期回调方法

Activity的定义

  • Activity,直译为活动,它是Android定义的四大应用组件之一,也是最重要用的最多的

  • Activity用来提供一个能让用户操作并与之交互的界面

  • 一个应用有多个界面,也就是包含多个activity

  • 打电话,发短信,拍照,发邮件等功能都是通过Activity来做的

官方解释

类比Activity和Servlet

 ServletActivity
组件 服务器端组件 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的补丁:

startActivityForResult(int reqCode, Indent data)2020已弃用,代替方法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. 在界面1点击“一般启动”:启动界面2,并显示界面1中输入的数据

  2. 在界面2点击“一般返回”:返回到界面1

  3. 在界面1点击“带回调启动”:启动界面2,并显示界面1输入的数据

  4. 在界面2点击“带结果返回”:返回界面1,并显示界面2输入的数据

实现顺序

  1. 界面布局

  2. 实现Activity的功能

    1)定义所有需要操作的视图对象并初始化

    2)给视图设置监听

    3)在回调方法中实现逻辑

  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

  4. 实现一般返回

    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();
 }
  1. 主界面startActivityForResult启动界面2

  2. 界面2 Oncreate 读取主界面传递信息

  3. 界面2设置返回结果,结束自己

  4. 主界面运行回调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的基本步骤

  1. 定义 Activity 类的子类 SecondActivity

  2. 在AndroidManifest.xml配置定义的组件

  3. 定义布局文件activity_second.xml

  4. 重写Activity的onCreate(),加载布局文件

AS中,在项目java文件夹的包下直接右键新建activity可以完成上述所有操作

启动一个Activity的流程图

图中两个Intent对象为同一个。

3.2 Activty的生命周期

Activity界面的四种状态

  • 运行状态:可见也可操作

  • 暂停状态:可见但不可操作

  • 停止状态:不可见,但对象存在

  • 死亡状态:对象不存在

Activity的生命周期回调方法

  • onCreate()

  • onStart()

  • onResume()

  • onPause()

  • onRestart()

  • onStop()

  • onDestroy()

Activity的生命周期图

 

 

测试用例

  1. 界面从“死亡”-->“运行”

    创建对象--> onCreate() --> onStart() --> onResume()

  2. 界面从“运行”-->“死亡”

    onPause --> onStop() --> onDestroy

  3. 界面从“运行”-->“停止”

    onPause --> onStop()

  4. 界面从“停止”-->“运行”

    onRestart() --> onStart() --> onResume

  5. 界面从“运行”-->“暂停”

    onPause()

  6. 界面从“暂停”-->“运行”

    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_TASKFLAG_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. 功能练习:打电话与发短信

 

 

功能描述

  1. 点击“打电话”:进入拨号界面

  2. 长按“打电话”:直接拨打电话

  3. 点击“发短信”:进入编辑短信界面

  4. 长按“发短信”:直接将短信发出去

技术点

  1. 布局的设计

  2. 点击事件和长按事件监听的添加

  3. 使用隐式意图拨打电话,进入拨号界面,进入发短信界面

  4. 使用SmsMessager发短信

  5. 权限的声明(如打电话,发短信)

直接将短信发过去需要启动另一个模拟器。在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

相关文章

  • 活动预告 | Feature Store Summit 2022
    10月12日北京时间上午7:05-7:15 (10.1116:05-16:15GMT-7),OpenMLDBPMC、第四范式系统架构师卢冕,将在FeatureStoreSummit2022活动中为大家带来议题为《OpenMLDB:......
  • 天梯赛-L1002题
    本题要求你写个程序把给定的符号打印成沙漏的形状。例如给定17个“*”,要求按下列格式打印***************** 所谓“沙漏形状”,是指每行输出奇数个符号;各行符......
  • letcode刷题记录-day02-回文数
    回文数题目描述给定一个整数数组nums 和一个整数目标值target,请你在该数组中找出和为目标值target 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入......
  • 【人脸表情识别】不得不读的重要论文推荐(2019-2020篇)
    上一篇专栏文章我们介绍了2015-2018年基于图片的人脸表情识别代表性方法。本文将延续上一篇的内容,继续盘点2019-2020基于图片的人脸表情识别的代表性工作。作者&编辑|Menp......
  • 【人脸表情识别】情绪识别相关会议、比赛汇总(2018-2020)
    前面专栏中,我们介绍了有关基于图片/视频的人脸表情识别的相关内容,也了解了通过回归的方式来理解表情的方式——基于连续模型的人脸表情识别。在专栏的最后一篇文章中,我们将......
  • BAPI_NETWORK_COMP_REMOVE 物料组件删除
     物料组件删除BAPI:BAPI_NETWORK_COMP_REMOVE首先调用BAPI_NETWORK_COMP_GETDETAIL获取明细,得到物料组件唯一编号:component​​​​​​只需要将物料组件......
  • ModStartBlog v5.9.0 新增组件特性,基础布局优化
    系统介绍ModStart是一个基于Laravel模块化极速开发框架。模块市场拥有丰富的功能应用,支持后台一键快速安装,让开发者能快的实现业务功能开发。系统完全开源,基于Apache2.......
  • 1.1.NIO-三大组件
    1、NIO基础non-blockingio非阻塞io1.1、三大组件1.1.1、Channel&Bufferchannel类似于stream,它就是读写数据的双向通道,可以从channel将数据读入buffer,也可以将buffer......
  • 中望CAD机械版 2023软件安装包和安装教程
    中望CAD机械版2023软件简介:中望CAD机械版2023采用国际领先的CAD核心技术,持续优化软件性能和易用性,增强智能绘图功能,新增尺寸驱动、快速计算器、打印拼图等多个智能化功能,......
  • Jchardet——支持检测并输出文件编码方式的组件
    Jchardet——支持检测并输出文件编码方式的组件​简介​Jchardet是OpenAtomOpenHarmony(以下简称“OpenHarmony”)系统的一款检测文本编码的组件。当上传一个文件时,组件可以......