為什麼要丟掉 onActivityResult ?
如何啟動一個新的 Activity,並獲取返回值?
你的答案肯定是 startActivityForResult 和 onActivityResult 。
沒錯,一直以來,在某些場景下,例如啟動系統相機拍照,返回當前頁面後獲取照片數據,我們並沒有其他選擇,只能在 onActivityResult 中進行處理。
當遇到多個 Activity 跳轉的時候痛不欲生,以至於很多同學都放棄了寫 onActivityResult,改用 EventBus 來傳遞結果,終於等到這一天,Google 要對這個 API 下手了!
在最新的 Activity 1.2.0-alpha02 和 Fragment 1.3.0-alpha02 中,Google 提供了新的 Activity Result API, 讓我們可以更加優雅的處理 onActivityResult 。
在介紹新 API 之前,我們不妨思考一下,為什麼 Google 要丟掉 onActivityResult ?
減少樣板代碼,解耦 ,更易測試 。
舉個最簡單的場景,MainActivity 跳轉到 SecondActivity ,SecondActivity 中按鈕觸發返回並傳值回來。
SecondActivity 中的代碼很簡單:
<code>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() } } }/<code>
現在支持直接在 AppCompatActivity() 構造函數中傳入 layoutId 了,無需另外 setContentView() 。
回到 MainActivity 中,按照傳統的寫法,是這樣的:
<code>class MainActivity : AppCompatActivity(R.layout.activity_main) { private val REQUEST_CODE = 1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) jump.setOnClickListener { jump() } } 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(data?.getStringExtra("value") ?: "") } } }/<code>
- 定義一個 REQUEST_CODE ,同一頁面有多個時,保證不重複
- 調用 startActivityForResult
- 在 onActivityResult 中接收回調,並判斷 requestCode,resultCode
上面的邏輯中不乏重複的樣板代碼,且大多都耦合在視圖控制器(Activity/Fragment)中,也就造成了不易測試。
細品一下,的確不是那麼的合理。
可能一直以來我們也只有這一個選擇,所以也很少看到有人抱怨 onActivityResult。精益求精的 Google 工程師為我們改進了這一問題。
下面來看看如何使用最新的 Activity Result API 。
Activity Result API
<code>private val startActivity = prepareCall(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult? -> toast(result?.data?.getStringExtra("value") ?: "") } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) jump.setOnClickListener { jump() } } private fun jump() { startActivity.launch(Intent(this,SecondActivity::class.java)) }/<code>
恩,就是這麼簡單。主要就兩個方法,prepareCall() 和 launch() 。
拆解開來逐一分析。
<code>public ActivityResultLauncher prepareCall( @NonNull ActivityResultContract contract, @NonNull ActivityResultCallback callback) { return prepareCall(contraguanxict, mActivityResultRegistry, callback); } /<code>
prepare() 方法接收兩個參數,ActivityResultContract 和 ActivityResultCallback ,返回值是 ActivityResultLauncher 。這幾個名字取得都很好,見名知意。
ActivityResultContract
ActivityResultContract 可以理解為一種協議,它是一個抽象類,提供了兩個能力,createIntent 和 parseResult 。
這兩個能力放到啟動 Activity 中就很好理解了,createIntent 負責為 startActivityForResult 提供 Intent ,parseResult 負責處理 onActivityResult 中獲取的結果。
上面的例子中,prepare() 方法傳入的協議實現類是 StartActivityForResult 。
它是 ActivityResultContracts 類中的靜態內部類。除了 StartActivityForResult 之外,官方還默認提供了 RequestPermissions ,Dial ,RequestPermission ,TakePicture,它們都是 ActivityResultContract 的實現類。
所以,除了可以簡化 startActivityForResult ,權限請求,撥打電話,拍照,都可以通過 Activity Result API 得到了簡化。
除了使用官方默認提供的這些之外,我們還可以自己實現 ActivityResultContract,在後面的代碼中會進行演示。
ActivityResultCallback
<code>public interface ActivityResultCallback { /** * Called when result is available */ void onActivityResult(@SuppressLint("UnknownNullness") O result); }/<code>
這個就比較簡單了。當回調結果可用時,通過該接口通知。
需要注意的一點是,由於 prepare() 方法的泛型限制,這裡的返回值 result 一定是類型安全的。
下表是系統內置協議和其返回值類型的對應關係。
- StartActivityForResult -》ActivityResult
- TakePicture -》 Bitmap
- Dial/RequestPermission -》Boolean
- RequestPermissions -》 Map
ActivityResultLauncher
prepare() 方法的返回值。
prepare() 方法其實會調用 ActivityResultRegistry.registerActivityResultCallback() 方法,具體的源碼這裡就不分析了,後面會單獨寫一篇源碼解析。
大致流程就是,自動生成 requestCode,註冊回調並存儲起來,綁定生命週期,當收到 Lifecycle.Event.ON_DESTROY 事件時,自動解綁註冊。
代替 startActivityForResult() 的就是 ActivityResultLauncher.launch()方法,最後會調用到 ActivityResultRegistry.invoke() 方法,如下所示:
<code>@Override public void invoke( final int requestCode, @NonNull ActivityResultContract contract, I input) { Intent intent = contract.createIntent(input); if (ACTION_REQUEST_PERMISSIONS.equals(intent.getAction())) { // handle request permissions } else { ComponentActivity.this.startActivityForResult(intent, requestCode); } }/<code>
中間那一塊處理 request permissions 的我給掐掉了。這樣看起來看清晰。本來準備單獨水一篇源碼解析的,這馬上核心源碼都講完了。
前面展示過了 startActivityForResult() ,再來展示一下權限請求。
<code>private val requestPermission = prepareCall(ActivityResultContracts.RequestPermission()){ result -> toast("request permission $result") } requestPermission.launch(Manifest.permission.READ_PHONE_STATE)/<code>
撥打電話,拍照就不在這裡展示了。
如何自定義返回值 ?
前面提到的都是系統預置的協議,返回值也都是固定的。那麼,如何返回自定義類型的值呢?其實也很簡單,自定義 ActivityResultContract 就可以了。
我們以 TakePicture 為例,默認的返回值是 Bitmap ,現在我們讓它返回 Drawable 。
<code>private class TakePicDrawable : ActivityResultContract(){ override fun createIntent(input: Void?): Intent { return Intent(MediaStore.ACTION_IMAGE_CAPTURE) } override fun parseResult(resultCode: Int, intent: Intent?): Drawable? { if (resultCode != Activity.RESULT_OK || intent == null) return null val bitmap = intent.getParcelableExtra("data") return BitmapDrawable(bitmap) } }/<code>
使用:
<code>private val takePictureCustom = prepareCall(TakePicDrawable()) { result -> toast("take picture : $result") } pictureCustomBt.setOnClickListener { takePictureCustom()}/<code>
這樣就可以調用系統相機拍照並在結果回調中拿到 Drawable 對象了。
說好的解耦呢 ?
有時候我們可能會在結果回調中進行一些複雜的處理操作,無論是之前的 onActivityResult() 還是上面的寫法,都是直接耦合在視圖控制器中的。
通過新的 Activity Result API,我們還可以單獨的類中處理結果回調,真正做到 單一職責 。
其實 Activity Result API 的核心操作都是通過 ActivityResultRegistry 來完成的,ComponentActivity 中包含了一個 ActivityResultRegistry 對象 :
<code>@NonNull public ActivityResultRegistry getActivityResultRegistry() { return mActivityResultRegistry; }/<code>
現在要脫離 Activity 完成操作,就需要外部提供一個 ActivityResultRegistry 對象來進行結果回調的註冊工作。同時,我們一般通過實現 LifecycleObserver 接口,綁定個 LifecycleOwner 來進行自動解綁註冊。
完整代碼如下:
<code>class TakePhotoObserver( private val registry: ActivityResultRegistry, private val func: (Bitmap) -> Unit ) : DefaultLifecycleObserver { private lateinit var takePhotoLauncher: ActivityResultLauncher override fun onCreate(owner: LifecycleOwner) { takePhotoLauncher = registry.registerActivityResultCallback( "key", ActivityResultContracts.TakePicture() ) { bitmap -> func(bitmap) } } fun takePicture(){ takePhotoLauncher() } }/<code>
再玩點花出來 ?
在 Github 上看到了一些花式寫法,和大家分享一下。
<code>class TakePhotoLiveData(private val registry: ActivityResultRegistry) : LiveData() { private lateinit var takePhotoLauncher : ActivityResultLauncher override fun onActive() { super.onActive() registry.registerActivityResultCallback("key", ActivityResultContracts.TakePicture()){ result -> value = result } } override fun onInactive() { super.onInactive() takePhotoLauncher.dispose() } }/<code>
通過綁定 LiveData 自動註冊和解綁。
最後
不知道你如何看待最新的 Activity Result API ,歡迎在評論區留下你的意見。
我自己也是從事 Android 開發,從業這麼久,也積累了一些珍藏的資料,分享出來,希望可以幫助到大家提升進階
還分享一份由幾位大佬一起收錄整理的Android學習PDF+架構視頻+面試文檔+源碼筆記,高級架構技術進階腦圖、Android開發面試專題資料,高級進階架構資料
如果你有需要的話,可以私信我【進階】我發給你
喜歡本文的話,不妨給我點個小贊、評論區留言或者轉發支持一下唄~
關鍵字: startActivityForResult result API