Activityライフサイクル異常解析
Activityライフサイクル異常解析
通常、Activityが作成されるとonCreate onStart onResumeメソッドが実行されることを知っています.スクリーンロックの場合、ActivityはonPause onStopメソッドを実行します.画面が再度表示されると、onReStart onStart onResumeメソッドが実行されます.しかし、言語の切り替え、縦画面の切り替えなどの構成の変更、メモリの不足などの特殊な場合、activityが殺され、ライフサイクルが異常になる可能性があります.これは開発者にとって、これらを理解する必要があります.この2つの状況を具体的に分析しましょう
(一)ケース1:リソース関連の構成情報が変更され、Activityライフサイクルが異常となる
このような状況を理解するには、まずシステムのリソースロードメカニズムを理解しなければならない.ここで簡単に説明すると、stringリソースファイルについて言えば、私たちのプロジェクトが国際化されると、システムは現在のlocalに基づいて異なるstringリソースファイル(異なる国のstringファイルが設定されている)をロードします.また、横画面携帯電話と縦画面携帯電話が異なる画像(landscapeまたはportrait状態の画像が設定されている)を取得し、上記の2つの構成が変更された場合、Activityのデフォルトは破棄され、作成されます.
異常の場合Activityライフサイクル異常解析
システム構成が変更されると、Activityは破棄され、onPause,onStop,onDestroyメソッドが呼び出されます.Activityは例外的に実行が終了するため、現在のActivityの状態を保存するためにonSaveInstancesStateが呼び出されます.同時に、リカバリ時にActivityが実行されると、onRestoreInstanceStateメソッドが呼び出されてActivityの状態がリカバリされます.
また、Activityが例外終了後に作成を再起動すると、テキストボックスに入力されたデータ、ListViewがスクロールされた場所など、onSaveInstanceStateメソッドで現在のActivityのステータスがBundleで保存され、onCreateメソッドの前にonRestoreInstanceStateが呼び出されてこれらのステータスが回復することも知らなければなりません.
1.1 Activity状態回復メカニズム
これらのビューの状態がどのように保存されているかについては、ビューのソースコードを見ていると、ビューのソースコードにonSaveInstanceとonRestoreInstanceStateメソッドがあり、Activityにもこの2つのメソッドがありますが、Activityはビューの2つのメソッドを呼び出すことで状態を回復しますか?(説明:Fragmentにも実はこの2つの方法があります)
答えは肯定的で、Viewの階層構造の保存と復元について、システムのワークフローはこのようにしている:まずActivityが意外に終了すると、ActivityはonSaveInstanceメソッドを呼び出してデータを保存し、ActivityはWindowにデータの保存を依頼し、Windowはその上の最上位コンテナにデータの保存を依頼し、最上位コンテナ(DecorView)はView Groupである.最後に、最上位コンテナは、データを保存するためにサブ要素を一つ一つ通知し、保存プロセス全体が完了します.これは典型的な依頼思想であり、上層は下層に依頼し、容器はサブ要素に依頼して一つのことを処理する.この思想はAndroidに多くの応用があり、例えばViewの描画過程、イベント配布過程は類似の思想を採用している.(説明:fragmentもViewの場合と似ています)
1.2 ActivityにおけるView状態回復機構、ソースコード分析
つまり、ActivityがonSaveInstanceStateメソッドを呼び出してデータを復元すると同時に、ViewもonSaveInstanceメソッドを実行してデータを保存し、onRestoreInstanceStateを呼び出すと、ViewもonRestoreInstanceStateが復元される前の状態を呼び出す.ソースコードを見て分析しましょう
(1)ActivityのonSaveInstanceStateメソッドソースコード
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle("android:viewHierarchyState", this.mWindow.saveHierarchyState());
Parcelable p = this.mFragments.saveAllState();
if(p != null) {
outState.putParcelable("android:fragments", p);
}
this.getApplication().dispatchActivitySaveInstanceState(this, outState);
}
onSaveInstanceInstanceStateではwindowがsaveHierarchyStateを呼び出し、BuddleですべてのFragmentのデータをkey値が「android:fragments」のオブジェクトに配置し、FragmentActivityであればkeyは「android:support:fragments」であることがわかります.
(2)次にWindowのsaveHierarchyStateメソッドソースコードを見る
ActivityがWindowの下に掛けられていることはよく知られていますが、ASでWindowを発見するsaveHierarchyState法は抽象的な方法ですが、そのサブクラスPhoneWindowのソースコードは表示できません.最後にeveryThingを用いてAndroidソースコードにおいて,PhoneWindowにおけるこの方法のソースコードを見つけた.
/** {@inheritDoc} */
@Override
public void restoreHierarchyState(Bundle savedInstanceState) {
if (mContentParent == null) {
return;
}
SparseArray<Parcelable> savedStates
= savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
if (savedStates != null) {
mContentParent.restoreHierarchyState(savedStates);
}
// restore the focused view
int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);
if (focusedViewId != View.NO_ID) {
View needsFocus = mContentParent.findViewById(focusedViewId);
if (needsFocus != null) {
needsFocus.requestFocus();
} else {
Log.w(TAG,
"Previously focused view reported id " + focusedViewId
+ " during save, but can't be found during restore.");
}
}
}
saveHierarchyStateは実質的にmContentParentを実行していることがわかります.restoreHierarchyState(savedStates)メソッド、mContentParentは実はView Groupですが、restoreHierarchyStateはいったい何を書いたのでしょうか.このメソッドはViewで見つけられ、dispatchRestoreInstanceStateが呼び出され、最終的にViewのonRestoreInstanceStateメソッドが呼び出されました.
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
if(this.mID != -1) {
Parcelable state = (Parcelable)container.get(this.mID);
if(state != null) {
this.mPrivateFlags &= -131073;
this.onRestoreInstanceState(state);
if((this.mPrivateFlags & 131072) == 0) {
throw new IllegalStateException("Derived class did not call super.onRestoreInstanceState()");
}
}
}
}
ViewGroupはまた、親ViewのdispatchRestoreInstanceStateメソッドをリロードし、ソースコードは次のとおりです.
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
super.dispatchRestoreInstanceState(container);
int count = this.mChildrenCount;
View[] children = this.mChildren;
for(int i = 0; i < count; ++i) {
View c = children[i];
if((c.mViewFlags & 536870912) != 536870912) {
c.dispatchRestoreInstanceState(container);
}
}
}
明らかに、ViewGroupは各サブViewにdispatchRestoreInstanceStateを実行してそれぞれの状態を保存するように通知します.
まとめ:ActivityonRestoreInstanceStateメソッドも同様です.以上のソース分析から,Activityが異常終了した場合にはFragmentの状態を保存するとともに,Windowに依頼してActivity中のすべてのViewの状態をDecorViewに保存させるという結論が得られた.
1.3 Fragmentはどのように自分の状態を保存しますか
TVプロジェクトをしていたとき、プロジェクトが複数のFragmentからなるActivityを起動したとき、言語が簡体字中国語から英語に切り替わった後、再びアプリを起動したところ、FragmentにはViewが空の空のポインタの異常が何となく報告されていたことが分かった.長い間のピット登りを経て、google検索でも良い解決方法がなく、最後にソースコードを見てやっとこの問題を解決し、今この問題がどのように解決されたのかを分析します.
この問題は、構成の変更(多言語切り替え)によりActivityが異常終了し、再作成されることが明らかになった.この場合:
Activityのライフサイクルは、onResume onStop onDestroy onSaveInstanceState onCreate onStart onResumeです.Fragmentのライフサイクル:onResume onStop onDestroyView onCreateView
Fragmentの一部のデータ保存によるものだと思います.だからActivityのonSaveInstanceStateメソッドをブロックしたら異常に解決して、その時は楽しくて、問題は解決しましたが、これはよくありませんね、Activityのデータは保存できませんでした.Fragmentのデータだけをブロックしてもらえますか?答えはいいです.
ActivityのonSaveInstanceStateメソッドのソースコードを見続けます.このようなコードがあります.
Parcelable p = this.mFragments.saveAllState();
if(p != null) {
outState.putParcelable("android:fragments", p);
}
}
ActivityがmFragmentに保存されているデータを取得し、ActivityがBuddleにデータを配置し、Keyが「android:fragments」であることがわかります.これはやりやすいです.Buddleは実は集合クラスで、ActivityのonSaveInstancesStateメソッドでBuddle removeでキー値を調整すればFragmentが以前の状態を残さないのですか?実際には、この方法は実行可能であるが、Activityにとってキー値は「android:fragments」であることに注意しなければならないが、FragmentActivityはまた「android:support:fragments」になった.
最後にキーコードを貼り付けます.
(1)ActivityのonSaveInstanceStateメソッドで、pageIndexはActivityで選択したfragmentに対応するindex値です.
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if(outState != null){
outState.putInt("pageIndex",mJumpType);
outState.remove(this.getFragmentTagForSaveInstance());
}
}
(2)getFragmentTagForSaveInstance()メソッドはfragment状態が保存されているときに対応するkey値を取得する.
protected String getFragmentTagForSaveInstance() {
try {
Field f = Activity.class.getDeclaredField("FRAGMENTS_TAG");
f.setAccessible(true);
Object fragmentTagObj = f.get(null);
if (fragmentTagObj != null) {
return String.valueOf(fragmentTagObj);
}
} catch (Exception e) {
e.printStackTrace();
}
return "android:fragments";
}
(3)Activity onCreateメソッドはkey値がpageIndexの値を取得する
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null){
mJumpType = savedInstanceState.getInt("pageIndex",0);
}
}
説明:Activityサイクル異常によるFragment空ポインタの異常はgoogleシステムレベルのバグであり、この問題はstackFlowでもうまく解決できないので、Activityとfragmentライフサイクル異常の状況を分析するのに役立つことを願っています.
(二)ケース2:リソースメモリ不足により優先度の低いActivityが殺される
この状況はシミュレーションしにくいが,データストレージとリカバリプロセスの状況は状況と完全に一致している.ここではActivityの優先度について説明します.Activityは、優先度が高いものから低いものまで、次の3つに分けられます.
(1)フロントActivity-ユーザーと対話中のActivity、優先度が最も高い
(2)Activityのような単一非フロントActivityダイアログボックスが表示され、Activityが表示されますが、バックグラウンドではユーザーと対話できません.
(3)バックグラウンドActivity-onStopメソッドを実行するなど、一時停止されているActivityの優先度が最も低い.
システムメモリが不足している場合、システムは上記の優先順位で現在のActivityが存在するプロセスを殺し、その後、onSaveInstanceStateとonRestoreInstanceStateでデータを格納および復元します.1つのプロセスで4つのコンポーネントが実行されていない場合、このプロセスは簡単に殺されます.バックグラウンドで作業することで、プロセスの優先度を確保し、簡単にシステムに殺されないようにするのが良い方法です.
既存
0人がメッセージを発表し、猛撃->
ここで<<-ディスカッションに参加
ITeye推奨