Activity和Intent学习
Activity
Activity定义
- Activity,直译为活动,它是Android定义的四大应用组件之一,也是最重要、用得最多的组件
- Activity用来提供一个能让用户操作并与之交互的界面
- 一个应用有多个界面,也就是包含多个Activity
- 打电话、发短信、拍照、发邮件等功能都是通过Activity来做的
Activity相关API
- startActivity(Intent intent):一般启动Activity
- startActivityForResult(int reqCode, Intent intent):带回调启动Activity
- onActivityResult(int reqCode, int resultCode, Intent data):回调方法
- setResult(int resultCode, Intent data):设置要返回的结果
- finish():结束当前Activity
- getIntent():得到启动Activity的意图
Activity的生命周期函数
- onCreate():创建了一个界面
- onStart():界面呈现出来,但仍然不可以交互
- onResume():界面可见,可以交互
- onPause():暂停,界面不可交互,可能还可见(比如弹出窗口,只能操作窗口而主界面不可交互,但主界面仍然可见)
- onStop():界面不可见
- onDestory():界面进行销毁
-
生命周期函数的使用建议
-
onCreate():可以做一些控件、数据的初始化工作
-
onPause():可做一些小数据保存(不建议,因为会影响下一个界面打开的速度)
-
onDestroy():可以做一些收尾工作,数据保存工作
-
Intent
Intent的理解
- Intent是Activity,Service和BroadcastReceiver这三个应用组件之间进行通信的信使
- 例如:我要在Activity中启动另一个Actvity,就必须使用Intent对象
- Intent对象还可以携带数据
- 注意:Intent不是Android中的四大应用组件之一
Intent的分类
-
显式意图:明确指定目标组件的意图
- 创建对象:Intent(Context context, Class class)
- 何时使用:当操作当前自己应用的组件时使用
-
隐式意图:没有明确指定目标组件的意图
- 创建对象:Intent(String action)
- 何时使用:当操作其它应用的组件时使用
Intent相关API
Intent(Context packageContext, Class<?> cls):用于创建显示意图对象
Intent(String action):用于创建隐式意图对象
putExtra(String name, Xxx value):保存额外数据
Xxx getXxxExtra(String name):获取额外数据
setData(Uri data):设置有特定格式的uri数据
显式Intent
一共有三种跳转方式
- 第一种方式:
Intent intent = new Intent(MainActivity.this, SettingActivity.class);
- 第二种方式:(MainActivity.this只是比this的指向更加明了,本质上是相同的意思)
Intent intent = new Intent();
intent.setClass(this, SettingActivity.class);
- 第三种方式:
Intent intent = new Intent();
intent.setClassName("com.example.activityjump","com.example.activityjump.SettingActivity");
- 下面的方法与上面是等价的
Intent intent = new Intent();
intent.setClassName(MainActivity.this,"com.example.activityjump.SettingActivity");
隐式Intent
- 隐式跳转不明确的指明要跳转到哪一个界面,而是通过条件筛选确定目的界面
筛选条件的规则
-
Action(必要):动作
-
Category(必要):类别,是一个执行动作Action的附加信息
-
Data(可选):数据,是执行动作的URI和MIME类型,不同的Action有不同的Data数据指定
-
Type(可选):类型,显式指定Intent的数据类型(MIME)
-
Component(可选):组件,指定Intent的的目标组件的类名称
-
Extra(可选):扩展信,是添加一些组件的附加信息
-
如果你的Activity希望其它应用能访问到,需要在AndroidManifest中配置
-
如果你想启动其它应用的界面,你必须用隐式intent,且目标界面Activty配置了
-
可以在中为Activity设置三个规则,方便筛选和跳转
下图是一个示例:
<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>
Action:动作
- 描述intent的动作。
- 方法是intent.setAction()
- 必须有,只能set一个,必须和筛选条件里的action值一模一样才能匹配
- 自己程序内部可以随意设置action的值,但是一般用独一无二的包名作为action的值
Category:类别
- 对intent的一种额外描述
- 方法是addCategory()
- 设置intent时可选**(配置AndroidManifest时是必须的)**,可以add多个
- intent里默认会添加一个Default,不写也会自带一条category:
intent.addCategory(Intent.CATEGORY_DEFAULT);
- 每个都要和筛选条件里的category值一样才能匹配
如果想要当前Activity可以被隐式意图匹配,那么必须在AndroidManifest里面声明default的category
<activity
android:name=".CustomActivity"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.CUSTOM" />
//default这条必须声明,否则无法被隐式意图匹配
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Data:数据
- 目标界面需要有处理该数据的能力
- 是intent条件的附加条件,是可选的
data类筛选条件主要有以下两种:
- 数据地址:URI(统一资源标识符),比如:http://www.baidu.com,tel:110 等
- URI的格式:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
-
类型:mimeType,比如:image/jpeg、image/*、video/mp4 等
-
Manifest文件的过滤条件data示例:
<data android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string" />
我们需要做的:
-
在Intent代码中设置的条件:
- 自定义的条件:用来跳到自己写的界面
- 系统提供的条件:用来跳到系统其他app的界面(常用)
-
隐式跳转都是用来跳到其他app界面的
-
显式跳转都是用来跳到app内部界面的
隐式跳转实战示例
跳转到浏览器
布局文件:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="jumpToBaidu"
android:padding="10dp"
android:text="跳转到浏览器百度"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
在MainActivity中写jumpToBaidu方法:
其中Uri uri = Uri.parse(path),就是将一个网址字符串解析成为一个uri对象
public void jumpToBaidu(View view) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
}
跳转到相机
布局文件:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="jumpToCamera"
android:padding="10dp"
android:text="打开相机"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
在MainActivity中写jumpToCamera方法:
public void jumpToCamera(View view) {
Intent intent = new Intent();
intent.setAction("android.media.action.IMAGE_CAPTURE");
startActivity(intent);
}
注意:上面的代码只是单纯的调用手机的相机进行拍摄,并没有对拍摄后照片进行保存的功能。如果想要实现打开手机的相机应用的功能,请参考跳转微信的代码,将包名和类名改为相机的包名和类名即可。
跳转到相册
布局文件:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="jumpToAlbum"
android:padding="10dp"
android:text="打开相册"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
在MainActivity中写jumpToAlbum方法:
public void jumpToAlbum(View view) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_PICK);
//让intent可以匹配图片类型
intent.setType("image/*");
startActivity(intent);
}
跳转到电话界面
布局文件:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="jumpToPhone"
android:padding="10dp"
android:text="打电话"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
在MainActivity中写jumpToPhone方法:
public void jumpToPhone(View view) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_DIAL);
//tel:后面可以写电话号码,跳转后的界面会把该号码输入好
intent.setData(Uri.parse("tel:"));
startActivity(intent);
}
跳转到发消息界面
布局文件:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="jumpToMessage"
android:padding="10dp"
android:text="发消息"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
在MainActivity中写jumpToMessage方法:
public void jumpToMessage(View view) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SENDTO);
//smsto:后面可以写电话号码
intent.setData(Uri.parse("smsto:"));
//sms_body意思是消息主体,后面是要发送的消息
intent.putExtra("sms_body","你好,这是给你的短信");
startActivity(intent);
}
跳转到第三方界面(以微信举例)
布局文件:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="jumpToWeChat"
android:padding="10dp"
android:text="打开微信"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
在MainActivity中写jumpToWeChat方法(启动模式详见附录):
public void jumpToWeChat(View view) {
try{
Intent intent = new Intent();
//ACTION_MAIN表示intent作为初始活动启动,没有输入和返回
intent.setAction(Intent.ACTION_MAIN);
//用setComponent指定微信的包名
intent.setComponent(new ComponentName("com.tencent.mm","com.tencent.mm.ui.LauncherUI"));
//CATEGORY_LAUNCHER表示intent指定的是应用的启动器
intent.addCategory(Intent.CATEGORY_LAUNCHER);
//等价于指定启动模式为singleTask,具体见附录
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}catch (Exception e){
//无法顺利执行上述代码说明手机里找不到微信的包名,可能是没有安装微信
ToastUtil toastutil = new ToastUtil();
toastutil.ShowMsg(getApplicationContext(),"打开应用失败,可能没有安装此应用");
}
}
ToastUtil是TextView章节里面的Toast集成类,用于解决多次点击Toast无法即时响应的问题,代码如下:
package com.example.activityjump2;
import android.content.Context;
import android.widget.Toast;
public class ToastUtil {
public static Toast mToast;
public static void ShowMsg (Context context, String string) {
if (mToast != null) {
mToast.cancel();
}
mToast = Toast.makeText(context, string, Toast.LENGTH_SHORT);
mToast.show();
}
}
多界面情况下的Activity生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OqGSHsCb-1691145009921)(…/pictures/Activity4.png)]
Home键返回桌面和Back键退出应用的不同
-
当按下Home键,默认情况下stop前台的actiity,即activity设置成onstop,而不是ondestory
-
如果再次启动该activity不是调用onCreate,而是调用onSavedInstanceState方法,保持上次Activity的状态则是从onRestart开始->onStart->onResume
-
而当按下back键则不同,back键默认finish前台的activity,即activity的状态为onDestory为止
-
再次启动该activity则从onCreate开始,不会调用onSavedInstanceState方法
从一个界面跳转到另一个界面
- 从一个界面跳转到另一个界面时:
- 先onPause暂停当前页面,然后走新页面的创建流程
- 最后onStop之前那个页面。但它并没有被销毁。(因为他只是被别的盖住了,在任务栈的下一层,并没有退出)
- 从另一个界面返回时:
- 先onPause暂停当前页面,然后重新开始之前的页面(因为它没有销毁,所以不用创建)
- 最后,停止当前页面、销毁当前页面。(因为它从任务栈中退出了)
附录(相关知识的补充)
在AndroidManifest.xml中指定启动模式
-
在Android中,启动一个Activity有时需要总是创建一个新对象,有时需要复用已有的对象,可以在配置activity时通过launchMode属性指定。
-
launchMode属性值(4个)为:
- standard:标准模式,每次调用startActivity()方法就会产生一个新的实例
- singleTop(栈顶复用):如果已经有一个实例位于Activity任务栈的顶部时,就不产生新的实例;如果不位于栈顶,会产生一个新的实例
- singleTask(栈内复用):如果有一个实例,则把实例上面的全部弹出,将实例暴露在最上面;如果没有实例,则新建一个,正常入栈
- singleInstance:如果某任务栈有这个实例,直接复用:如果没有,则新建一个栈专门盛放该实例
-
activity的taskAffinity属性:为Activity指定所需任务栈,默认情况下,所有Activity所需任务栈的名字为应用的包名
在Intent中设置标志位指定启动模式
-
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)这种方式也可以为Activity指定启动模式
-
在Manifest文件中设置与在intent中设置的区别是:
- 优先级上intent中设置的方式高于在AndroidManifest.xml中设置
- 两种方式上限定范围上有所不同,比如:
- 第一种方式无法直接指定FLAG_ACTIVITY_CLEAR_TOP标示
- 第二种无法直接指定singleInstance模式
-
FLAG_ACTIVITY_CLEAR_TOP 加上 FLAG_ACTIVITY_NEW_TASK:相当于在xml中指定启动模式为singleTask
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- FLAG_ACTIVITY_SINGLE_TOP:相当于在xml中指定启动模式为singleTop
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
弹出对话框
在给按钮设置监听时,如果选择不用toast弹出消息,那么可以考虑使用弹出对话框的方式。
如隐式跳转到第三方界面的代码可以做出如下修改:
public void jumpToWeChat(View view) {
try{
Intent intent = new Intent();
intent.setAction(Intent.ACTION_MAIN);
intent.setComponent(new ComponentName("com.tencent.mm","com.tencent.mm.ui.LauncherUI"));
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
//可以更精确的抓取报错信息
}catch (ActivityNotFoundException e){
AlertDialog.Builder builder = new AlertDialog.Builder(this);
//设置对话框的标题
builder.setTitle("Error");
//设置对话框的内容
builder.setMessage("打开应用失败,可能没有安装此应用");
//设置退出按钮
builder.setNegativeButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
AlertDialog alertDialog = builder.create();
alertDialog.show();
}
}
弹出对话框式的Activity
上面的示例是使用了系统自带的对话框样式,我们也可以弹出一个对话框样式的Activity
- 首先在themes.xml文件中写下我们对话框样式的主题:
<style name="MyDialogTheme" parent="Theme.MaterialComponents.DayNight.Dialog.Alert" />
- 然后在AndroidManifest.xml文件中为我们的Activity配置这个主题:
<activity
android:name=".DialogActivity"
android:theme="@style/MyDialogTheme"
android:exported="false" />
- 编写布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
android:paddingTop="25dp"
tools:context=".DialogActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="打开应用失败,可能没有安装此应用"
android:textSize="20sp">
</TextView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="DialogDestroy" //按钮的监听
android:text="确定"
android:textSize="20dp"
android:layout_margin="20dp"/>
</LinearLayout>
</LinearLayout>
- 在DialogActivity中写个简单的退出:
public void DialogDestroy(View view) {
this.finish();
}
- 剩下就是在MainActivity布局中写一个按钮显式跳转到这个对话框式Activity:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="popDialog"
android:padding="10dp"
android:text="弹出界面"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
public void popDialog(View view) {
Intent intent = new Intent(this,DialogActivity.class);
startActivity(intent);
}
- 这样就实现了弹出一个对话框式的Activity
- 下图比较清晰的说明了在弹出一个对话框式的Activity时两个Activity的生命周期
弹出对话框和对话框式Activity的不同
-
弹出对话框全程没有对ActiviyA产生任何影响,ActivityA没有onPause更没有onStop
-
而弹出一个对话框式的ActivityB是先onPause暂停当前界面,然后走B的创建流程,并没有停止之前的界面
-
在我们的视角,ActivityA虽然被遮住了一部分,但还是可见的,只是不能交互,所以是处于onPause状态(只有页面不可见时才是onStop)
-
对话框式的ActivityB退出时也是先暂停B,再onResume活动A,再执行B的销毁任务(因为它从任务栈中退出了)
Of=“parent”
app:layout_constraintStart_toStartOf=“parent”
app:layout_constraintTop_toTopOf=“parent” />
```java
public void popDialog(View view) {
Intent intent = new Intent(this,DialogActivity.class);
startActivity(intent);
}
- 这样就实现了弹出一个对话框式的Activity
- 下图比较清晰的说明了在弹出一个对话框式的Activity时两个Activity的生命周期
弹出对话框和对话框式Activity的不同
-
弹出对话框全程没有对ActiviyA产生任何影响,ActivityA没有onPause更没有onStop
-
而弹出一个对话框式的ActivityB是先onPause暂停当前界面,然后走B的创建流程,并没有停止之前的界面
-
在我们的视角,ActivityA虽然被遮住了一部分,但还是可见的,只是不能交互,所以是处于onPause状态(只有页面不可见时才是onStop)
-
对话框式的ActivityB退出时也是先暂停B,再onResume活动A,再执行B的销毁任务(因为它从任务栈中退出了)