簡體   English   中英

嵌套片段 - IllegalStateException“onSaveInstanceState后無法執行此操作”

[英]Nested Fragments - IllegalStateException “Can not perform this action after onSaveInstanceState”

背景

Android中的異步回調

嘗試在Android上以可靠的方式執行異步操作是不必要的錯綜復雜的,即AsyncTask在概念上是否真的有缺陷,或者我只是缺少某些東西?

現在,這是在引入Fragments之前的全部內容。 隨着Fragments的引入, onRetainNonConfigurationInstance()已被棄用。 因此,最新的谷歌縱容黑客是使用持久的非UI片段,當發生配置更改時(即旋轉屏幕,更改語言設置等),它會從您的活動中附加/分離。

示例: https//code.google.com/p/android/issues/detail?id = 23096#c4

IllegalStateException - 無法在onSaveInstanceState之后執行此操作

從理論上講,上面的黑客可以讓你繞過可怕的:

IllegalStateException - "Can not perform this action after onSaveInstanceState"

因為持久的非UI片段將接收onViewStateRestored()(或者onResume)和onSaveInstanceState()(或者onPause)的回調。 因此,您可以判斷實例狀態何時被保存/恢復。 這是一個很簡單的代碼,但是利用這些知識,您可以排隊異步回調,直到活動的FragmentManager在執行它們之前將其mStateSaved變量設置為false。

mStateSaved是最終負責觸發此異常的變量。

private void checkStateLoss() {
    if (mStateSaved) {
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState");
    }
    if (mNoTransactionsBecause != null) {
        throw new IllegalStateException(
                "Can not perform this action inside of " + mNoTransactionsBecause);
    }
}

所以從理論上講,現在你知道什么時候執行片段事務是安全的,因此你可以避免可怕的IllegalStateException。

錯誤!

嵌套片段

上面的解決方案僅適用於Activity的FragmentManager。 片段本身也有一個子片段管理器,用於嵌套片段。 遺憾的是,子片段管理器根本不與Activity的片段管理器保持同步。 因此,雖然活動的片段管理器是最新的,並且始終具有正確的mStateSaved; 兒童片段不這么認為,並且會在不適當的時候愉快地拋出可怕的IllegalStateException。

現在,如果您查看了支持庫中的Fragment.java和FragmentManager.java,您將不會感到驚訝的是,所有與片段有關的內容都容易出錯。 設計和代碼質量是......啊,有問題。 你喜歡布爾嗎?

mHasMenu = false
mHidden = false
mInLayout = false
mIndex = 1
mFromLayout = false
mFragmentId = 0
mLoadersStarted = true
mMenuVisible = true
mNextAnim = 0
mDetached = false
mRemoving = false
mRestored = false
mResumed = true
mRetainInstance = true
mRetaining = false
mDeferStart = false
mContainerId = 0
mState = 5
mStateAfterAnimating = 0
mCheckedForLoaderManager = true
mCalled = true
mTargetIndex = -1
mTargetRequestCode = 0
mUserVisibleHint = true
mBackStackNesting = 0
mAdded = true

無論如何,有點偏離主題。

死胡同解決方案

所以,你可能認為問題的解決方案很簡單, 就像這一點似乎是一個反義詞,為你的子片段管理器添加另一個漂亮的hacky非UI片段。 據推測,它的回調與其內部狀態是同步的,事情都會花哨。

又錯了!

Android不支持作為子項附加到其他片段(也稱為嵌套片段)的保留片段實例。 現在,事后看來這應該是有道理的。 如果在活動被銷毀時銷毀了父片段,因為它未被保留,則無法重新附加子片段。 所以這不會起作用。

我的問題

是否有人有一個解決方案來確定何時可以安全地對子片段執行片段事務以及異步代碼回調?

更新2

反應原生

如果你可以忍受它,使用React Native。 我知道,我知道......“骯臟的網絡技術”,但嚴肅地說,Android SDK是一場災難,所以吞下你的驕傲,只是試一試。 你可能會驚訝自己; 我知道我做到了!

