轉(zhuǎn)自:Google 更新:Android開發(fā)者是時候丟掉 onActivityResult 了 庄蹋!
但是這邊文章的相關api是alpha時的api,現(xiàn)在有點方法名已經(jīng)變了哦
在學習Jetpack相關庫的時候,在ComponentActivity(屬于:androidx.activity:activity:1.2.0-alpha05)里看到了ActivityResultRegistry這個類,剛好在知乎又看到過類似的文章,于是來學習一下.
但凡涉及到啟動新Activity,并獲取返回值,或者調(diào)用相機拍照,那一定會逃不過startActivityForResult 和 onActivityResult的,在有些業(yè)務情景中,這個模式很大的制約了代碼的設計,谷歌在Activity 1.2.0-alpha02 和 Fragment 1.3.0-alpha02 開始,提供了新的Result API,讓我們能更加優(yōu)雅的處理onActivityResult,已到達:減少樣板代碼,解耦,靈活,易測試的目的
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()
}
}
}
當我們集成了androidx.activity:activity:1.2.0-alpha05
后,startActivityForResult
方法已經(jīng)被標記為了@Deprecated
插播:
新的AppCompatActivity以及ComponentActivity,支持構(gòu)造函數(shù)中傳入layotuId了,不用再setContentView()了
基本的流程是:
- 定義一個 REQUEST_CODE ,同一頁面有多個時殖妇,保證不重復
- 調(diào)用 startActivityForResult
- 在 onActivityResult 中接收回調(diào)恩敌,并判斷 requestCode狼牺,resultCode
而且上述代碼,都必須寫在視圖控制器(Activity/Fragment)里,也就造成了不容易測試等問題,
但是長久以來,我們也只有這一個選擇,所以也很少看到有人抱怨 onActivityResult词裤。
Google 工程師為我們改進了這一問題演训。就推出了新的 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個方法: registerForActivityResult 和 launch
下面來詳細解讀一下 這兩個方法:
@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull ActivityResultContract<I, O> contract,
@NonNull ActivityResultCallback<O> callback) {
return registerForActivityResult(contract, mActivityResultRegistry, callback);
registerForActivityResult方法接收兩個參數(shù),ActivityResultContract 和 ActivityResultCallback 样悟,返回值是 ActivityResultLauncher 。這幾個名字取得都很好,見名知意窟她。
ActivityResultContract
ActivityResultContract 可以理解為一種協(xié)議陈症,它是一個抽象類,提供了兩個能力震糖,createIntent 和 parseResult 录肯。
這兩個能力放到啟動 Activity 中就很好理解了,createIntent 負責為 startActivityForResult 提供 Intent 吊说,parseResult 負責處理 onActivityResult 中獲取的結(jié)果论咏。
上面的例子中,registerForActivityResult() 方法傳入的協(xié)議實現(xiàn)類是 StartActivityForResult 颁井。
它是 ActivityResultContracts 類中的靜態(tài)內(nèi)部類厅贪。除了 StartActivityForResult 之外,官方還默認提供了 RequestPermissions 雅宾,Dial 养涮,RequestPermission ,TakePicture眉抬,它們都是 ActivityResultContract 的實現(xiàn)類,而且都位于ActivityResultContracts 這個final類里面
所以贯吓,除了可以簡化 startActivityForResult ,權(quán)限請求蜀变,撥打電話悄谐,拍照,都可以通過 Activity Result API 得到了簡化库北。
除了使用官方默認提供的這些之外爬舰,我們還可以自己實現(xiàn) ActivityResultContract,在后面的代碼中會進行演示贤惯。
ActivityResultCallback
public interface ActivityResultCallback<O> {
/**
* Called when result is available
*/
void onActivityResult(@SuppressLint("UnknownNullness") O result);
}
這個就很容易立即,是結(jié)果的回調(diào)接口
需要注意的是: registerForActivityResult()方法的泛型現(xiàn)在,這里的返回值result即泛型里的O (output)泛型,一定是類型安全的.
- StartActivityForResult --> ActivityResult
- TakePicture --> Bitmap
- Dial/RequestPermission --> Boolean
- RequestPermissions--> Map
具體的,可以去ActivityResultContracts類里面查看各個協(xié)議的具體參數(shù)類型
ActivityResultLauncher
registerForActivityResult的返回值,通過調(diào)用launch()方法,執(zhí)行業(yè)務,最終會調(diào)用ActivityResultRegistry.register()方法,具體的源碼這里就不分析了洼专,后面原博會單獨寫一篇源碼解析。
大致流程是: 自動生成 requestCode孵构,注冊回調(diào)并存儲起來屁商,綁定生命周期,當收到 Lifecycle.Event.ON_DESTROY 事件時颈墅,自動解綁注冊蜡镶。
代替 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 的我給掐掉了官还。這樣看起來看清晰。本來準備單獨水一篇源碼解析的毒坛,這馬上核心源碼都講完了望伦。
前面展示過了 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)預置的協(xié)議(ActivityResultContract)腿箩,輸入值,返回值類型也都是固定的。那么劣摇,如何返回自定義類型的值呢珠移?其實也很簡單,自定義 ActivityResultContract,指明業(yè)務所需要的泛型就ok了
我們以TakePicturePreview為例,輸入值是Void 默認返回值是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)相機拍照并在結(jié)果回調(diào)中拿到 Drawable 對象了末融。
4.說好的解耦呢 钧惧?
有時候我們可能會在結(jié)果回調(diào)中進行一些復雜的處理操作,無論是之前的 onActivityResult() 還是上面內(nèi)部類的寫法勾习,都是直接耦合在視圖控制器中的浓瞪。
通過新的 Activity Result API,我們還可以單獨的類中處理結(jié)果回調(diào)语卤,真正做到 單一職責 追逮。
其實 Activity Result API 的核心操作都是通過 ActivityResultRegistry 來完成的,ComponentActivity 中包含了一個 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粹舵,我們還可以單獨的類中處理結(jié)果回調(diào)钮孵,真正做到 單一職責 。
其實 Activity Result API 的核心操作都是通過 ActivityResultRegistry 來完成的眼滤,ComponentActivity 中包含了一個 ActivityResultRegistry 對象
我們可以看到,registerForActivityResult(,,_)方法的第二個參數(shù),就是ActivityResultRegistry,并且ComponentActivity還提供了相應的get方法暴露ActivityResultRegistry
現(xiàn)在要脫離 Activity 完成操作巴席,就需要把ActivityResultRegistry 提供給外部,用來來進行結(jié)果回調(diào)的注冊工作。同時诅需,我們一般通過實現(xiàn) LifecycleObserver 接口漾唉,綁定個 LifecycleOwner 來進行自動解綁注冊。
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'
包里的,也是唯一個文件,
繼承自FullLifecyclerObserver
(androidx.lifecycle:lifecycle-common:2.3.0-alpha03
包內(nèi)的),
而且兩個類完全一樣,而且DefaultLifecyclerObserver
@SuppressWarnings("unused")
表示該屬性在方法或類中沒有使用堰塌。添加此注解可以去除屬性上的黃色警告U孕獭!场刑!
但是FullLifecycleObserver
不是public的是package private(即:我們常說的包保護)的,因此,
外部類是不能直接實現(xiàn)它的,所以需要借助DefaultLifecyclerObserver
,來使用相應方法
/**
* 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的一個回調(diào)接口,
如果使用java8 作為語言環(huán)境,那么使用這個DefaultLifecyclerObserver
是優(yōu)于使用注解處理生命周期回調(diào)的
下面翻譯一下上方注釋的其余部分:
如果某個類實現(xiàn)了即實現(xiàn)了{@link DefaultLifecyclerObserver} 又實現(xiàn)了{@link LifecycleEventObserver}接口,
那么 {@link DefaultLifecyclerObserver}的實現(xiàn)方法,優(yōu)先調(diào)用,然后會回調(diào) {@link LifecycleEventObserver#onStateChanged(LifecycleOwner, Lifecycle.Event)}
方法
如果某個類實現(xiàn)了{@link DefaultLifecyclerObserver}接口,同時,又使用了 {@link OnLifecycleEvent}的注解,
那么注解會被忽略,不會被調(diào)用
再附上 LifecycleObserver FullLifecycleObserver LifecycleEventObserver DefaultLifecyclerObserver
的層級關系
LifecycleObserver
?LifecycleEventObserver
?FullLifecycleObserver(package private)
?DefaultLifecyclerObserver
5.再玩點花出來 般此?結(jié)合LiveData,進行數(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()
}
}
但是,又一個問題出現(xiàn)了,目前不知道如何解決:可以看到,在onInactive()這個LiveData的生命周期里,
我注釋掉了一句代碼takePhotoLauncher.unregister()
這么做的原因是,當我們喚起相機,進行拍照時,我們的Activity或Fragment作為LifecycleOwener,會進入onPause()
生命周期,同時onInactive(),會在生命周期不是{@link Lifecycle.State#STARTED} 時 {@link Lifecycle.State#RESUMED}
被回調(diào),也就是說,我們喚起相機拍照時,onInactive會被回調(diào),如果我們在這里對ActivityResultLauncher進行unregister(),
那么,我們就拿不到返回的結(jié)果了,也就無法通過LiveData進一步把數(shù)據(jù)通知出去了.
對于這個問題,我暫時沒有想到好的解決辦法,
只有一個想法,就是我們在繼承LiveData的同時,實現(xiàn)DefaultLifecycleObserver,就想之前寫的TakePicPreviewObserver一樣,
在DefaultLifecycleObserver#onDestroy 里進行 unregister()
至此,關于Activity Result API的學習,就暫告一段落,希望對你有所幫助...
本篇文字寫于: 2020/6/8