Android Result Api不能在生命周期onStart及之后注冊的解決辦法

現(xiàn)在Activity的startActivityForResult廢棄了,Google建議我們使用Activity Result Api.

然而這個Activity Result Api坑倒是不少

比如在使用

    def activity_version = "1.2.2"
    implementation "androidx.activity:activity:$activity_version"
    implementation "androidx.activity:activity-ktx:$activity_version"
    implementation "androidx.fragment:fragment:$activity_version"

可能會導(dǎo)致

java.lang.IllegalArgumentException: Can only use lower 16 bits for requestCode

要將版本升到1.3.0之后才行

我尋思著干脆將版本直接升到最高吧1.5.0好了,然而升到1.5.0又會導(dǎo)致構(gòu)建版本太低的項(xiàng)目中報錯......

無語......

算了就用1.3.0了.

回歸我們的主題,Android Result Api必須在Activity 生命周期onStart之前注冊,也就是基本上我們必須在onCreate的時候注冊

    val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
        Log.d(TAG, "get activity result")
    }

然后在需要使用的時候再launch

    launcher.launch(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))

如果我們不在onCreate中注冊,那么我們launch的時候就會報如下錯誤然后崩掉

java.lang.IllegalStateException: LifecycleOwner com.yxf.extensions.MainActivity@f8e47f4 is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.

這操作就離譜,這和原本的startActivityForResult,然后在onActivityResult里監(jiān)聽有太大區(qū)別嗎?

這特么不還是得分成兩段代碼,一點(diǎn)都不優(yōu)雅好吧.


那么這么不優(yōu)雅的問題有辦法解決嗎?

我們先分析下為什么會崩吧,先看看registerForActivityResult都做了什么

    @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);
    }
    @NonNull
    public final <I, O> ActivityResultLauncher<I> register(
            @NonNull final String key,
            @NonNull final LifecycleOwner lifecycleOwner,
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final ActivityResultCallback<O> callback) {

        Lifecycle lifecycle = lifecycleOwner.getLifecycle();

        if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
            throw new IllegalStateException("LifecycleOwner " + lifecycleOwner + " is "
                    + "attempting to register while current state is "
                    + lifecycle.getCurrentState() + ". LifecycleOwners must call register before "
                    + "they are STARTED.");
        }

        final int requestCode = registerKey(key);
        LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key);
        if (lifecycleContainer == null) {
            lifecycleContainer = new LifecycleContainer(lifecycle);
        }
        LifecycleEventObserver observer = new LifecycleEventObserver() {
            @Override
            public void onStateChanged(
                    @NonNull LifecycleOwner lifecycleOwner,
                    @NonNull Lifecycle.Event event) {
                if (Lifecycle.Event.ON_START.equals(event)) {
                    mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));
                    //......
                } else if (Lifecycle.Event.ON_STOP.equals(event)) {
                    mKeyToCallback.remove(key);
                } else if (Lifecycle.Event.ON_DESTROY.equals(event)) {
                    unregister(key);
                }
            }
        };
        lifecycleContainer.addObserver(observer);
        //......
        return new ActivityResultLauncher<I>() {
            //......
        };
    }

可以看到在register的時候,在最前面就先判斷了當(dāng)前生命周期,如果生命周期不在STARTED之前就會直接拋異常......

然而講道理,它非要我們在onCreate里注冊的原因不過是它需要在Activity生命周期為onStart的時候執(zhí)行這么一個操作

mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));

主要是存儲一個回調(diào)接口.

當(dāng)然在onStart時還包含了mParsedPendingResultsmPendingResults的處理,不過這兩個處理主要如果Activity 觸發(fā)了onStop,則將結(jié)果回調(diào)從onActivityResult延時到Activity重新onStart的時候觸發(fā).不過這里我不是很理解延遲回調(diào)到onStart的意義是什么,如果有知道的大佬希望可以指點(diǎn)一二.

回到主題,也就是說,為了以上操作,直接就讓我們不能在onStart之后注冊了,感覺就挺離譜的......

不過話說回來ActivityResultRegistry源碼中其實(shí)也考慮了不用生命周期的注冊方式,注冊過程不會拋異常,ActivityResultRegistry的第二個注冊方法如下

    @NonNull
    public final <I, O> ActivityResultLauncher<I> register(
            @NonNull final String key,
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final ActivityResultCallback<O> callback) {
        final int requestCode = registerKey(key);
        mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));
        //......
        return new ActivityResultLauncher<I>() {
            //......
        };
    }

這個方法挺好的,有什么需要處理的直接就處理了.不過因?yàn)闆]有監(jiān)聽Activity的生命周期變化,如果不在不需要的時候解除注冊可能會導(dǎo)致Activity內(nèi)存泄漏.

我們可以嘗試借助第二個方法來實(shí)現(xiàn)Result Api的即時注冊和即時使用,方法如下

private val nextLocalRequestCode = AtomicInteger()

