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 中两个重要的组件:ActivityResultContractActivityResultLauncher

ActivityResultContract: 协议,它定义了如何传递数据和如何处理返回的数据。ActivityResultContract是一个抽象类,你需要继承它来创建自己的协议,每个 ActivityResultContract 都需要定义输入和输出类型,如果您不需要任何输入,可使用 Void(在 Kotlin 中,使用 Void? 或 Unit)作为输入类型。

ActivityResultLauncher: 启动器,调用ActivityResultLauncherlaunch方法来启动页面跳转,作用相当于原来的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中,除了StartActivityForResultRequestMultiplePermissions之外,基本都是处理的与其他APP交互,返回数据的场景,比如,拍照,选择图片,选择联系人,打开文档等等。使用最多的就是StartActivityForResultRequestMultiplePermissions了。

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)
    
 

MainActivitySecondActivity的代码比较简单:

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使用

registerForActivityResult()

再见!onActivityResult!你好,Activity Results API!

Uri与真实路径转换File-全适配

丢掉onActivityResult,探索Activity Results API极简方案

DeMon-ARA

—个人学习笔记

文末

我总结了一些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

android中怎么kill掉一个activity?

android 监听每个activity的状态