Jetpack:使用 ActivityResult 处理 Activity 之间的数据通信
目錄
前言?
ActivityResult使用?
ActivityResultContract
原理
總結(jié)
前言?
本文先介紹ActivityResult的基本使用,最后會(huì)通過源碼來探討背后的原理。
在Android中,我們?nèi)绻朐贏ctivity之間雙向傳遞數(shù)據(jù),需要使用startActivityForResult啟動(dòng),然后在onActivityResult中處理返回,另外申請(qǐng)權(quán)限也是類似的步驟。
但是這樣的處理方式會(huì)讓我們的代碼變得非常復(fù)雜,并且也無法保證在 Activity 發(fā)送或接收數(shù)據(jù)時(shí)參數(shù)的類型安全。
ActivityResult是Jetpack提供的一個(gè)功能,可以簡(jiǎn)化Activity直接的數(shù)據(jù)傳遞(包括權(quán)限申請(qǐng))。它通過提供類型安全的 contract (協(xié)定) 來簡(jiǎn)化處理來自 Activity 的數(shù)據(jù)。這些協(xié)定為一些常見操作 (比如: 拍照或請(qǐng)求權(quán)限) 定義了預(yù)期的輸入和輸出類型,除此之外您還能夠自定義協(xié)定來滿足不同場(chǎng)景的需求。
ActivityResult API 提供了一些組件用于注冊(cè) Activity 的處理結(jié)果、發(fā)起請(qǐng)求以及在系統(tǒng)返回結(jié)果后立即進(jìn)行相應(yīng)處理。您也可以在啟動(dòng) Activity 的地方使用一個(gè)獨(dú)立的類接收返回結(jié)果,這樣依然能夠保證類型安全。
ActivityResult使用?
使用ActivityResult先添加依賴:
dependencies {// 在 https://developer.android.google.cn/jetpack/androidx/releases/activity 獲得最新版本號(hào)def activity_version = "1.2.0"// 在 https://developer.android.google.cn/jetpack/androidx/releases/fragment 獲得最新版本號(hào)def fragment_version = "1.3.0"implementation "androidx.activity:activity:$activity_version"implementation "androidx.fragment:fragment:$fragment_version” }然后先看看最簡(jiǎn)單的使用方式,比如打開系統(tǒng)文件管理器選擇一個(gè)圖片,代碼如下:
val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->// 處理返回的 Uri }getContent.launch("image/*") //過濾圖片這里涉及幾個(gè)重要的類和函數(shù):
(1)registerForActivityResult:是ComponentActivity的一個(gè)函數(shù),注意這里的ComponentActivity是androidx.activity.ComponentActivity而不是androidx.core.app.ComponentActivity,androidx.core中的對(duì)應(yīng)類(截止1.3.0)還不支持這項(xiàng)功能。
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(@NonNull ActivityResultContract<I, O> contract,@NonNull ActivityResultCallback<O> callback)可以看到這個(gè)函數(shù)接收兩個(gè)參數(shù),分別是ActivityResultContract和回調(diào)ActivityResultCallback,ActivityResultContract是封裝啟動(dòng)所需要的各項(xiàng)參數(shù)(組成Intent,后面會(huì)細(xì)說)。函數(shù)返回ActivityResultLauncher,可以看到后面通過他的launch函數(shù)就可以啟動(dòng)activity。
(2)GetContent:ActivityResultContracts.GetContent類是一個(gè)繼承ActivityResultContract的具體實(shí)現(xiàn)類,封裝了調(diào)用系統(tǒng)文件管理器的功能。Jetpack提供了一些常用的ActivityResultContract,比如選取圖片,拍照等等,如果我們需要拉起自己的Activity,就需要自定義一個(gè)ActivityResultContract。
(3)launch:ActivityResultLauncher的函數(shù),啟動(dòng)activity,代替了之前的startActivity。
ActivityResultContract
下面我們來看看GetContent是如何實(shí)現(xiàn)的,代碼如下:
public static class GetContent extends ActivityResultContract<String, Uri> {@CallSuper@NonNull@Overridepublic Intent createIntent(@NonNull Context context, @NonNull String input) {return new Intent(Intent.ACTION_GET_CONTENT).addCategory(Intent.CATEGORY_OPENABLE).setType(input);}@Nullable@Overridepublic final SynchronousResult<Uri> getSynchronousResult(@NonNull Context context,@NonNull String input) {return null;}@Nullable@Overridepublic final Uri parseResult(int resultCode, @Nullable Intent intent) {if (intent == null || resultCode != Activity.RESULT_OK) return null;return intent.getData();}}可以看到實(shí)現(xiàn)來兩個(gè)關(guān)鍵的接口:
-
createIntent就是用于將傳入的參數(shù)封裝成intent,用于啟動(dòng)activity,GetContent的該函數(shù)就是封裝一個(gè)打開系統(tǒng)文件的Intent;
-
parseResult則是將返回的intent進(jìn)行解析,整理成我們需要的格式返回,GetContent中我們只需要返回的文件uri即可。
上面我們提到的回調(diào)ActivityResultCallback,它的參數(shù)就是parseResult的返回值。
所以如果我們自己的頁面間通信,則自定義ActivityResultContract即可,與GetContent類似,根據(jù)自己的需求實(shí)現(xiàn)這兩個(gè)函數(shù)即可,當(dāng)然還可以直接使用jetpack提供的StartActivityForResult(見下面)即可。
在Jetpack提供的已封裝好的ActivityResultContract有(都是ActivityResultContracts的子類):
(1)StartActivityForResult
public static final class StartActivityForResultextends ActivityResultContract<Intent, ActivityResult>最簡(jiǎn)單的,相當(dāng)于傳統(tǒng)方式的startActivityForResult,只不過將onActivityResult的幾個(gè)參數(shù)封裝成一個(gè)ActivityResult
public ActivityResult(int resultCode, @Nullable Intent data)(2)StartIntentSenderForResult
相當(dāng)于Activity.startIntentSender(IntentSender, Intent, int, int, int),與PendingIntent配合使用
(3)RequestMultiplePermissions
用于批量申請(qǐng)權(quán)限
public static final class RequestMultiplePermissionsextends ActivityResultContract<String[], java.util.Map<String, Boolean>>以Map形式返回每個(gè)權(quán)限的情況。
(4)RequestPermission
申請(qǐng)單個(gè)權(quán)限
public static final class RequestPermission extends ActivityResultContract<String, Boolean>通過這兩個(gè)來申請(qǐng)權(quán)限就可以很方便的進(jìn)行后續(xù)處理。
(5)TakePicturePreview
拉起拍照預(yù)覽
public static class TakePicturePreview extends ActivityResultContract<Void, Bitmap>直接返回bitmap數(shù)據(jù)。(跟傳統(tǒng)方式一樣,這個(gè)bitmap只是一個(gè)圖片預(yù)覽,因?yàn)閕ntent中不能傳輸過大的數(shù)據(jù))
注意雖然輸入是Void,但是執(zhí)行ActivityResultLauncher的lanch函數(shù)是還需要傳入一個(gè)null才行。
(6)TakePicture
拉起拍照
public static class TakePicture extends ActivityResultContract<Uri, Boolean>輸入圖片要保存的位置uri
(7)TakeVideo
錄制視頻
public static class TakeVideo extends ActivityResultContract<Uri, Bitmap>輸入視頻要保存的位置uri,返回視頻的縮略圖。
(8)PickContact
選取聯(lián)系人
public static final class PickContact extends ActivityResultContract<Void, Uri>(9)GetContent
獲取單個(gè)文件
public static class GetContent extends ActivityResultContract<String, Uri>輸入過濾類型,返回文件uri
(10)GetMultipleContents
文件多選
public static class GetMultipleContents extends ActivityResultContract<String, List<Uri>>同上
(11)OpenDocument
打開單個(gè)文檔(拉起的是系統(tǒng)文檔管理器)
@TargetApi(19)public static class OpenDocument extends ActivityResultContract<String[], Uri>對(duì)應(yīng)Intent.ACTION_OPEN_DOCUMENT,輸入的是類型過濾(如image/*),輸出uri
(12)OpenMultipleDocuments
打開多個(gè)文檔,與上面類似
(13)OpenDocumentTree
打開文檔tree,對(duì)應(yīng)Intent.ACTION_OPEN_DOCUMENT_TREE
(14)CreateDocument
新建一個(gè)文檔,對(duì)應(yīng)Intent.ACTION_CREATE_DOCUMENT
可以看到Android已經(jīng)將常用的功能都封裝了,基本可以滿足我們的開發(fā)使用。
原理
那么ActivityResult的原理是什么,為什么可以這樣實(shí)現(xiàn)?
launch應(yīng)該很好理解,就是通過ActivityResultContract的createIntent得到的intent去啟動(dòng)即可。
那么怎么實(shí)現(xiàn)result的回調(diào)的?
先看registerForActivityResult源碼:
@NonNull@Overridepublic final <I, O> ActivityResultLauncher<I> registerForActivityResult(@NonNull final ActivityResultContract<I, O> contract,@NonNull final ActivityResultRegistry registry,@NonNull final ActivityResultCallback<O> callback) {return registry.register("activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);}@NonNull@Overridepublic final <I, O> ActivityResultLauncher<I> registerForActivityResult(@NonNull ActivityResultContract<I, O> contract,@NonNull ActivityResultCallback<O> callback) {return registerForActivityResult(contract, mActivityResultRegistry, callback);}最終調(diào)用ActivityResultRegistry(mActivityResultRegistry)的register函數(shù):
@NonNullpublic final <I, O> ActivityResultLauncher<I> register(@NonNull final String key,@NonNull final LifecycleOwner lifecycleOwner,@NonNull final ActivityResultContract<I, O> contract,@NonNull final ActivityResultCallback<O> callback) {Lifecycle lifecycle = lifecycleOwner.getLifecycle();if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {throw new IllegalStateException("LifecycleOwner " + lifecycleOwner + " is "+ "attempting to register while current state is "+ lifecycle.getCurrentState() + ". LifecycleOwners must call register before "+ "they are STARTED.");}final int requestCode = registerKey(key);LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key);if (lifecycleContainer == null) {lifecycleContainer = new LifecycleContainer(lifecycle);}LifecycleEventObserver observer = new LifecycleEventObserver() {@Overridepublic void onStateChanged(@NonNull LifecycleOwner lifecycleOwner,@NonNull Lifecycle.Event event) {if (Lifecycle.Event.ON_START.equals(event)) {mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));if (mParsedPendingResults.containsKey(key)) {@SuppressWarnings("unchecked")final O parsedPendingResult = (O) mParsedPendingResults.get(key);mParsedPendingResults.remove(key);callback.onActivityResult(parsedPendingResult);}final ActivityResult pendingResult = mPendingResults.getParcelable(key);if (pendingResult != null) {mPendingResults.remove(key);callback.onActivityResult(contract.parseResult(pendingResult.getResultCode(),pendingResult.getData()));}} else if (Lifecycle.Event.ON_STOP.equals(event)) {mKeyToCallback.remove(key);} else if (Lifecycle.Event.ON_DESTROY.equals(event)) {unregister(key);}}};lifecycleContainer.addObserver(observer);mKeyToLifecycleContainers.put(key, lifecycleContainer);return new ActivityResultLauncher<I>() {@Overridepublic void launch(I input, @Nullable ActivityOptionsCompat options) {onLaunch(requestCode, contract, input, options);}@Overridepublic void unregister() {ActivityResultRegistry.this.unregister(key);}@NonNull@Overridepublic ActivityResultContract<I, ?> getContract() {return contract;}};}首先可以看到這個(gè)函數(shù)的調(diào)用是有時(shí)機(jī)限制的,需要在Activity的start生命周期之前(包含start)才可以,否則會(huì)拋出異常。
往下可以看到是通過lifecycle這個(gè)功能實(shí)現(xiàn)的,為啟動(dòng)的context(如activity)添加一個(gè)Observer,在Observer中發(fā)現(xiàn)是在onStart這個(gè)事件里處理的返回。但是實(shí)際上返回是在onActivityResult函數(shù)中,這里就需要關(guān)注mPendingResults,在ActivityResultRegistry中的doDispatch函數(shù)中為它賦予了數(shù)據(jù),而doDispatch則被dispatchResult函數(shù)調(diào)用。那么在那里執(zhí)行了dispatchResult?
@MainThreadpublic final boolean dispatchResult(int requestCode, int resultCode, @Nullable Intent data) {String key = mRcToKey.get(requestCode);if (key == null) {return false;}doDispatch(key, resultCode, data, mKeyToCallback.get(key));return true;}private <O> void doDispatch(String key, int resultCode, @Nullable Intent data,@Nullable CallbackAndContract<O> callbackAndContract) {if (callbackAndContract != null && callbackAndContract.mCallback != null) {ActivityResultCallback<O> callback = callbackAndContract.mCallback;ActivityResultContract<?, O> contract = callbackAndContract.mContract;callback.onActivityResult(contract.parseResult(resultCode, data));} else {// Remove any parsed pending resultmParsedPendingResults.remove(key);// And add these pending results in their placemPendingResults.putParcelable(key, new ActivityResult(resultCode, data));}}答案是在ComponentActivity中,ComponentActivity中持有一個(gè)ActivityResultRegistry的對(duì)象,即上面提到的mActivityResultRegistry。在ComponentActivity的onActivityResult和onRequestPermissionsResult中都會(huì)調(diào)用dispatchResult函數(shù)。這樣就實(shí)現(xiàn)了結(jié)果(包括申請(qǐng)權(quán)限)的回調(diào)。
@CallSuper@Override@Deprecatedprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) {super.onActivityResult(requestCode, resultCode, data);}}@CallSuper@Override@Deprecatedpublic void onRequestPermissionsResult(int requestCode,@NonNull String[] permissions,@NonNull int[] grantResults) {if (!mActivityResultRegistry.dispatchResult(requestCode, Activity.RESULT_OK, new Intent().putExtra(EXTRA_PERMISSIONS, permissions).putExtra(EXTRA_PERMISSION_GRANT_RESULTS, grantResults))) {if (Build.VERSION.SDK_INT >= 23) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);}}}總結(jié)
通過上面的介紹可以看到ActivityResult其實(shí)是對(duì)之前startActivityForResult模式的一次封裝,在簡(jiǎn)化使用的同時(shí)增加了安全性。但是我們需要提前注冊(cè)回調(diào),并生成ActivityResultLauncher對(duì)象,而且這一步需要ComponentActivity對(duì)象,而且有時(shí)機(jī)的限制,所以還不是特別靈活(尤其在權(quán)限管理這塊)。
?
總結(jié)
以上是生活随笔為你收集整理的Jetpack:使用 ActivityResult 处理 Activity 之间的数据通信的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android App Bundle:动
- 下一篇: Flutter:Navigator2.0