轉(zhuǎn)自:Google 更新:Android開發(fā)者是時(shí)候丟掉 onActivityResult 了 !
但是這邊文章的相關(guān)api是alpha時(shí)的api,現(xiàn)在有點(diǎn)方法名已經(jīng)變了哦
在學(xué)習(xí)Jetpack相關(guān)庫的時(shí)候,在ComponentActivity(屬于:androidx.activity:activity:1.2.0-alpha05)里看到了ActivityResultRegistry這個(gè)類,剛好在知乎又看到過類似的文章,于是來學(xué)習(xí)一下.
但凡涉及到啟動新Activity,并獲取返回值,或者調(diào)用相機(jī)拍照,那一定會逃不過startActivityForResult 和 onActivityResult的,在有些業(yè)務(wù)情景中,這個(gè)模式很大的制約了代碼的設(shè)計(jì),谷歌在Activity 1.2.0-alpha02 和 Fragment 1.3.0-alpha02 開始,提供了新的Result API,讓我們能更加優(yōu)雅的處理onActivityResult,已到達(dá):減少樣板代碼,解耦,靈活,易測試的目的
1.傳統(tǒng)的startActivityForResult
最簡單的場景: MainActivity跳到SecondActivity,SecondActivity傳值回來,代碼如下:
const val TAG = "ActivityResultContracts"
class MainActivity : ComponentActivity(R.layout.activity_main) {
private val REQUEST_CODE = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
button2.setOnClickListener {
jump()
}
}
//<editor-fold desc="頁面跳轉(zhuǎn) 傳統(tǒng)寫法">
private fun jump() {
startActivityForResult(
Intent(this, SecondActivity::class.java),
REQUEST_CODE
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE) {
Toast.makeText(
this,
data?.getStringExtra("value") ?: "no return data",
Toast.LENGTH_SHORT
).show()
}
}
//</editor-fold>
}
class SecondActivity : AppCompatActivity(R.layout.activity_second) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
back.setOnClickListener {
setResult(Activity.RESULT_OK, Intent().putExtra("value", "I am back !"))
finish()
}
}
}
當(dāng)我們集成了androidx.activity:activity:1.2.0-alpha05后,startActivityForResult方法已經(jīng)被標(biāo)記為了@Deprecated
插播:
新的AppCompatActivity以及ComponentActivity,支持構(gòu)造函數(shù)中傳入layotuId了,不用再setContentView()了
基本的流程是:
- 定義一個(gè) REQUEST_CODE ,同一頁面有多個(gè)時(shí),保證不重復(fù)
- 調(diào)用 startActivityForResult
- 在 onActivityResult 中接收回調(diào),并判斷 requestCode,resultCode
而且上述代碼,都必須寫在視圖控制器(Activity/Fragment)里,也就造成了不容易測試等問題,
但是長久以來,我們也只有這一個(gè)選擇,所以也很少看到有人抱怨 onActivityResult。
Google 工程師為我們改進(jìn)了這一問題。就推出了新的 Activity Result API 。
2.Activity Result API
//<editor-fold desc="頁面跳轉(zhuǎn) 新的寫法">
private val startActivity =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
Log.d(TAG, "activityResult -> $it")
Log.d(TAG, "activityResult -> ${it.data?.getStringExtra("value")}")
textView.text = it.data?.getStringExtra("value")
}
fun jumpV2() {
startActivity.launch(Intent(this, SecondActivity::class.java))
}
//</editor-fold>
P.S.
registerForActivityResult方法,在之前較早的版本總是叫做prepareCall的
可以看到,主要就是2個(gè)方法: registerForActivityResult 和 launch
下面來詳細(xì)解讀一下 這兩個(gè)方法:
@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull ActivityResultContract<I, O> contract,
@NonNull ActivityResultCallback<O> callback) {
return registerForActivityResult(contract, mActivityResultRegistry, callback);
registerForActivityResult方法接收兩個(gè)參數(shù),ActivityResultContract 和 ActivityResultCallback ,返回值是 ActivityResultLauncher 。這幾個(gè)名字取得都很好,見名知意。
ActivityResultContract
ActivityResultContract 可以理解為一種協(xié)議,它是一個(gè)抽象類,提供了兩個(gè)能力,createIntent 和 parseResult 。
這兩個(gè)能力放到啟動 Activity 中就很好理解了,createIntent 負(fù)責(zé)為 startActivityForResult 提供 Intent ,parseResult 負(fù)責(zé)處理 onActivityResult 中獲取的結(jié)果。
上面的例子中,registerForActivityResult() 方法傳入的協(xié)議實(shí)現(xiàn)類是 StartActivityForResult 。
它是 ActivityResultContracts 類中的靜態(tài)內(nèi)部類。除了 StartActivityForResult 之外,官方還默認(rèn)提供了 RequestPermissions ,Dial ,RequestPermission ,TakePicture,它們都是 ActivityResultContract 的實(shí)現(xiàn)類,而且都位于ActivityResultContracts 這個(gè)final類里面
所以,除了可以簡化 startActivityForResult ,權(quán)限請求,撥打電話,拍照,都可以通過 Activity Result API 得到了簡化。
除了使用官方默認(rèn)提供的這些之外,我們還可以自己實(shí)現(xiàn) ActivityResultContract,在后面的代碼中會進(jìn)行演示。
ActivityResultCallback
public interface ActivityResultCallback<O> {
/**
* Called when result is available
*/
void onActivityResult(@SuppressLint("UnknownNullness") O result);
}
這個(gè)就很容易立即,是結(jié)果的回調(diào)接口
需要注意的是: registerForActivityResult()方法的泛型現(xiàn)在,這里的返回值result即泛型里的O (output)泛型,一定是類型安全的.
- StartActivityForResult --> ActivityResult
- TakePicture --> Bitmap
- Dial/RequestPermission --> Boolean
- RequestPermissions--> Map
具體的,可以去ActivityResultContracts類里面查看各個(gè)協(xié)議的具體參數(shù)類型
ActivityResultLauncher
registerForActivityResult的返回值,通過調(diào)用launch()方法,執(zhí)行業(yè)務(wù),最終會調(diào)用ActivityResultRegistry.register()方法,具體的源碼這里就不分析了,后面原博會單獨(dú)寫一篇源碼解析。
大致流程是: 自動生成 requestCode,注冊回調(diào)并存儲起來,綁定生命周期,當(dāng)收到 Lifecycle.Event.ON_DESTROY 事件時(shí),自動解綁注冊。
代替 startActivityForResult() 的就是 ActivityResultLauncher.launch()方法,最后會調(diào)用到 ActivityResultRegistry.invoke() 方法,如下所示:
Intent intent = contract.createIntent(activity, input);
if ("androidx.activity.result.contract.action.REQUEST_PERMISSIONS".equals(intent.getAction())) {
// handle request permissions
} else if ("androidx.activity.result.contract.action.INTENT_SENDER_REQUEST".equals(intent.getAction())) {
// handle intentSender
} else {
Bundle optionsBundle = null;
if (intent.hasExtra("androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE")) {
optionsBundle = intent.getBundleExtra("androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE");
} else if (options != null) {
optionsBundle = options.toBundle();
}
ActivityCompat.startActivityForResult(activity, intent, requestCode, optionsBundle);
}
可以看到,最終調(diào)用的是 ActivityCompat.startActivityForResult();
中間那一塊處理 request permissions 的我給掐掉了。這樣看起來看清晰。本來準(zhǔn)備單獨(dú)水一篇源碼解析的,這馬上核心源碼都講完了。
前面展示過了 startActivityForResult() ,再來展示一下權(quán)限請求。
private val permissionRequest =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
Log.d(TAG, "permissionResult -> $it")
textView.text = "CAMERA result -> $it"
}
fun jumpV3() {
permissionRequest.launch(Manifest.permission.CAMERA)
}
其余權(quán)限的請求,在這里就不展示了
3.如何自定義返回值 ?
前面提到的都是系統(tǒng)預(yù)置的協(xié)議(ActivityResultContract),輸入值,返回值類型也都是固定的。那么,如何返回自定義類型的值呢?其實(shí)也很簡單,自定義 ActivityResultContract,指明業(yè)務(wù)所需要的泛型就ok了
我們以TakePicturePreview為例,輸入值是Void 默認(rèn)返回值是Bitmap,現(xiàn)在假如我們需要返回Drawable
private class TakeDrawable(val context: Context) : ActivityResultContract<Void, Drawable>() {
override fun createIntent(context: Context, input: Void?): Intent {
return Intent(MediaStore.ACTION_IMAGE_CAPTURE)
}
override fun parseResult(resultCode: Int, intent: Intent?): Drawable? {
if (null == intent || resultCode != Activity.RESULT_OK)
return null
if (intent == null || resultCode != Activity.RESULT_OK) return null;
val bitmap = intent.getParcelableExtra<Bitmap>("data")
return BitmapDrawable(context.resources, bitmap);
}
}
private val picture =
registerForActivityResult(TakeDrawable(this)) {
Log.d(TAG, "picture -> $it")
imageView.setImageDrawable(it)
}
fun takePic() {
picture.launch(null);
}
大部分代碼參照TakePicturePreview的邏輯,只需要在parseResult里面,使用構(gòu)造方法傳入的Context,把bimap對象轉(zhuǎn)換為BitmapDrawable即可.
這樣就可以調(diào)用系統(tǒng)相機(jī)拍照并在結(jié)果回調(diào)中拿到 Drawable 對象了。
4.說好的解耦呢 ?
有時(shí)候我們可能會在結(jié)果回調(diào)中進(jìn)行一些復(fù)雜的處理操作,無論是之前的 onActivityResult() 還是上面內(nèi)部類的寫法,都是直接耦合在視圖控制器中的。
通過新的 Activity Result API,我們還可以單獨(dú)的類中處理結(jié)果回調(diào),真正做到 單一職責(zé) 。
其實(shí) Activity Result API 的核心操作都是通過 ActivityResultRegistry 來完成的,ComponentActivity 中包含了一個(gè) ActivityResultRegistry 對象
@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull ActivityResultContract<I, O> contract,
@NonNull ActivityResultCallback<O> callback) {
return registerForActivityResult(contract, mActivityResultRegistry, callback);
}
@NonNull
@Override
public 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);
}
/**
* Get the {@link ActivityResultRegistry} associated with this activity.
*
* @return the {@link ActivityResultRegistry}
*/
@NonNull
@Override
public final ActivityResultRegistry getActivityResultRegistry() {
return mActivityResultRegistry;
}
通過新的 Activity Result API,我們還可以單獨(dú)的類中處理結(jié)果回調(diào),真正做到 單一職責(zé) 。
其實(shí) Activity Result API 的核心操作都是通過 ActivityResultRegistry 來完成的,ComponentActivity 中包含了一個(gè) ActivityResultRegistry 對象
我們可以看到,registerForActivityResult(,,_)方法的第二個(gè)參數(shù),就是ActivityResultRegistry,并且ComponentActivity還提供了相應(yīng)的get方法暴露ActivityResultRegistry
現(xiàn)在要脫離 Activity 完成操作,就需要把ActivityResultRegistry 提供給外部,用來來進(jìn)行結(jié)果回調(diào)的注冊工作。同時(shí),我們一般通過實(shí)現(xiàn) LifecycleObserver 接口,綁定個(gè) LifecycleOwner 來進(jìn)行自動解綁注冊。
class TakePicPreviewObserver(
val activityResultRegistry: ActivityResultRegistry,
val onResult: (Bitmap) -> Unit
) : DefaultLifecycleObserver {
lateinit var takePhotoLauncher :ActivityResultLauncher<Void>
override fun onCreate(owner: LifecycleOwner) {
Log.d("TakePicObsver","onCreate")
takePhotoLauncher = activityResultRegistry.register(
"key",
ActivityResultContracts.TakePicturePreview(),
onResult
)
}
fun takePicture() {
takePhotoLauncher.launch(null)
}
override fun onDestroy(owner: LifecycleOwner) {
Log.d("TakePicObsver","onDestroy")
takePhotoLauncher.unregister()
}
}
插播:對DefaultLifycycleObserver的說明
DefaultLifecyclerObserver是'androidx.lifecycle:lifecycle-common-java8:2.3.0-alpha03'包里的,也是唯一個(gè)文件,
繼承自FullLifecyclerObserver(androidx.lifecycle:lifecycle-common:2.3.0-alpha03包內(nèi)的),
而且兩個(gè)類完全一樣,而且DefaultLifecyclerObserver
@SuppressWarnings("unused") 表示該屬性在方法或類中沒有使用。添加此注解可以去除屬性上的黃色警告?。?!
但是FullLifecycleObserver不是public的是package private(即:我們常說的包保護(hù))的,因此,
外部類是不能直接實(shí)現(xiàn)它的,所以需要借助DefaultLifecyclerObserver,來使用相應(yīng)方法
/**
* Callback interface for listening to {@link LifecycleOwner} state changes.
* <p>
* If you use Java 8 language, <b>always</b> prefer it over annotations.
* <p>
* If a class implements both this interface and {@link LifecycleEventObserver}, then
* methods of {@code DefaultLifecycleObserver} will be called first, and then followed by the call
* of {@link LifecycleEventObserver#onStateChanged(LifecycleOwner, Lifecycle.Event)}
* <p>
* If a class implements this interface and in the same time uses {@link OnLifecycleEvent}, then
* annotations will be ignored.
*/
上面的注釋也已經(jīng)指明,DefaultLifecyclerObserver是LifecycleOwner的一個(gè)回調(diào)接口,
如果使用java8 作為語言環(huán)境,那么使用這個(gè)DefaultLifecyclerObserver是優(yōu)于使用注解處理生命周期回調(diào)的
下面翻譯一下上方注釋的其余部分:
如果某個(gè)類實(shí)現(xiàn)了即實(shí)現(xiàn)了{(lán)@link DefaultLifecyclerObserver} 又實(shí)現(xiàn)了{(lán)@link LifecycleEventObserver}接口,
那么 {@link DefaultLifecyclerObserver}的實(shí)現(xiàn)方法,優(yōu)先調(diào)用,然后會回調(diào) {@link LifecycleEventObserver#onStateChanged(LifecycleOwner, Lifecycle.Event)}
方法
如果某個(gè)類實(shí)現(xiàn)了{(lán)@link DefaultLifecyclerObserver}接口,同時(shí),又使用了 {@link OnLifecycleEvent}的注解,
那么注解會被忽略,不會被調(diào)用
再附上 LifecycleObserver FullLifecycleObserver LifecycleEventObserver DefaultLifecyclerObserver
的層級關(guān)系
LifecycleObserver
?LifecycleEventObserver
?FullLifecycleObserver(package private)
?DefaultLifecyclerObserver
5.再玩點(diǎn)花出來 ?結(jié)合LiveData,進(jìn)行數(shù)據(jù)觀察
在 Github 上看到了一些花式寫法,和大家分享一下。
class TakePicPreviewLiveData(
val activityResultRegistry: ActivityResultRegistry
) : LiveData<Bitmap>() {
private lateinit var takePhotoLauncher: ActivityResultLauncher<Void>
override fun onActive() {
super.onActive()
Log.d("TakePicLiveData", "onActive")
takePhotoLauncher = activityResultRegistry.register(
"key",
ActivityResultContracts.TakePicturePreview()
) {
Log.d("TakePicLiveData", "onActive callback thread: ${Thread.currentThread().name}")
value = it
}
}
fun takePicture() {
takePhotoLauncher.launch(null)
}
override fun onInactive() {
super.onInactive()
Log.d("TakePicLiveData", "onInactive")
//takePhotoLauncher.unregister()
}
}
但是,又一個(gè)問題出現(xiàn)了,目前不知道如何解決:可以看到,在onInactive()這個(gè)LiveData的生命周期里,
我注釋掉了一句代碼takePhotoLauncher.unregister()
這么做的原因是,當(dāng)我們喚起相機(jī),進(jìn)行拍照時(shí),我們的Activity或Fragment作為LifecycleOwener,會進(jìn)入onPause()
生命周期,同時(shí)onInactive(),會在生命周期不是{@link Lifecycle.State#STARTED} 時(shí) {@link Lifecycle.State#RESUMED}
被回調(diào),也就是說,我們喚起相機(jī)拍照時(shí),onInactive會被回調(diào),如果我們在這里對ActivityResultLauncher進(jìn)行unregister(),
那么,我們就拿不到返回的結(jié)果了,也就無法通過LiveData進(jìn)一步把數(shù)據(jù)通知出去了.
對于這個(gè)問題,我暫時(shí)沒有想到好的解決辦法,
只有一個(gè)想法,就是我們在繼承LiveData的同時(shí),實(shí)現(xiàn)DefaultLifecycleObserver,就想之前寫的TakePicPreviewObserver一樣,
在DefaultLifecycleObserver#onDestroy 里進(jìn)行 unregister()
至此,關(guān)于Activity Result API的學(xué)習(xí),就暫告一段落,希望對你有所幫助...
本篇文字寫于: 2020/6/8