一、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的简单使用。
引入库
1
2
3implementation '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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public 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返回数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public 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)示例二:申请单个或多个权限
推荐用第三方请求权限的库,不推荐这样写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28//申请单个权限 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
。代码如下:
1
2<uses-permission android:name="android.permission.READ_CONTACTS"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66public 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()); } } }); } }
弹出权限申请窗口时点击了同意权限。日志打印结果为:
1
2
3D/MainActivity: 获取权限成功 D/MainActivity: 用户ID:1,用户姓名:张三,用户手机号:130 1234 5678 | 131 1234 5678 |
4)示例四:调用相机拍照
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//申请多个权限 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
文件配置
1
2
3
4
5
6
7
8
9
10
11
12<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
指定文件类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28<?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>
③工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230/** * 包含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)); } }
解决了适配问题,看如何获取文件:
获取单类型单文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//获取单类型单文件 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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/** * 自定义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的代码
1
2
3
4
5
6Intent intent = new Intent(); intent.putExtra("out","2"); setResult(1001,intent); finish();
在MainActivity中使用
1
2
3
4
5
6
7
8registerForActivityResult(new CustomActivityResultContract(), new ActivityResultCallback<String>() { @Override public void onActivityResult(String result) { //参数就是返回值 Log.d(TAG, "out:" + result); } }).launch(1); //输入值就是在这里传入
日志
1
2
38058-8058/com.abc.rxjava3 D/MainActivity: out:2
四、初级封装
1、在Activity/Fragment中自动注册
Activity生命周期监听,有个接口类Application.ActivityLifecycleCallbacks,先看下面的简单示例:
示例的目录:
首先看AppLifeCallback
类,实现Application.ActivityLifecycleCallbacks
接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class 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
方法:
1
2
3
4
5
6
7
8
9
10
11object AppHelper { /** * 注冊Application的生命周期 */ fun init(application: Application) { //注册自定义的生命周期 application.registerActivityLifecycleCallbacks(AppLifeCallback()) } }
在App
中完成init
初始化操作:
1
2
3
4
5
6
7
8
9class App :Application() { override fun onCreate() { super.onCreate() //注册生命周期 AppHelper.init(this) } }
MainActivity
和SecondActivity
的代码比较简单:
1
2
3
4
5
6
7
8
9
10
11
12class 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)); } }
1
2
3
4
5
6
7
8
9
10class SecondActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_second) //finish finish() } }
打印日志:
1
2
3
4
5D/AppHelper: onActivityCreated:MainActivity D/AppHelper: onActivityCreated:SecondActivity D/AppHelper: onActivityDestroyed:SecondActivity
可以发现实现了Application.ActivityLifecycleCallbacks
,所有Activity的生命周期都会在这里回调,那么就可以在这里完成Activity跳转前的注册。
2、初级封装
以下代码来自于DeMon-ARA,详细注释都写在代码里
目录结构:
DemoActivityCallbacks
类代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54object 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
类代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class 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
类的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43/** * 注册和跳转的具体实现 * @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
类的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55/** * 注册和具体跳转的调用处 * @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中注册
1
2
3
4
5
6
7
8class App : Application() { override fun onCreate() { super.onCreate() DemoResultHelper.init(this) } }
在MainActivity
中点击按钮跳转:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class 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:
1
2
3
4
5
6
7
8
9
10
11
12
13class 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() } }
打印结果:
1
2
3D/MainActivity: 我是SecondActivity
成功跳转并带回了数据。
五、基于Kotlin进一步的封装
以下代码来自于DeMon-ARA
增加了二个Kotlin的扩展类:
ActivityResultApi
是个ktx大杂烩,需要有点耐心才能完全看明白,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165/** * @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的扩展,主要是存取值,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174/** * @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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class 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) } } } } }
1
2
3
4
5
6
7
8
9
10
11
12class SecondActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_second) //回传数据 finishResult("value" to "我是SecondActivity") } }
打印的日志:
1
2
3D/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-的全部内容,更多相关Android内容请搜索靠谱客的其他文章。
发表评论 取消回复