不能或不會使用React Native

不用擔心,我建議從根本上改變你的網絡方法。 觸發請求並運行請求處理程序以更新UI僅對Android的組件生命周期不起作用。

而是嘗試以下之一:

  1. 轉移到基於LocalBroadcastReceiver簡單消息傳遞系統,並且具有長生命對象(常規Java類或Android服務)在您的應用程序的本地狀態發生更改時執行請求並觸發事件。 然后在您的Activity / Fragment中,只聽取某些Intent並相應地更新。
  2. 使用Reactive事件庫(例如RxJava)。 我自己並沒有在Android上試過這個,但是使用類似的概念庫ReactiveCocoa在Mac /桌面應用程序上取得了相當不錯的成功。 不可否認,這些庫有一個相當陡峭的學習曲線,但是一旦你習慣它,這種方法就會非常清爽。

更新1:快速和骯臟(官方)解決方案

我相信這是谷歌最新的官方解決方案。 但是,解決方案確實不能很好地擴展。 如果你不熟悉隊列,處理程序和保留的實例狀態,那么這可能是你唯一的選擇...但不要說我沒有警告你!

Android活動和片段支持可以與AsyncTaskLoader一起使用的LoaderManager 在幕后,加載程序管理器的保留方式與保留的碎片完全相同。 因此,這個解決方案確實與我自己的解決方案有一點共同之處。 AsyncTaskLoader是一種部分預先固定的解決方案,在技術上可行 但是,API非常麻煩; 因為我相信你會在幾分鍾內注意到它。

我的解決方案

首先,我的解決方案實施起來並不簡單。 但是,一旦您的實施工作,使用起來輕而易舉,您可以根據自己的內容進行自定義。

我使用一個保留的片段添加到Activity的片段管理器(或者在我的案例支持片段管理器中)。 這與我的問題中提到的技術相同。 這個片段充當各種類的提供者 ,它跟蹤它附加到哪個活動,並具有Message和Runnable(實際上是一個自定義子類)隊列。 當實例狀態不再保存相應的處理程序(或可運行)“准備執行”時,隊列將執行。

每個處理程序/ runnable存儲一個引用消費者的UUID。 消費者通常是活動中某處的片段(可以安全地嵌套)。 消費者片段附加到活動時,它會查找提供者片段並使用其UUID注冊自己。

使用某種抽象(如UUID)而不是直接引用消費者 (即片段)非常重要。 這是因為片段經常被破壞和重新創建,並且您希望您的回調具有對新片段的“引用”; 不屬於被破壞的活動的舊的。 因此,遺憾的是,您很少能安全地使用匿名類捕獲的變量。 同樣,這是因為這些變量可能引用舊的已破壞的片段或活動。 相反,您必須向提供者詢問與處理程序已存儲的UUID匹配的使用者 然后,您可以將此使用者轉換為它實際存在的任何片段/對象並安全地使用它,因為您知道它具有有效Context(活動)的最新片段。

消費者 (由UUID引用)准備就緒時,處理程序(或可運行)將“准備好執行”。 除了提供者之外,有必要檢查消費者是否已准備就緒,因為正如我的問題所述,消費者片段可能認為即使提供者另有說明,也會保存其實例狀態。 如果使用者(或提供者)尚未就緒,則將Message(或runnable)放入提供者的隊列中。

使用者片段到達onResume()時,它通知提供者它已准備好使用排隊的消息/ runnables。 此時, 提供程序可以嘗試在其剛剛准備就緒的消費者的隊列中執行任何操作。

這導致處理程序總是使用有效的Context(提供者引用的Activity)和最新的有效Fragment(又名“consumer”)執行。

結論

解決方案非常復雜,但是一旦你弄清楚如何實現它,它確實可以正常工作。 如果有人想出一個更簡單的解決方案,那么我很樂意聽到它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM