是時候丟掉 onActivityResult 了 !

爲什麼要丟掉 onActivityResult ?

如何啓動一個新的 Activity,並獲取返回值?
你的答案肯定是 startActivityForResultonActivityResult 。沒錯,一直以來,在某些場景下,例如啓動系統相機拍照,返回當前頁面後獲取照片數據,我們並沒有其他選擇,只能在 onActivityResult 中進行處理。

在最新的 Activity 1.2.0-alpha02 和 Fragment 1.3.0-alpha02 中,Google 提供了新的 Activity Result API, 讓我們可以更加優雅的處理 onActivityResult 。在介紹新 API 之前,我們不妨思考一下,爲什麼 Google 要丟掉 onActivityResult ?
減少樣板代碼,解耦 ,更易測試 。
舉個最簡單的場景,MainActivity 跳轉到 SecondActivitySecondActivity中按鈕觸發返回並傳值回來。SecondActivity 中的代碼很簡單:

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()
        }
    }
}

現在支持直接在 AppCompatActivity() 構造函數中傳入 layoutId 了,無需另外 setContentView() 。

回到 MainActivity 中,按照傳統的寫法,是這樣的:

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") ?: "")
        }
    }API
}
  • 定義一個 REQUEST_CODE ,同一頁面有多個時,保證不重複
  • 調用 startActivityForResult
  • 在 onActivityResult 中接收回調,並判斷 requestCode,resultCode

上面的邏輯中不乏重複的樣板代碼,且大多都耦合在視圖控制器(Activity/Fragment)中,也就造成了不易測試。細品一下,的確不是那麼的合理。

可能一直以來我們也只有這一個選擇,所以也很少看到有人抱怨 onActivityResult。精益求精的 Google 工程師爲我們改進了這一問題。

下面來看看如何使用最新的 Activity Result API 。

Activity Result API

    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))
    }

恩,就是這麼簡單。主要就兩個方法,prepareCall()launch()。拆解開來逐一分析。

    public <I, O> ActivityResultLauncher<I> prepareCall(
            @NonNull ActivityResultContract<I, O> contract,
            @NonNull ActivityResultCallback<O> callback) {
        return prepareCall(contraguanxict, mActivityResultRegistry, callback);
    }

prepare() 方法接收兩個參數,ActivityResultContractActivityResultCallback ,返回值是 ActivityResultLauncher。這幾個名字取得都很好,見名知意。

ActivityResultContract

ActivityResultContract 可以理解爲一種協議,它是一個抽象類,提供了兩個能力,createIntentparseResult 。這兩個能力放到啓動 Activity 中就很好理解了,createIntent 負責爲 startActivityForResult 提供 Intent ,parseResult 負責處理 onActivityResult 中獲取的結果。

上面的例子中,prepare() 方法傳入的協議實現類是 StartActivityForResult。它是 ActivityResultContracts 類中的靜態內部類。除了 StartActivityForResult 之外,官方還默認提供了 RequestPermissionsDialRequestPermissionTakePicture ,它們都是 ActivityResultContract 的實現類。

所以,除了可以簡化 startActivityForResult,權限請求,撥打電話,拍照,都可以通過 Activity Result API 得到了簡化。除了使用官方默認提供的這些之外,我們還可以自己實現 ActivityResultContract,在後面的代碼中會進行演示。

ActivityResultCallback

public interface ActivityResultCallback<O> {

    /**
     * Called when result is available
     */
    void onActivityResult(@SuppressLint("UnknownNullness") O result);
}

這個就比較簡單了。當回調結果可用時,通過該接口通知。需要注意的一點是,由於 prepare() 方法的泛型限制,這裏的返回值 result 一定是類型安全的。下表是系統內置協議和其返回值類型的對應關係。

Github

ActivityResultLauncher

prepare()方法的返回值。

