Android Activity Results API-
Posted QXXXD
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Activity Results API-相关的知识,希望对你有一定的参考价值。
一、Activity Results API介绍
Activity Results API 是 Google官方推荐的Activity、Fragment获取数据的方式。Activity Results API 中两个重要的组件:ActivityResultContract
和ActivityResultLauncher
。
① ActivityResultContract
: 协议,它定义了如何传递数据和如何处理返回的数据。ActivityResultContract
是一个抽象类,你需要继承它来创建自己的协议,每个 ActivityResultContract
都需要定义输入和输出类型,如果您不需要任何输入,可使用 Void(在 Kotlin 中,使用 Void? 或 Unit)作为输入类型。
②ActivityResultLauncher
: 启动器,调用ActivityResultLauncher
的launch
方法来启动页面跳转,作用相当于原来的startActivity()
二、调用预定义的Contract
在Activity和Fragment中调用内置的Contract的简单使用。
引入库
implementation 'androidx.activity:activity:1.4.0'
implementation 'androidx.fragment:fragment:1.4.1'
1、预定义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 , 返回可以表示流的内容。 |
上面这些预定义的Contract中,除了StartActivityForResult
和RequestMultiplePermissions
之外,基本都是处理的与其他APP交互,返回数据的场景,比如,拍照,选择图片,选择联系人,打开文档等等。使用最多的就是StartActivityForResult
和RequestMultiplePermissions
了。
2、简单使用
ActivityResultLauncher必需在activity的onCreate()方法或fragment的onCreate()、onAttach()前先注册,然后在需要调用的地方调用launch方法
1)示例一:页面跳转
从MainActivity跳转到TwoActicity
public class MainActivity extends AppCompatActivity
private ActivityResultLauncher<Intent> mIntentActivityResultLauncher;
private Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
//只能在onCreate、onAttach方法中注册
mIntentActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>()
@Override
public void onActivityResult(ActivityResult result)
//回传的数据处理
String answer = result.getData().getStringExtra("message");
Toast.makeText(mContext, answer, Toast.LENGTH_LONG).show();
);
//按钮点击事件
findViewById(R.id.bt).setOnClickListener(v ->
//执行跳转的方法
jumpToTwoActivity();
);
private void jumpToTwoActivity()
//封装Intent
Intent intent = new Intent(mContext,TwoActivity.class);
intent.putExtra("message", "问:吃饭了吗?");
//跳转
mIntentActivityResultLauncher.launch(intent);
从TwoActivity返回数据
public class TwoActivity extends AppCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//页面传递过来的数据
String askMsg = getIntent().getStringExtra("message");
Toast.makeText(this, askMsg, Toast.LENGTH_LONG).show();
//点击事件
findViewById(R.id.bt).setOnClickListener(v ->
//回传数据
Intent intent = new Intent();
intent.putExtra("message", "答:我吃过了");
setResult(RESULT_OK, intent);
finish();
);
2)示例二:申请单个或多个权限
推荐用第三方请求权限的库,不推荐这样写
//申请单个权限
registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback<Boolean>()
@Override
public void onActivityResult(Boolean result)
if(result)
Log.d(TAG,"获取权限成功");
else
Log.d(TAG,"获取权限失败");
).launch(Manifest.permission.READ_CONTACTS);
//申请多个权限
String[] permissions = Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE;
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>()
@Override
public void onActivityResult(Map<String, Boolean> result)
//result的key为权限,value为权限是否申请通过
//是否请求权限前弹窗询问?
//第一次用户用户点击拒绝权限shouldShowRequestPermissionRationale返回false,如果用户拒绝了权限并且勾选了不再询问shouldShowRequestPermissionRationale返回true
//通过这个特性可以判断哪些权限被永久拒绝
ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,Manifest.permission.CAMERA);
).launch(permissions);
3)示例三:获取联系人信息
获取联系人信息前需要申请权限Manifest.permission.READ_CONTACTS
。代码如下:
<uses-permission android:name="android.permission.READ_CONTACTS"/>
public class MainActivity extends AppCompatActivity
private static final String TAG = MainActivity.class.getSimpleName();
private ActivityResultLauncher<Void> mIntentActivityResultLauncher;
private Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
//申请读取通讯录权限
registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback<Boolean>()
@Override
public void onActivityResult(Boolean result)
if(result)
Log.d(TAG,"获取权限成功");
//跳转读取联系人信息
mIntentActivityResultLauncher.launch(null);
else
Log.d(TAG,"获取权限失败");
).launch(Manifest.permission.READ_CONTACTS);
//只能在onCreate、onAttach方法中注册
mIntentActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.PickContact(), new ActivityResultCallback<Uri>()
@SuppressLint("Range")
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onActivityResult(Uri result)
if (result != null)
StringBuilder builder = new StringBuilder();
Cursor cursor = getContentResolver().query(result, null, null, null);
while (cursor.moveToNext())
//联系人ID
String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
builder.append("用户ID:" + id + ",");
//联系人姓名
String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
builder.append("用户姓名:" + name + ",");
Cursor query = getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + id,
null,
null);
builder.append("用户手机号:");
while (query.moveToNext())
String phoneNum = query.getString(query.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
builder.append(phoneNum + " | ");
query.close();
cursor.close();
//打印
Log.d(TAG, builder.toString());
);
弹出权限申请窗口时点击了同意权限。日志打印结果为:
D/MainActivity: 获取权限成功
D/MainActivity: 用户ID:1,用户姓名:张三,用户手机号:130 1234 5678 | 131 1234 5678 |
4)示例四:调用相机拍照
//申请多个权限
String[] permissions = Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE;
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>()
@Override
public void onActivityResult(Map<String, Boolean> result)
//申请权限成功后申请调用相机拍照
mIntentActivityResultLauncher.launch(null);
).launch(permissions);
//申请调用相机拍照
mIntentActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.TakePicturePreview(), new ActivityResultCallback<Bitmap>()
@Override
public void onActivityResult(Bitmap bitmap)
Glide.with(mContext).load(bitmap).format(PREFER_ARGB_8888).into(iv);
);
5)示例五:获取文件
在Android Q以上获取文件比较困难,存在适配的问题,解决办法:
①mainfest
文件配置
<provider
android:name="androidx.core.content.FileProvider" //模块中需要继承FileProvider创建新的
android:authorities="你的包名.fileprovider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="android:authorities">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
②file_paths.xml
指定文件类型
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<root-path
name="root"
path="" />
<files-path
name="files"
path="" />
<cache-path
name="cache"
path="" />
<external-path
name="external"
path="" />
<external-files-path
name="external_file_path"
path="" />
<external-cache-path
name="external_cache_path"
path="" />
</paths>
</resources>
③工具类
/**
* 包含Uri转path
* 包含path转uri
* 兼容Android 10
*/
public class FileProviderUtils
/**
* 根据Uri获取文件绝对路径,解决Android4.4以上版本Uri转换 兼容Android 10
*
* @param context
* @param imageUri
*/
public static String getFileAbsolutePath(Context context, Uri imageUri)
if (context == null || imageUri == null)
return null;
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT)
return getRealFilePath(context, imageUri);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && DocumentsContract.isDocumentUri(context, imageUri))
if (isExternalStorageDocument(imageUri))
String docId = DocumentsContract.getDocumentId(imageUri);
String[] split = docId.split(":");
String type = split[0];
if ("primary".equalsIgnoreCase(type))
return Environment.getExternalStorageDirectory() + "/" + split[1];
else if (isDownloadsDocument(imageUri))
String id = DocumentsContract.getDocumentId(imageUri);
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
else if (isMediaDocument(imageUri))
String docId = DocumentsContract.getDocumentId(imageUri);
String[] split = docId.split(":");
String type = split[0];
Uri contentUri = null;
if ("image".equals(type))
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
else if ("video".equals(type))
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
else if ("audio".equals(type))
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String selection = MediaStore.Images.Media._ID + "=?";
String[] selectionArgs = new String[]split[1];
return getDataColumn(context, contentUri, selection, selectionArgs);
// MediaStore (and general)
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
return uriToFileApiQ(context,imageUri);
else if ("content".equalsIgnoreCase(imageUri.getScheme()))
// Return the remote address
if (isGooglePhotosUri(imageUri))
return imageUri.getLastPathSegment();
return getDataColumn(context, imageUri, null, null);
// File
else if ("file".equalsIgnoreCase(imageUri.getScheme()))
return imageUri.getPath();
return null;
//此方法 只能用于4.4以下的版本
private static String getRealFilePath(final Context context, final Uri uri)
if (null == uri)
return null;
final String scheme = uri.getScheme();
String data = null;
if (scheme == null)
data = uri.getPath();
else if (ContentResolver.SCHEME_FILE.equals(scheme))
data = uri.getPath();
else if (ContentResolver.SCHEME_CONTENT.equals(scheme))
String[] projection = MediaStore.Images.ImageColumns.DATA;
Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
// Cursor cursor = context.getContentResolver().query(uri, new String[]MediaStore.Images.ImageColumns.DATA, null, null, null);
if (null != cursor)
if (cursor.moveToFirst())
int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
if (index > -1)
data = cursor.getString(index);
cursor.close();
return data;
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
private static boolean isExternalStorageDocument(Uri uri)
return "com.android.externalstorage.documents".equals(uri.getAuthority());
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
private static boolean isDownloadsDocument(Uri uri)
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs)
Cursor cursor = null;
String column = MediaStore.Images.Media.DATA;
String[] projection = column;
try
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst())
int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
finally
if (cursor != null)
cursor.close();
return null;
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
private static boolean isMediaDocument(Uri uri)
return "com.android.providers.media.documents".equals(uri.getAuthority());
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
private static boolean isGooglePhotosUri(Uri uri)
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
/**
* Android 10 以上适配 另一种写法
* @param context
* @param uri
* @return
*/
@SuppressLint("Range")
private static String getFileFromContentUri(Context context, Uri uri)
if (uri == null)
return null;
String filePath;
String[] filePathColumn = MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME;
ContentResolver contentResolver = context.getContentResolver();
Cursor cursor = contentResolver.query(uri, filePathColumn, null,
null, null);
if (cursor != null)
cursor.moveToFirst();
try
filePath = cursor.getString(cursor.getColumnIndex(filePathColumn[0]));
return filePath;
catch (Exception e)
finally
cursor.close();
return "";
/**
* Android 10 以上适配
* @param context
* @param uri
* @return
*/
@SuppressLint("Range")
@RequiresApi(api = Build.VERSION_CODES.Q)
private static String uriToFileApiQ(Context context, Uri uri)
File file = null;
//android10以上转换
if (uri.getScheme().equals(ContentResolver.SCHEME_FILE))
file = new File(uri.getPath());
else if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT))
//把文件复制到沙盒目录
ContentResolver contentResolver = context.getContentResolver();
Cursor cursor = contentResolver.query(uri, null, null, null, null);
if (cursor.moveToFirst())
String displayName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
try
InputStream is = contentResolver.openInputStream(uri);
File cache = new File(context.getExternalCacheDir().getAbsolutePath(), Math.round((Math.random() + 1) * 1000) + displayName);
FileOutputStream fos = new FileOutputStream(cache);
FileUtils.copy(is, fos);
file = cache;
fos.close();
is.close();
catch (IOException e)
e.printStackTrace();
return file.getAbsolutePath();
/**
* path转uri
* @param context
* @param filePath
* @return
*/
public static Uri toUri(Context context,String filePath)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
return FileProvider.getUriForFile(context, context.getApplicationInfo().packageName + ".fileprovider", new File(filePath));
return Uri.fromFile(new File(filePath));
解决了适配问题,看如何获取文件:
获取单类型单文件
//获取单类型单文件
registerForActivityResult(new ActivityResultContracts.GetContent(), new ActivityResultCallback<Uri>()
@Override
public void onActivityResult(Uri uri)
if (uri != null)
//获取文件真实路径
String path = FileProviderUtils.getFileAbsolutePath(mContext, uri);
Log.d(TAG, "文件真实路径:" + path);
).launch("text/plain"); //具体多少中文件类型可以看MediaFile这个类
//打印日志
D/MainActivity: 文件真实路径:/storage/emulated/0/Android/data/com.cad/files/tbslog/tbslog.txt
获取多种文件类型,可以使用OpenDocument。
三、自定义ActivityResultContract
新建一个Contract类,继承自ActivityResultContract<I,O>,其中,I是输入的类型,O是输出的类型。需要实现2个方法,createIntent和parseResult,输入类型I作为createIntent的参数,输出类型O作为parseResult方法的返回值。
自定义ActivityResultContract<I,O>
/**
* 自定义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");
SecondActivity的代码
Intent intent = new Intent();
intent.putExtra("out","2");
setResult(1001,intent);
finish();
在MainActivity中使用
registerForActivityResult(new CustomActivityResultContract(), new ActivityResultCallback<String>()
@Override
public void onActivityResult(String result) //参数就是返回值
Log.d(TAG, "out:" + result);
).launch(1); //输入值就是在这里传入
日志
8058-8058/com.abc.rxjava3 D/MainActivity: out:2
四、初级封装
1、在Activity/Fragment中自动注册
Activity生命周期监听,有个接口类Application.ActivityLifecycleCallbacks,先看下面的简单示例:
示例的目录:
首先看AppLifeCallback
类,实现Application.ActivityLifecycleCallbacks
接口:
class AppLifeCallback : Application.ActivityLifecycleCallbacks
companion object
const val TAG = "AppHelper"
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?)
//打印调用此方法的Activity的类的名称
Log.d(TAG, "onActivityCreated:$activity.localClassName")
override fun onActivityDestroyed(activity: Activity)
//打印调用此方法的Activity的类的名称
Log.d(TAG, "onActivityDestroyed:$activity.localClassName")
override fun onActivityStarted(activity: Activity)
override fun onActivityResumed(activity: Activity)
override fun onActivityPaused(activity: Activity)
override fun onActivityStopped(activity: Activity)
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle)
再看AppHelper
类,重点在init
方法:
object AppHelper
/**
* 注冊Application的生命周期
*/
fun init(application: Application)
//注册自定义的生命周期
application.registerActivityLifecycleCallbacks(AppLifeCallback())
在App
中完成init
初始化操作:
class App :Application()
override fun onCreate()
super.onCreate()
//注册生命周期
AppHelper.init(this)
MainActivity
和SecondActivity
的代码比较简单:
class MainActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
registerForActivityResult(ActivityResultContracts.StartActivityForResult(), ActivityResultCallback
//JUST STARTACTIVITY DO NOTHING
).launch(Intent(this, SecondActivity::class.java));
class SecondActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
//finish
finish()
打印日志:
D/AppHelper: onActivityCreated:MainActivity
D/AppHelper: onActivityCreated:SecondActivity
D/AppHelper: onActivityDestroyed:SecondActivity
可以发现实现了Application.ActivityLifecycleCallbacks
,所有Activity的生命周期都会在这里回调,那么就可以在这里完成Activity跳转前的注册。
2、初级封装
以下代码来自于DeMon-ARA,详细注释都写在代码里
目录结构:
DemoActivityCallbacks
类代码:
object DemoActivityCallbacks : Application.ActivityLifecycleCallbacks
private val TAG = "DemoActivityCallbacks"
const val DEMON_ACTIVITY_KEY = "Demo_Activity_Key"
val DEMON_FRAGMENT_KEY = "Demo_Fragment_Key"
//临时存储FragmentCallbacks
val callbackMap = mutableMapOf<String, DemoFragmentCallbacks>()
//临时存储DeMonActivityResult
val resultMap = mutableMapOf<String, DemoActivityResult<Intent, ActivityResult>>()
override fun onActivityCreated(activity: Activity, p1: Bundle?)
if (activity is FragmentActivity)
val mapKey: String = activity.javaClass.simpleName + System.currentTimeMillis()
Log.i(TAG, "onActivityCreated: mapKey=$mapKey")
//Fragment生命周期的自定义实现类
val fragmentCallbacks = DemoFragmentCallbacks()
callbackMap[mapKey] = fragmentCallbacks
//注册Fragment的生命周期
activity.supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentCallbacks, false)
//Activity注册获取ActivityResult对象
val result = DemoActivityResult(activity, ActivityResultContracts.StartActivityForResult())
//Activity生命周期持有Intent(getIntent),通过往Intent注入Key来获取ActivityResult对象(HashMap)
//ActivityResult可以用于跳转
activity.intent.putExtra(DEMON_ACTIVITY_KEY, mapKey)
resultMap[mapKey] = result
override fun onActivityDestroyed(activity: Activity)
if (activity is FragmentActivity)
val mapKey = activity.intent.getStringExtra(DEMON_ACTIVITY_KEY)
Log.i(TAG, "onActivityDestroyed: mapKey=$mapKey")
if (!mapKey.isNullOrEmpty())
//反注册Fragment的生命周期
callbackMap[mapKey]?.let
activity.supportFragmentManager.unregisterFragmentLifecycleCallbacks(it)
//移除Fragment的生命周期的回调
callbackMap.remove(mapKey)
//移除ActivityResult
resultMap.remove(mapKey)
override fun onActivityStarted(p0: Activity)
override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle)
override fun onActivityResumed(p0: Activity)
override fun onActivityPaused(p0: Activity)
override fun onActivityStopped(p0: Activity)
DemoFragmentCallbacks
类代码:
class DemoFragmentCallbacks : FragmentManager.FragmentLifecycleCallbacks()
private val TAG = "DeMonFragmentCallbacks"
override fun onFragmentAttached(fm: FragmentManager, fragment: Fragment, context: Context)
super.onFragmentAttached(fm, fragment, context)
val mapKey: String = fragment.javaClass.simpleName + System.currentTimeMillis()
Log.i(TAG, "onFragmentAttached: mapKey=$mapKey")
//Fragment注册
val result = DemoActivityResult(fragment, ActivityResultContracts.StartActivityForResult())
//Activity生命周期持有Intent(getIntent),通过往Intent注入Key来获取ActivityResult对象(HashMap)
//ActivityResult可以用于跳转
fragment.requireActivity().intent.putExtra(DEMON_FRAGMENT_KEY, mapKey)
DemoActivityCallbacks.resultMap[mapKey] = result
override fun onFragmentDetached(fm: FragmentManager, fragment: Fragment)
super.onFragmentDetached(fm, fragment)
val mapKey = fragment.requireActivity().intent.getStringExtra(DEMON_FRAGMENT_KEY)
Log.i(TAG, "onFragmentDetached: mapKey=$mapKey")
if (!mapKey.isNullOrEmpty())
DemoActivityCallbacks.resultMap.remove(mapKey)
DemoActivityResult
类的代码:
/**
* 注册和跳转的具体实现
* @author DeMon
* Created on 2022/3/1.
* E-mail idemon_liu@qq.com
* Desc:
*/
class DemoActivityResult<I, O>(caller: ActivityResultCaller, contract: ActivityResultContract<I, O>)
/**
* 直接点击返回键或者直接finish是否会触发回调
* 用于处理一些特殊情况:如只要返回就刷新等
* 注意此时回调返回的值或者ActivityResult#getData()应该为空,需要做好判空处理
*/
private var isNeedBack = false
private var launcher: ActivityResultLauncher<I>? = caller.registerForActivityResult(contract)
if (isNeedBack)
callback?.onActivityResult(it)
else
if (it != null)
if (it is ActivityResult)
if (it.resultCode == Activity.RESULT_OK) callback?.onActivityResult(it)
else
callback?.onActivityResult(it)
//回收单次的callback
callback = null
private var callback: ActivityResultCallback<O>? = null
@JvmOverloads
fun launch(input: I, isNeedBack: Boolean = false, callback: ActivityResultCallback<O>?)
this.callback = callback
this.isNeedBack = isNeedBack
launcher?.launch(input)
DemoResultHelper
类的代码:
/**
* 注册和具体跳转的调用处
* @author DeMon
* Created on 2022/3/2.
* E-mail idemon_liu@qq.com
* Desc: Activity Results API
*/
object DemoResultHelper
/**
* 初始化,注册ActivityLifecycleCallbacks
*/
@JvmStatic
fun init(@NonNull application: Application)
application.registerActivityLifecycleCallbacks(DemoActivityCallbacks)
/**
* 跳转使用的方法
* 获取在Activity生命周期自动注册的ActivityResult
* Activity中请使用此方法
*/
@JvmStatic
fun getActivityResult(@NonNull activity: FragmentActivity): DemoActivityResult<Intent, ActivityResult>?
activity.run
val mapKey = intent.getStringExtra(DemoActivityCallbacks.DEMON_ACTIVITY_KEY)
return if (!mapKey.isNullOrEmpty())
DemoActivityCallbacks.resultMap[mapKey]
else
null
/**
* 跳转使用的方法
* 获取在Fragment生命周期自动注册的ActivityResult
* Fragment中请使用此方法
*/
@JvmStatic
fun getActivityResult(@NonNull fragment: Fragment): DemoActivityResult<Intent, ActivityResult>?
fragment.requireActivity().run
val mapKey = intent.getStringExtra(DemoActivityCallbacks.DEMON_FRAGMENT_KEY)
return if (!mapKey.isNullOrEmpty())
DemoActivityCallbacks.resultMap[mapKey]
else
null
测试以上代码的跳转:
先在Application中注册
class App : Application()
override fun onCreate()
super.onCreate()
DemoResultHelper.init(this)
在MainActivity
中点击按钮跳转:
class MainActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var text = findViewById<TextView>(R.id.tv)
//点击事件
text.setOnClickListener
//获取提前注册好的ActivityResult
var result: DemoActivityResult<Intent, ActivityResult>? = DemoResultHelper.getActivityResult(this@MainActivity)
//跳转
result?.launch(Intent(this, SecondActivity::class.java), true)
//解析回调的数据
it?.data?.getStringExtra("value")?.let
value -> Log.d("MainActivity", value)
在SecondActivity
直接SetResult后finish:
class SecondActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
var intent = Intent().putExtra("value", "我是SecondActivity")
setResult(RESULT_OK, intent)
finish()
打印结果:
D/MainActivity: 我是SecondActivity
成功跳转并带回了数据。
五、基于Kotlin进一步的封装
以下代码来自于DeMon-ARA
增加了二个Kotlin的扩展类:
ActivityResultApi
是个ktx大杂烩,需要有点耐心才能完全看明白,代码如下:
/**
* @author DeMon
* Created on 2021/10/20.
* E-mail idemon_liu@qq.com
* Desc: ktx扩展
*/
/**
* Activity中获取DeMonActivityResult
*/
fun FragmentActivity.getActivityResult(): DeMonActivityResult<Intent, ActivityResult>?
val mapKey = intent.getStringExtra(DeMonActivityCallbacks.DEMON_ACTIVITY_KEY)
return if (!mapKey.isNullOrEmpty())
DeMonActivityCallbacks.resultMap[mapKey]
else
null
/**
* Fragment中获取DeMonActivityResult
*/
fun Fragment.getActivityResult(): DeMonActivityResult<Intent, ActivityResult>?
val mapKey = requireActivity().intent.getStringExtra(DeMonActivityCallbacks.DEMON_FRAGMENT_KEY)
return if (!mapKey.isNullOrEmpty())
DeMonActivityCallbacks.resultMap[mapKey]
else
null
/**
* Activity跳转并在回调用获取返回结果
* <pre>
* val intent = Intent(this@MainActivity,JavaActivity::class.java)
* forActivityResult(intent)
* val str = it?.getStringExtra("tag") ?: ""
* text.text = "跳转页面返回值:$str"
*
* </pre>
*
* @param isCanBack 直接点击返回键或者直接finish是否会触发回调
*/
inline fun FragmentActivity.forActivityResult(
data: Intent,
isCanBack: Boolean = false,
crossinline callback: ((result: Intent?) -> Unit)
)
getActivityResult()?.launch(data, isCanBack)
callback(it.data)
/**
* Activity跳转并在回调用获取返回结果
* <pre>
* forActivityResult<TestJumpActivity>(
* "tag" to TAG,
* "timestamp" to System.currentTimeMillis(),
* isCanBack = false
* )
* val str = it?.getStringExtra("tag") ?: ""
* text.text = "跳转页面返回值:$str"
*
* </pre>
*
* @param extras 可变参数Pair键值对
* @param isCanBack 直接点击返回键或者直接finish是否会触发回调
*/
inline fun <reified T : FragmentActivity> FragmentActivity.forActivityResult(
vararg extras: Pair<String, Any?>,
isCanBack: Boolean = false,
crossinline callback: ((result: Intent?) -> Unit)
)
val intent = pairIntent<T>(*extras)
forActivityResult(intent, isCanBack, callback)
/**
* Fragment中使用
* Activity跳转并在回调用获取返回结果
*
* @param isCanBack 直接点击返回键或者直接finish是否会触发回调
*/
inline fun Fragment.forActivityResult(
data: Intent,
isCanBack: Boolean = false,
crossinline callback: ((result: Intent?) -> Unit)
)
getActivityResult()?.launch(data, isCanBack)
callback(it.data)
/**
* Fragment中使用
* Activity跳转并在回调用获取返回结果
*
* @param extras 可变参数Pair键值对
* @param isCanBack 直接点击返回键或者直接finish是否会触发回调
*/
inline fun <reified T : FragmentActivity> Fragment.forActivityResult(
vararg extras: Pair<String, Any?>,
isCanBack: Boolean = false,
crossinline callback: ((result: Intent?) -> Unit)
)
val intent = pairIntent<T>(*extras)
forActivityResult(intent, isCanBack, callback)
/**
* 作用同[Activity.finish]
* <pre>
* finish(this, "Key" to "Value")
* </pre>
*
* @param params 可变参数Pair键值对
*/
fun FragmentActivity.finishResult(vararg params: Pair<String, Any?>) = run
setResult(Activity.RESULT_OK, Intent().putExtras(*params))
finish()
fun FragmentActivity.finishResult(intent: Intent) = run
setResult(Activity.RESULT_OK, intent)
finish()
/**
* 普通跳转
*/
fun Context.toActivity(intent: Intent, vararg extras: Pair<String, Any?>)
startActivity(intent.putExtras(*extras))
fun Fragment.toActivity(intent: Intent, vararg extras: Pair<String, Any?>)
requireActivity().startActivity(intent.putExtras(*extras))
inline fun <reified T : FragmentActivity> Context.toActivity(vararg extras: Pair<String, Any?>)
startActivity(Intent(this, T::class.java).putExtras(*extras))
inline fun <reified T : FragmentActivity> Fragment.toActivity(vararg extras: Pair<String, Any?>)
requireActivity().run
startActivity(Intent(this, T::class.java).putExtras(*extras))
/**
* 泛型Activity获取一个Intent实例的扩展
* <pre>
* pairIntent<ActResultActivity>(
* "tag" to TAG,
* "timestamp" to System.currentTimeMillis()
* )
* </pre>
*/
inline fun <reified T : FragmentActivity> Context.pairIntent(vararg extras: Pair<String, Any?>) = Intent(this, T::class.java).putExtras(*extras)
inline fun <reified T : FragmentActivity> Fragment.pairIntent(vararg extras: Pair<String, Any?>) = requireActivity().pairIntent<T>(*extras)
IntentExt
是Intent的扩展,主要是存取值,代码如下:
/**
* @author DeMon
* Created on 2020/7/22.
* E-mail idemon_liu@qq.com
* Desc:
*/
fun Intent.putExtras(vararg extras: Pair<String, Any?>): Intent
if (extras.isEmpty()) return this
extras.forEach (key, value) ->
value ?: return@forEach
when (value)
is Bundle -> this.putExtra(key, value)
is Boolean -> this.putExtra(key, value)
is BooleanArray -> this.putExtra(key, value)
is Byte -> this.putExtra(key, value)
is ByteArray -> this.putExtra(key, value)
is Char -> this.putExtra(key, value)
is CharArray -> this.putExtra(key, value)
is String -> this.putExtra(key, value)
is CharSequence -> this.putExtra(key, value)
is Double -> this.putExtra(key, value)
is DoubleArray -> this.putExtra(key, value)
is Float -> this.putExtra(key, value)
is FloatArray -> this.putExtra(key, value)
is Int -> this.putExtra(key, value)
is IntArray -> this.putExtra(key, value)
is Long -> this.putExtra(key, value)
is LongArray -> this.putExtra(key, value)
is Short -> this.putExtra(key, value)
is ShortArray -> this.putExtra(key, value)
is Parcelable -> this.putExtra(key, value)
is Serializable -> this.putExtra(key, value)
is Array<*> ->
@Suppress("UNCHECKED_CAST")
when
value.isArrayOf<String>() ->
this.putStringArrayListExtra(key, value as ArrayList<String?>)
value.isArrayOf<CharSequence>() ->
this.putCharSequenceArrayListExtra(key, value as ArrayList<CharSequence?>)
value.isArrayOf<Parcelable>() ->
this.putParcelableArrayListExtra(key, value as ArrayList<out Parcelable?>)
else ->
throw IllegalArgumentException("Not support $value type $value.javaClass..")
return this
class ActivityExtras<T>(private val extraName: String, private val defaultValue: T) : ReadWriteProperty<Activity, T>
/**
* getExtras字段对应的值
*/
private var extra: T? = null
override fun getValue(thisRef: Activity, property: KProperty<*>): T
// 如果extra不为空则返回extra
// 如果extra是空的,则判断intent的参数的值,如果值不为空,则将值赋予extra,并且返回
// 如果intent参数的值也为空,则返回defaultValue,并且将值赋予extra
return extra ?: thisRef.intent?.get<T>(extraName)?.also extra = it
?: defaultValue.also extra = it
override fun setValue(thisRef: Activity, property: KProperty<*>, value: T)
extra = value
/**
* 获取Intent参数,Fragment
* 示例同[ActivityExtras]
*/
class FragmentExtras<T>(private val extraName: String, private val defaultValue: T) : ReadWriteProperty<Fragment, T>
/**
* getExtras字段对应的值
*/
private var extra: T? = null
override fun getValue(thisRef: Fragment, property: KProperty<*>): T
// 如果extra不为空则返回extra
// 如果extra是空的,则判断intent的参数的值,如果值不为空,则将值赋予extra,并且返回
// 如果intent参数的值也为空,则返回defaultValue,并且将值赋予extra
return extra ?: thisRef.arguments?.get<T>(extraName)?.also extra = it
?: defaultValue.also extra = it
override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T)
extra = value
fun <T> extraFrag(extraName: String): FragmentExtras<T?> = FragmentExtras(extraName, null)
fun <T> extraFrag(extraName: String, defaultValue: T): FragmentExtras<T> = FragmentExtras(extraName, defaultValue)
fun <T> extraAct(extraName: String): ActivityExtras<T?> = ActivityExtras(extraName, null)
fun <T> extraAct(extraName: String, defaultValue: T): ActivityExtras<T> = ActivityExtras(extraName, defaultValue)
/**
* [Intent]的扩展方法,此方法可无视类型直接获取到对应值
* 如getStringExtra()、getIntExtra()、getSerializableExtra()等方法通通不用
* 可以直接通过此方法来获取到对应的值,例如:
* <pre>
* var mData: List<String>? = null
* mData = intent.get("Data")
* </pre>
* 而不用显式强制转型
*
* @param key 对应的Key
* @return 对应的Value
*/
fun <O> Intent?.get(key: String, defaultValue: O? = null) =
this?.internalMap()?.get(key) as? O ?: defaultValue
/**
* 作用同Intent.[get]
*/
fun <O> Bundle?.get(key: String, defaultValue: O? = null) =
this?.internalMap()?.get(key) as? O ?: defaultValue
/**
* 不报错执行
*/
inline fun <T, R> T.runSafely(block: (T) -> R) = try
block(this)
catch (e: Exception)
e.printStackTrace()
null
internal object IntentFieldMethod
private val bundleClass =
(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) BaseBundle::class else Bundle::class).java
private val mExtras: Field? by lazy
Intent::class.java.getDeclaredField("mExtras").also it.isAccessible = true
private val mMap: Field? by lazy
runSafely
bundleClass.getDeclaredField("mMap").also
it.isAccessible = true
private val unparcel: Method? by lazy
runSafely
bundleClass.getDeclaredMethod("unparcel").also
it.isAccessible = true
internal fun Intent.internalMap() = runSafely
mMap?.get((mExtras?.get(this) as? Bundle).also
it?.run unparcel?.invoke(this)
) as? Map<String, Any?>
internal fun Bundle.internalMap() = runSafely
unparcel?.invoke(it)
mMap?.get(it) as? Map<String, Any?>
进一步封装后的使用,还是上例中的二个Activity:
class MainActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var text = findViewById<TextView>(R.id.tv)
//点击事件
text.setOnClickListener
//跳转
forActivityResult(Intent(this@MainActivity, SecondActivity::class.java))
//解析回调的数据
it?.getStringExtra("value")?.let value ->
Log.d("MainActivity", value)
class SecondActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
//回传数据
finishResult("value" to "我是SecondActivity")
打印的日志:
D/MainActivity: 我是SecondActivity
跳转后成功拿到了回调数据。
参考了以下文章,表示感谢:
Android ActivityResultContract使用_xiangxiongfly-程序员ITS301
Android ActivityResultContract使用
再见!onActivityResult!你好,Activity Results API!
丢掉onActivityResult,探索Activity Results API极简方案
—个人学习笔记
文末
我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。
需要的直接点击文末小卡片可以领取哦!我免费分享给你,以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持,需要的自己领取)
Android学习PDF+架构视频+面试文档+源码笔记
部分资料一览:
- 330页PDF Android学习核心笔记(内含8大板块)
- Android学习的系统对应视频
- Android进阶的系统对应学习资料
- Android BAT大厂面试题(有解析)
领取地址
点击下方卡片免费领取。
以上是关于Android Activity Results API-的主要内容,如果未能解决你的问题,请参考以下文章
AppCompatActivity 中未解析的 Android Activity Results API 引用
再见!onActivityResult!你好,Activity Results API!
SCAN_RESULTS_AVAILABLE_ACTION 在 Android 6.0 中返回空列表
Android:Google Place API queryAutoComplete 在 API 17 - API 19 上提供 ZERO_RESULTS