Google 更新:Android開發者是時候丟掉 onActivityResult 了 !


Google 更新:Android開發者是時候丟掉 onActivityResult 了 !

為什麼要丟掉 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開發面試專題資料,高級進階架構資料

如果你有需要的話,可以私信我【進階】我發給你

喜歡本文的話,不妨給我點個小贊、評論區留言或者轉發支持一下唄~

Google 更新:Android開發者是時候丟掉 onActivityResult 了 !

Google 更新:Android開發者是時候丟掉 onActivityResult 了 !

Google 更新:Android開發者是時候丟掉 onActivityResult 了 !

Google 更新:Android開發者是時候丟掉 onActivityResult 了 !


分享到:


相關文章: