ApiDemo/FragmentRetainInstance 解析,androidapidemo
/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.example.android.apis.app;import com.example.android.apis.R;import android.app.Activity;import android.app.Fragment;import android.app.FragmentManager;import android.os.Bundle;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.ProgressBar;/** * This example shows how you can use a Fragment to easily propagate state * (such as threads) across activity instances when an activity needs to be * restarted due to, for example, a configuration change. This is a lot * easier than using the raw Activity.onRetainNonConfiguratinInstance() API. */public class FragmentRetainInstance extends Activity {private static String TAG="FragmentRetainInstance"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // First time init, create the UI. if (savedInstanceState == null) { getFragmentManager().beginTransaction().add(android.R.id.content, new UiFragment()).commit(); } } /** * This is a fragment showing UI that will be updated from work done * in the retained fragment. */ public static class UiFragment extends Fragment { RetainedFragment mWorkFragment; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_retain_instance, container, false); // Watch for button clicks. Button button = (Button)v.findViewById(R.id.restart); button.setOnClickListener(new OnClickListener() { public void onClick(View v) { mWorkFragment.restart(); } }); return v; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); FragmentManager fm = getFragmentManager(); // Check to see if we have retained the worker fragment. mWorkFragment = (RetainedFragment)fm.findFragmentByTag("work"); // If not retained (or first time running), we need to create it. if (mWorkFragment == null) { mWorkFragment = new RetainedFragment(); // Tell it who it is working with. mWorkFragment.setTargetFragment(this, 0); fm.beginTransaction().add(mWorkFragment, "work").commit(); } } } /** * This is the Fragment implementation that will be retained across * activity instances. It represents some ongoing work, here a thread * we have that sits around incrementing a progress indicator. */ public static class RetainedFragment extends Fragment { ProgressBar mProgressBar; int mPosition; boolean mReady = false; boolean mQuiting = false; /** * This is the thread that will do our work. It sits in a loop running * the progress up until it has reached the top, then stops and waits. */ final Thread mThread = new Thread() { @Override public void run() { // We'll figure the real value out later. int max = 10000; // This thread runs almost forever. while (true) { // Update our shared state with the UI. synchronized (this) { // Our thread is stopped if the UI is not ready // or it has completed its work. while (!mReady || mPosition >= max) { if (mQuiting) { return; } try { wait(); } catch (InterruptedException e) { } } // Now update the progress. Note it is important that // we touch the progress bar with the lock held, so it // doesn't disappear on us. mPosition++; max = mProgressBar.getMax(); mProgressBar.setProgress(mPosition); } // Normally we would be doing some work, but put a kludge // here to pretend like we are. synchronized (this) { try { wait(50); } catch (InterruptedException e) { } } } } }; /** * Fragment initialization. We way we want to be retained and * start our thread. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Tell the framework to try to keep this fragment around // during a configuration change. setRetainInstance(true); // Start up the worker thread. mThread.start(); } /** * This is called when the Fragment's Activity is ready to go, after * its content view has been installed; it is called both after * the initial fragment creation and after the fragment is re-attached * to a new activity. */ @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // Retrieve the progress bar from the target's view hierarchy. mProgressBar = (ProgressBar)getTargetFragment().getView().findViewById( R.id.progress_horizontal); // We are ready for our thread to go. synchronized (mThread) { mReady = true; mThread.notify(); } } /** * This is called when the fragment is going away. It is NOT called * when the fragment is being propagated between activity instances. */ @Override public void onDestroy() { // Make the thread go away. Log.d(TAG, "onDestroy"); synchronized (mThread) { mReady = false; mQuiting = true; mThread.notify(); } super.onDestroy(); } /** * This is called right before the fragment is detached from its * current activity instance. */ @Override public void onDetach() { // This fragment is being detached from its activity. We need // to make sure its thread is not going to touch any activity // state after returning from this function. Log.d(TAG, "onDetach"); synchronized (mThread) { mProgressBar = null; mReady = false; mThread.notify(); } super.onDetach(); } /** * API for our UI to restart the progress thread. */ public void restart() { synchronized (mThread) { mPosition = 0; mThread.notify(); } } }}
這個小例子有兩個關鍵點
1、通過Fragment儲存狀態。 通常在Activity銷毀時和Activity關聯的Fragment也會被銷毀。當Activity重建時會自動建立相關的Fragment。因此經常在Activity的onCreate 函數中判處savedInstanceState 是否為空白,(當Activity 有關聯的Fragment時,重建Activity時savedInstanceState不為空白)來避免重複建立Fragment。重建的Fragment和之前的Fragment是兩個不同的對象。但是如果對Fragment調用setRetainInstance(true),那麼在Activity銷毀時(設定改變導致的activity銷毀,如橫豎屏切換)會保留該Fragment(onDetach會被調用,onDestroy不會被調用),Activity重建時會繼續關聯該Fragment。即通過FragmentManager 得到的還是之前的Fragment。可以利用Fragment的這個性質儲存Activity的狀態。 與通過onSaveInstance或onRetainNonConfiguratinInstance()方法相比,通過Fragment儲存狀態很方便。特別是對於比較大的對象如Bitmap或不容易序列化的 對象(如本例中的線程對象)。用於儲存狀態的Fragment一般不能有視圖(onCreateView 返回null),但是可以設定TargetFragment,可以擷取TargetFragment,更新TargetFragment的UI。
2、如何避免在Activity銷毀期間後台線程更新UI。
在本例中activity銷毀期間,線程仍在執行,線程執行期間可能會更改進度條。但這時UI已經銷毀了。
本例中在onDetach中將mReady 設定為false,來避免更新進度條。
同時在更新進度條時獲得互斥鎖,防止更新進度條時UI資源被回收。