Activity之间的通信方式
假定一个场景,Activity1需要发送数据给到Activity2,并获取Activity2的返回的数据,例如:
输入信息,按下右下角按钮时,需要跳转到Activity2,同时携带Activity1中的数据。Activity2获取信息并弹出:
Activity2输入信息,按左下角返回Activity1,将数据返回:
实现该场景,可通过如下几种方式:
1、通过Intent跳转时携带Bundle
Intent是组件通信的载体,通过startActivity(Intent)方法跳转Activity,将数据由Intent中的Bundle携带。Bundle可以携带各种基本类型、包装类型数据,也可以携带序列化后的对象。
Serializable和Parcelable是常见的两种序列化方式,Bundle都可携带(Parcelable需要API Level>=33),eg:
跳转时携带数据:
Intent intent = new Intent(this, Activirty2.class);
MyUser myUser = new MyUser(account.getText().toString(), password.getText().toString(), 0, null);
// 1、创建Bundle
// Bundle bundle = new Bundle();
// bundle.putParcelable("user", myUser);
// intent.putExtras(bundle);
// 2、Intent的putExtra,本质上也是放到Bundle中,该方法会自动判断Intent是否已绑定Bundle,有则放入,没有则创建Bundle后放入
intent.putExtra("user", myUser);
startActivity(intent);
获取Bundle携带的数据:
private void getMessage() {
// getIntent()方法取出Intent
Intent intent = getIntent();
if(intent != null){
MyUser myUser = intent.getParcelableExtra("user", MyUser.class);
if(myUser!=null){
Log.e(TAG, "onCreate: "+ myUser.toString());
Toast.makeText(this, myUser.toString(), Toast.LENGTH_LONG).show();
}
}
}
上述方法在onCreate生命周期函数中调用,即可获取数据。
也可以通过startActivityForResult(Intent, int):第二个参数为requestCode,用于标识请求源,重写onActivityResult方法可以获取新启动的Activity的返回结果:
已过时
//启动Activity
startActivityForResult(intent,1)
//重写onActivityResult处理返回结果
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == 1 && resultCode == Activity.RESULT_OK){
// 处理第二个页面带回的数据
}
}
2、通过类的静态变量
通过类的静态变量,可以互相访问,从而达到相互通信。
两个Activity都创建用于相互访问的静态类变量,eg:
Activity1中:
//定义类的静态变量
public static MyUser myUser;
//跳转前更新好静态变量
myUser = new MyUser(account.getText().toString(), password.getText().toString(), 0, null);
Intent intent = new Intent(this, Activirty2.class);
startActivity(intent);
//获取数据时,访问Activirty2的静态类变量
if(Activirty2.myUser!=null){
Log.e(TAG, "onCreate: "+ Activirty2.myUser.toString());
Toast.makeText(this, Activirty2.myUser.toString(), Toast.LENGTH_LONG).show();
}
Activity2同理
3、通过Application
定义一个用于通信的类,eg:MyApplication,继承Application:
public class MyApplication extends Application {
private MyUser myUser;
public MyUser getMyUser() {
return myUser;
}
public void setMyUser(MyUser myUser) {
this.myUser = myUser;
}
}
需要在AndroidManifest.xml中声明
<application
android:name=".MyApplication"
...>
</application>
通过
MyApplication application = (MyApplication) getApplicationContext();
即可操作全局变量
4、借助外部存储
- SharedPreference:六种简单数据的存储:string,set,int,long,float,boolean
- SQLite
- 文件读写
5、Activity Results API
Google推荐的Activity、Fragment获取数据的方式
两个重要的组件:
- ActivityResultContract:抽象基类,需要继承它来实现自己的协议,从而定义如何传递、处理数据,需要定义输入类和输出类,无需输入可用Void
- ActivityResultLauncher:
- 调用registerForActivityResult(ActivityResultContract, ActivityResultCallback)方法注册ActivityResultLauncher,第一个参数为协议,可以使用预定义的,第二个参数定义跳转回来的回调函数
- 调用launch()方法来启动页面跳转
5.1、使用Activity Results API需要加入依赖:
implementation 'androidx.activity:activity:1.4.0'
implementation 'androidx.fragment:fragment:1.4.1'
5.2、调用预定义的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获取联系人 |
CreateDocument() | 提示用户选择一个文档,返回一个(file:/http:/content:)开头的Uri。 |
OpenDocumentTree() | 提示用户选择一个目录,并返回用户选择的作为一个Uri返回,应用程序可以完全管理返回目录中的文档。 |
OpenMultipleDocuments() | 提示用户选择文档(可以选择多个),分别返回它们的Uri,以List的形式。 |
OpenDocument() | 提示用户选择文档,返回它的Uri |
GetContent() | 提示用户选择一条内容,返回一个通过ContentResolver#openInputStream(Uri)访问原生数据的Uri地址(content://形式) 。默认情况下,它增加了 Intent#CATEGORY_OPENABLE, 返回可以表示流的内容。 |
使用最多的就是StartActivityForResult和RequestMultiplePermissions了。
(1)StartActivityForResult完成页面跳转
Activity1:
// 声明ActivityResultLauncher
private ActivityResultLauncher<Intent> activityResultLauncher;
//只能在onCreate、onAttach方法中注册
activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
MyUser user = result.getData().getParcelableExtra("user", MyUser.class);
if(user!=null){
Log.e(TAG, "onCreate: "+ user.toString());
Toast.makeText(MainActivity.this, user.toString(), Toast.LENGTH_LONG).show();
}
}
});
//点击按钮跳转
Intent intent = new Intent(this, Activirty2.class);
MyUser myUser = new MyUser(account.getText().toString(), password.getText().toString(), 0, null);
intent.putExtra("user", myUser);
activityResultLauncher.launch(intent);
Activity2:
// onCreate中获取Intent
Intent intent = getIntent();
if(intent != null){
myUser = intent.getParcelableExtra("user", MyUser.class);
Log.e(TAG, "onCreate: "+ myUser.toString());
Toast.makeText(this, myUser.toString(), Toast.LENGTH_LONG).show();
}
// 点击按钮时返回Activity1
Intent intent = new Intent();
if(myUser != null){
myUser.setAge(Integer.parseInt(age.getText().toString()));
myUser.setPhone(phone.getText().toString());
}
intent.putExtra("user", myUser);
setResult(RESULT_OK, intent);
finish();
(2)申请单个或多个权限
首先AndroidManifest.xml文件中需要加入权限声明:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
private ActivityResultLauncher<String> permissionLauncher;
private ActivityResultLauncher<String[]> multiplePermissionsLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
permissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback<Boolean>() {
@Override
public void onActivityResult(Boolean result) {
Log.d(TAG, "onActivityResult: "+(result? "权限申请成功" : "权限申请失败"));
if(result){
Toast.makeText(MainActivity.this, "权限申请成功", Toast.LENGTH_LONG).show();
return;
}
boolean b = shouldShowRequestPermissionRationale(Manifest.permission.CAMERA);
if(b){
// 被拒绝
Toast.makeText(MainActivity.this, "被用户拒绝", Toast.LENGTH_LONG).show();
}else{
Toast.makeText(MainActivity.this, "被用户拒绝,申请时或之前选择不再提示,智能跳转设置页手动赋予权限", Toast.LENGTH_LONG).show();
}
}
});
Button btnRequestPermission = findViewById(R.id.btn_RequestPermission);
btnRequestPermission.setOnClickListener(v->permissionLauncher.launch(Manifest.permission.CAMERA));
multiplePermissionsLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() {
@Override
public void onActivityResult(Map<String, Boolean> result) {
result.entrySet().forEach(entry -> {
Log.d(TAG, entry.getKey()+": "+(entry.getValue() ? "权限申请成功" : "权限申请失败"));
});
}
});
Button btnRequestMultiplePermissions = findViewById(R.id.btn_RequestMultiplePermissions);
btnRequestMultiplePermissions.setOnClickListener(v->
multiplePermissionsLauncher.launch(new String[]{Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION}));
}
(3)调用相机拍照
首先需要在res/xml下新增file_path.xml文件,如下:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path
name="images"
path="." />
<cache-path
name="cache-path"
path="." />
<external-path
name="external_storage_root"
path="." />
<external-cache-path
name="external_cache_path"
path="." />
</paths>
然后在AndroidManifest.xml注册provider
<provider
android:authorities="${applicationId}.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
private ActivityResultLauncher<Uri> takePictureLauncher;
private ActivityResultLauncher<Void> takePicturePreviewLauncher;
private Uri imageUri;
//拍照返回缩略图
takePicturePreviewLauncher = registerForActivityResult(new ActivityResultContracts.TakePicturePreview(), new ActivityResultCallback<Bitmap>() {
@Override
public void onActivityResult(Bitmap result) {
ivResult.setImageBitmap(result);
}
});
btnCaptureSmall.setOnClickListener(v->{
imageUri = getSaveUri(1);
//判断是否授权:照相
if(!isGranted(Manifest.permission.CAMERA)){
permissionLauncher.launch(Manifest.permission.CAMERA);
}else{
takePicturePreviewLauncher.launch(null);
}
});
// 拍照
takePictureLauncher = registerForActivityResult(new ActivityResultContracts.TakePicture(), new ActivityResultCallback<Boolean>() {
@Override
public void onActivityResult(Boolean result) {
if(result){
ivResult.setImageURI(imageUri);
}else{
Toast.makeText(MainActivity.this, "出错了55555555", Toast.LENGTH_LONG).show();
}
}
});
btnCaptureRaw.setOnClickListener(v->{
imageUri = getSaveUri(1);
//判断是否授权:照相
if(!isGranted(Manifest.permission.CAMERA)){
permissionLauncher.launch(Manifest.permission.CAMERA);
}else{
takePictureLauncher.launch(imageUri);
}
});
private Uri getSaveUri(int type) {
File storageFile;
if(Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()){
storageFile = getExternalCacheDir();
}else{
storageFile = getCacheDir();
}
File file = null;
try {
if(type == 1){
//photo
file = File.createTempFile("tmp_iamge_file", ".png", storageFile);
file.createNewFile(); //创建新的临时文件
// photoFile.deleteOnExit(); //推出JVM时删除
}else if(type == 2){
// vedio
file = File.createTempFile("tmp_iamge_file", ".mp4", storageFile);
file.createNewFile(); //创建新的临时文件
}else{
// 待新增
}
} catch (IOException e) {
throw new RuntimeException(e);
}
Uri res = FileProvider.getUriForFile(this, getApplicationContext().getPackageName()+".fileprovider", file);
return res;
}
private boolean isGranted(String permission){
return ActivityCompat.checkSelfPermission(MainActivity.this, permission) == PackageManager.PERMISSION_GRANTED;
}
(4)调用相机录像
private Uri videoUri;
private ActivityResultLauncher<Uri> takeVideoLauncher;
private boolean isPlay;
// 录视频
takeVideoLauncher = registerForActivityResult(new ActivityResultContracts.TakeVideo(), new ActivityResultCallback<Bitmap>() {
@Override
public void onActivityResult(Bitmap result) {
videoView.setVideoURI(videoUri);
videoView.setVisibility(View.VISIBLE);
}
});
btnCaptureVideo.setOnClickListener(v->{
videoUri = getSaveUri(2);
//判断是否授权:照相
if(!isGranted(Manifest.permission.CAMERA)){
permissionLauncher.launch(Manifest.permission.CAMERA);
}else{
takeVideoLauncher.launch(videoUri);
}
});
videoView.setOnClickListener(v->{
VideoView vv = (VideoView) v;
if(isPlay){
vv.pause();
}else{
vv.start();
}
});
(5)打开相册
private ActivityResultLauncher<String> selectPhotoLauncher;
// 选择图片
selectPhotoLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), new ActivityResultCallback<Uri>() {
@Override
public void onActivityResult(Uri result) {
ivResult.setImageURI(result);
}
});
btnSelectPhoto.setOnClickListener(v->{
selectPhotoLauncher.launch("image/*");
});
5.3、自定义ActivityResultContract
新建一个Contract类,继承ActivityResultContract<I,O>,其中,I是输入的类型,O是输出的类型。需要实现2个方法,createIntent和parseResult,输入类型I作为createIntent的参数,输出类型O作为parseResult方法的返回值。
/**
* 自定义ActivityResultContract
*/
public class CustomActivityResultContract extends ActivityResultContract<Integer, String> {
//input为输入值,然后包装成Intent传递
@NotNull
@Override
public Intent createIntent(@NotNull Context context, Integer input) {
//Intent包装了跳转到SecondActivity
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("in", input);
return intent;
}
//返回的Intent拆解,变换成String作为返回值
@Override
public String parseResult(int result, @Nullable Intent intent) {
//拿到SecondActivity返回的Intent,拆解出需要的数据并返回
return intent.getStringExtra("out");
}
}
6、EventBus
6.1、概述
一套适用于Android和Java的事件发布/订阅框架,用于在组件/线程间通信的场景中,将数据或事件传递给对应的订阅者
组件/线程间通信可以有很多种方式,如接口监听、Bundle、Handler、Executors、LocalBroadcastManager,但都存在一定的问题:
- 接口监听:强耦合
- Bundle:组件间通信,但是不够灵活
- Handler/Executors:主要用于线程间通信,样板代码臃肿
- LocalBroadcastManager:没有类型安全,从Extras中取数据容易类型转换异常
EventBus的优点:
- 使用事件总线机制,Publisher和Subscriber松耦合
- 提供透明线程间通信,隐藏线程切换
6.2、使用步骤
-
添加依赖
// Java: android { defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = [ eventBusIndex : 'com.have.a.good.MyEventBusAppIndex' ] } } } } dependencies { def eventbus_version = '3.2.0' implementation "org.greenrobot:eventbus:$eventbus_version" annotationProcessor "org.greenrobot:eventbus-annotation-processor:$eventbus_version" }
-
准备订阅者,@Subscribe
@Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) { Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show(); }
threadMode 指定线程模型,一共有五种
-
注册与注销
发布事件之前,注册订阅者;订阅者生命周期结束,注销订阅者
@Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { EventBus.getDefault().unregister(this); super.onStop(); }
-
发布事件
EventBus.getDefault().post(new MessageEvent("Hello everyone!")); EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
粘性事件的特点:
1、订阅 @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void onEvent(MessageEvent event) { textField.setText(event.message); } 2、发布 EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!")); 3、获取粘性事件 MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class); if(stickyEvent != null) { 4、移除粘性事件 EventBus.getDefault().removeStickyEvent(stickyEvent); // do something. } 5、移除粘性事件 MessageEvent stickyEvent = EventBus.getDefault().removeStickyEvent(MessageEvent.class); if(stickyEvent != null) { // do something. }
实现上文假定的场景,同样可以使用EventBus,但是应该注意的是,Activity1发布事件时,Activity2还未注册订阅者,因此需要发布粘性事件(存储在ConcurrentHashMap中),不再使用的粘性事件应该及时删除,避免内存泄漏。
Activity1、Activity2同时都是订阅者,因此应该在onCreate注册,onDestory注销
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
EventBus.getDefault().register(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
定义事件,messageSource标识发布源
public class MessageEvent {
private MyUser user;
private String messageSource;
public MessageEvent(MyUser user, String messageSource) {
this.user = user;
this.messageSource = messageSource;
}
public MyUser getUser() {
return user;
}
public void setUser(MyUser user) {
this.user = user;
}
public String getMessageSource() {
return messageSource;
}
public void setMessageSource(String messageSource) {
this.messageSource = messageSource;
}
}
Activity1、Activity2都应该准备订阅方法,区别只是判断messageSource:
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onMessageEvent(MessageEvent messageEvent){
if(messageEvent.getMessageSource().equals("ActivityX")){
// Activity2应该为成员变量赋值
//myUser = messageEvent.getUser();
Log.e(TAG, "onCreate: "+ messageEvent.getUser().toString());
Toast.makeText(MainActivity.this, messageEvent.getUser().toString(), Toast.LENGTH_LONG).show();
// 哈希表持有粘性事件的强引用,不再使用的粘性事件应该移除
EventBus.getDefault().removeStickyEvent(this);
}
}
Activity1:
// 按下按钮
MyUser myUser = new MyUser(account.getText().toString(), password.getText().toString(), 0, null);
EventBus.getDefault().postSticky(new MessageEvent(myUser, "Activity1"));
startActivity(new Intent(this, Activirty2.class));
Activity2:
// 按下按钮
if(myUser != null){
myUser.setAge(Integer.parseInt(age.getText().toString()));
myUser.setPhone(phone.getText().toString());
EventBus.getDefault().postSticky(new MessageEvent(myUser, "Activity2"));
}
finish();
6.3 、编译时索引
待学习补充
6.4、构建者模式
可以使用EventBusBuilder构建EventBus实例,也可以EventBus.getDefault()获得默认的 EventBus 实例
-
异常处理配置
配置项 描述 默认值 logSubscriberExceptions 订阅函数执行有异常时,打印异常信息 true sendSubscriberExceptionEvent 订阅函数执行有异常时,发布SubscriberExceptionEvent事件 true throwSubscriberException 订阅函数执行有异常时,抛出SubscriberException false logNoSubscriberMessages 事件无匹配订阅函数时,打印信息 true sendNoSubscriberEvent 事件无匹配订阅函数时,发布NoSubscriberEvent true -
索引配置
配置项 描述 默认值 ignoreGeneratedIndex 忽略订阅者索引 false addIndex(SubscriberInfoIndex index) 添加订阅者索引 无 -
事件订阅配置
配置项 描述 默认值 eventInheritance 是否触发父类事件订阅函数 true executorService(ExecutorService executorService) 线程池 Executors#newCachedThreadPool() strictMethodVerification 是否严格验证订阅函数签名数 true skipMethodVerificationFor(Class<?> clazz) 跳过方法签名验证 无
6.5、反混淆规则
待学习补充