fun <I, O> FragmentActivity.startContractForResult(
    contract: ActivityResultContract<I, O>,
    input: I,
    callback: ActivityResultCallback<O>
) {
    val key = "activity_rq_for_result#${nextLocalRequestCode.getAndIncrement()}"
    val registry = activityResultRegistry
    var launcher: ActivityResultLauncher<I>? = null
    val observer = object : LifecycleEventObserver {
        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
            if (Lifecycle.Event.ON_DESTROY == event) {
                launcher?.unregister()
                lifecycle.removeObserver(this)
            }
        }
    }
    lifecycle.addObserver(observer)
    val newCallback = ActivityResultCallback<O> {
        launcher?.unregister()
        lifecycle.removeObserver(observer)
        callback.onActivityResult(it)
    }
    launcher = registry.register(key, contract, newCallback)
    launcher.launch(input)
}

通過以上代碼我們就能優(yōu)雅的實(shí)現(xiàn)在onStart之后依然能注冊Result Api, 而且將注冊和launch的過程結(jié)合提高代碼的緊湊性.

    activity.startContractForResult(ActivityResultContracts.StartActivityForResult(), Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) {
        if (it.resultCode == Activity.RESULT_OK) {
            Log.d(TAG, "get activity result successfully")
        } else {
            Log.w(TAG, "get activity result failed")
        }
    }

如果你像我一樣依然還在使用RxJava,為此我覺得還可以更優(yōu)雅的將此裝封到RxJava擴(kuò)展中,代碼如下

internal class ObservableStartContractForResult<I, O>(
    private var fragmentActivity: FragmentActivity?,
    private val contract: ActivityResultContract<I, O>,
    private val input: I
) : Observable<O>() {

    override fun subscribeActual(observer: Observer<in O>) {
        observer.onSubscribe(StartContractForResultObserver(observer, fragmentActivity, contract, input))
        fragmentActivity = null
    }

    private inner class StartContractForResultObserver(
        private val downStream: Observer<in O>,
        private var activity: FragmentActivity?,
        private val contract: ActivityResultContract<I, O>,
        private val input: I
    ) : AtomicReference<Disposable>(), Disposable {


        private var launcher: ActivityResultLauncher<I>? = null

        val observer = LifecycleEventObserver { _, event ->
            if (Lifecycle.Event.ON_DESTROY == event) {
                releaseAll()
                downStream.onError(LifecycleDestroyedException())
            }
        }


        init {
            runOnMainThreadSync {
                val key = "activity_rq_for_result#${nextLocalRequestCode.getAndIncrement()}"
                val registry = activity!!.activityResultRegistry
                activity!!.lifecycle.addObserver(observer)
                val newCallback = ActivityResultCallback<O> {
                    releaseAll()
                    downStream.onNext(it)
                    downStream.onComplete()
                }
                launcher = registry.register(key, contract, newCallback)
                launcher?.launch(input)
            }
        }

        private fun releaseAll() {
            releaseObserver()
            releaseLauncher()
            releaseActivity()
        }

        private fun releaseActivity() {
            activity = null
        }

        private fun releaseLauncher() {
            launcher?.unregister()
            launcher = null
        }

        private fun releaseObserver() {
            activity?.lifecycle?.removeObserver(observer)
        }


        override fun dispose() {
            if (isDisposed) {
                return
            }
            DisposableHelper.dispose(this)
            runOnMainThread{
                releaseAll()
            }
        }

        override fun isDisposed(): Boolean {
            return get() == DisposableHelper.DISPOSED
        }

    }

}
fun <I, O> FragmentActivity.rxStartContractForResult(contract: ActivityResultContract<I, O>, input: I): Observable<O> {
    return RxJavaPlugins.onAssembly(ObservableStartContractForResult(this, contract, input))
}

如此我們便可以配合RxJava實(shí)現(xiàn)極致的優(yōu)雅.

    activity.rxStartContractForResult(ActivityResultContracts.StartActivityForResult(), Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
        .subscribe {
            Log.d(TAG, "get activity result")
        }

具體源碼參見 https://github.com/dqh147258/RxAndroidExtensions


當(dāng)然,其實(shí)以上功能吧,long long ago,就有大佬們通過Fragment實(shí)現(xiàn)了......

不過畢竟通過一個不可見的Fragment還是太騷了,也會影響一些關(guān)于Fragment數(shù)量的邏輯判斷

emmmmm, that's all.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末芜果,一起剝皮案震驚了整個濱河市常柄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌医咨,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件误续,死亡現(xiàn)場離奇詭異飘哨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)尼啡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門暂衡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人崖瞭,你說我怎么就攤上這事狂巢。” “怎么了书聚?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵唧领,是天一觀的道長。 經(jīng)常有香客問我雌续,道長斩个,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任西雀,我火速辦了婚禮萨驶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘艇肴。我一直安慰自己腔呜,他們只是感情好叁温,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著核畴,像睡著了一般膝但。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谤草,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天跟束,我揣著相機(jī)與錄音,去河邊找鬼丑孩。 笑死冀宴,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的温学。 我是一名探鬼主播略贮,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼仗岖!你這毒婦竟也來了逃延?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤轧拄,失蹤者是張志新(化名)和其女友劉穎揽祥,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體檩电,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拄丰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了俐末。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愈案。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鹅搪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情遭铺,我是刑警寧澤丽柿,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站魂挂,受9級特大地震影響甫题,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜涂召,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一坠非、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧果正,春花似錦炎码、人聲如沸盟迟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽攒菠。三九已至,卻和暖如春歉闰,著一層夾襖步出監(jiān)牢的瞬間揭斧,已是汗流浹背演侯。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赏半。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像蕾殴,于是被迫代替她去往敵國和親普办。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容