startActivityForResult的新解決辦法:ActivityResultContract

轉(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容