prepare()方法其實會調用 ActivityResultRegistry.registerActivityResultCallback()方法,具體的源碼這裏就不分析了,後面會單獨寫一篇源碼解析。大致流程就是,自動生成 requestCode,註冊回調並存儲起來,綁定生命週期,當收到Lifecycle.Event.ON_DESTROY 事件時,自動解綁註冊。

        @Override
        public <I, O> void invoke(
                final int requestCode,
                @NonNull ActivityResultContract<I, O> contract,
                I input) {
            Intent intent = contract.createIntent(input);
            if (ACTION_REQUEST_PERMISSIONS.equals(intent.getAction())) {
           	// handle request permissions
            } else {
                ComponentActivity.this.startActivityForResult(intent, requestCode);
            }
        }

中間那一塊處理 request permissions 的我給掐掉了。這樣看起來看清晰。本來準備單獨水一篇源碼解析的,這馬上核心源碼都講完了。

前面展示過了 startActivityForResult(),再來展示一下權限請求。

    private val requestPermission = prepareCall(ActivityResultContracts.RequestPermission()){
        result -> toast("request permission $result")
    }

    requestPermission.launch(Manifest.permission.READ_PHONE_STATE)

撥打電話,拍照就不在這裏展示了。所有的示例代碼都已經上傳到了我的 Github 。

如何自定義返回值 ?

前面提到的都是系統預置的協議,返回值也都是固定的。那麼,如何返回自定義類型的值呢?其實也很簡單,自定義 ActivityResultContract就可以了。

我們以TakePicture爲例,默認的返回值是Bitmap ,現在我們讓它返回 Drawable

    private class TakePicDrawable : ActivityResultContract<Void,Drawable>(){

        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<Bitmap>("data")
            return BitmapDrawable(bitmap)
        }
    }

使用:

private val takePictureCustom = prepareCall(TakePicDrawable()) { result ->
    toast("take picture : $result")
}

pictureCustomBt.setOnClickListener {  takePictureCustom()}

這樣就可以調用系統相機拍照並在結果回調中拿到 Drawable 對象了。

說好的解耦呢 ?

有時候我們可能會在結果回調中進行一些複雜的處理操作,無論是之前的onActivityResult()還是上面的寫法,都是直接耦合在視圖控制器中的。通過新的 Activity Result API,我們還可以單獨的類中處理結果回調,真正做到 單一職責 。
其實 Activity Result API 的核心操作都是通過 ActivityResultRegistry 來完成的,ComponentActivity 中包含了一個 ActivityResultRegistry對象 :

  @NonNull
    public ActivityResultRegistry getActivityResultRegistry() {
        return mActivityResultRegistry;
   }

現在要脫離 Activity 完成操作,就需要外部提供一個 ActivityResultRegistry對象來進行結果回調的註冊工作。同時,我們一般通過實現 LifecycleObserver 接口,綁定一個 LifecycleOwner來進行自動解綁註冊。完整代碼如下:

class TakePhotoObserver(
    private val registry: ActivityResultRegistry,
    private val func: (Bitmap) -> Unit
) : DefaultLifecycleObserver {

    private lateinit var takePhotoLauncher: ActivityResultLauncher<Void?>

    override fun onCreate(owner: LifecycleOwner) {
        takePhotoLauncher = registry.registerActivityResultCallback(
            "key",
            ActivityResultContracts.TakePicture()
        ) { bitmap ->
            func(bitmap)
        }
    }

    fun takePicture(){
        takePhotoLauncher()
    }
}

再玩點花出來 ?

在 Github 上看到了一些花式寫法,和大家分享一下。

class TakePhotoLiveData(private val registry: ActivityResultRegistry) : LiveData<Bitmap>() {

    private lateinit var takePhotoLauncher : ActivityResultLauncher<Intent>

    override fun onActive() {
        super.onActive()
        registry.registerActivityResultCallback("key",
        ActivityResultContracts.TakePicture()){
            result -> value = result 
        }
    }

    override fun onInactive() {
        super.onInactive()
        takePhotoLauncher.dispose()
    }

}

通過綁定 LiveData 自動註冊和解綁。

最後

所有示例代碼已上傳至我的 Github ,地址 :
github.com/lulululbj/A… 。

特別分享字節跳動面試真題解析, 添加VX:q1607947758 即可免費獲取

  • Android高級架構視頻

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章