Android 屏幕旋轉 處理 AsyncTask 和 ProgressDialog 的最佳方案

1、概述

衆所周知,Activity在不明確指定屏幕方向和configChanges時,當用戶旋轉屏幕會重新啓動。當然了,應對這種情況,Android給出了幾種方案:

a、如果是少量數據,可以通過onSaveInstanceState()和onRestoreInstanceState()進行保存與恢復。

Android會在銷燬你的Activity之前調用onSaveInstanceState()方法,於是,你可以在此方法中存儲關於應用狀態的數據。然後你可以在onCreate()或onRestoreInstanceState()方法中恢復。

b、如果是大量數據,使用Fragment保持需要恢復的對象。

c、自已處理配置變化。

注:getLastNonConfigurationInstance()已經被棄用,被上述方法二替代。

2、難點

假設當前Activity在onCreate中啓動一個異步線程去夾在數據,當然爲了給用戶一個很好的體驗,會有一個ProgressDialog,當數據加載完成,ProgressDialog消失,設置數據。

這裏,如果在異步數據完成加載之後,旋轉屏幕,使用上述a、b兩種方法都不會很難,無非是保存數據和恢復數據。

但是,如果正在線程加載的時候,進行旋轉,會存在以下問題:

a)此時數據沒有完成加載,onCreate重新啓動時,會再次啓動線程;而上個線程可能還在運行,並且可能會更新已經不存在的控件,造成錯誤。

b)關閉ProgressDialog的代碼在線程的onPostExecutez中,但是上個線程如果已經殺死,無法關閉之前ProgressDialog。

c)谷歌的官方不建議使用ProgressDialog,這裏我們會使用官方推薦的DialogFragment來創建我的加載框,如果你不瞭解:請看 Android 官方推薦 : DialogFragment 創建對話框。這樣,其實給我們帶來一個很大的問題,DialogFragment說白了是Fragment,和當前的Activity的生命週期會發生綁定,我們旋轉屏幕會造成Activity的銷燬,當然也會對DialogFragment造成影響。

下面我將使用幾個例子,分別使用上面的3種方式,和如何最好的解決上述的問題。

3、使用onSaveInstanceState()和onRestoreInstanceState()進行數據保存與恢復

代碼:

[java] view plain copy
  1. package com.example.zhy_handle_runtime_change;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Arrays;  
  5.   
  6. import android.app.DialogFragment;  
  7. import android.app.ListActivity;  
  8. import android.os.AsyncTask;  
  9. import android.os.Bundle;  
  10. import android.util.Log;  
  11. import android.widget.ArrayAdapter;  
  12. import android.widget.ListAdapter;  
  13. /** 
  14.  * 不考慮加載時,進行旋轉的情況,有意的避開這種情況,後面例子會介紹解決方案 
  15.  * @author zhy 
  16.  * 
  17.  */  
  18. public class SavedInstanceStateUsingActivity extends ListActivity  
  19. {  
  20.     private static final String TAG = "MainActivity";  
  21.     private ListAdapter mAdapter;  
  22.     private ArrayList<String> mDatas;  
  23.     private DialogFragment mLoadingDialog;  
  24.     private LoadDataAsyncTask mLoadDataAsyncTask;  
  25.   
  26.     @Override  
  27.     public void onCreate(Bundle savedInstanceState)  
  28.     {  
  29.         super.onCreate(savedInstanceState);  
  30.         Log.e(TAG, "onCreate");  
  31.         initData(savedInstanceState);  
  32.     }  
  33.   
  34.     /** 
  35.      * 初始化數據 
  36.      */  
  37.     private void initData(Bundle savedInstanceState)  
  38.     {  
  39.         if (savedInstanceState != null)  
  40.             mDatas = savedInstanceState.getStringArrayList("mDatas");  
  41.   
  42.         if (mDatas == null)  
  43.         {  
  44.             mLoadingDialog = new LoadingDialog();  
  45.             mLoadingDialog.show(getFragmentManager(), "LoadingDialog");  
  46.             mLoadDataAsyncTask = new LoadDataAsyncTask();  
  47.             mLoadDataAsyncTask.execute();  
  48.               
  49.         } else  
  50.         {  
  51.             initAdapter();  
  52.         }  
  53.   
  54.     }  
  55.   
  56.     /** 
  57.      * 初始化適配器 
  58.      */  
  59.     private void initAdapter()  
  60.     {  
  61.         mAdapter = new ArrayAdapter<String>(  
  62.                 SavedInstanceStateUsingActivity.this,  
  63.                 android.R.layout.simple_list_item_1, mDatas);  
  64.         setListAdapter(mAdapter);  
  65.     }  
  66.   
  67.     @Override  
  68.     protected void onRestoreInstanceState(Bundle state)  
  69.     {  
  70.         super.onRestoreInstanceState(state);  
  71.         Log.e(TAG, "onRestoreInstanceState");  
  72.     }  
  73.   
  74.     @Override  
  75.     protected void onSaveInstanceState(Bundle outState)  
  76.     {  
  77.         super.onSaveInstanceState(outState);  
  78.         Log.e(TAG, "onSaveInstanceState");  
  79.         outState.putSerializable("mDatas", mDatas);  
  80.   
  81.     }  
  82.   
  83.     /** 
  84.      * 模擬耗時操作 
  85.      *  
  86.      * @return 
  87.      */  
  88.     private ArrayList<String> generateTimeConsumingDatas()  
  89.     {  
  90.         try  
  91.         {  
  92.             Thread.sleep(2000);  
  93.         } catch (InterruptedException e)  
  94.         {  
  95.         }  
  96.         return new ArrayList<String>(Arrays.asList("通過Fragment保存大量數據",  
  97.                 "onSaveInstanceState保存數據",  
  98.                 "getLastNonConfigurationInstance已經被棄用""RabbitMQ""Hadoop",  
  99.                 "Spark"));  
  100.     }  
  101.   
  102.     private class LoadDataAsyncTask extends AsyncTask<Void, Void, Void>  
  103.     {  
  104.         @Override  
  105.         protected Void doInBackground(Void... params)  
  106.         {  
  107.             mDatas = generateTimeConsumingDatas();  
  108.             return null;  
  109.         }  
  110.   
  111.         @Override  
  112.         protected void onPostExecute(Void result)  
  113.         {  
  114.             mLoadingDialog.dismiss();  
  115.             initAdapter();  
  116.         }  
  117.     }  
  118.   
  119.     @Override  
  120.     protected void onDestroy()  
  121.     {  
  122.         Log.e(TAG, "onDestroy");  
  123.         super.onDestroy();  
  124.     }  
  125.   
  126. }  


界面爲一個ListView,onCreate中啓動一個異步任務去加載數據,這裏使用Thread.sleep模擬了一個耗時操作;當用戶旋轉屏幕發生重新啓動時,會onSaveInstanceState中進行數據的存儲,在onCreate中對數據進行恢復,免去了不必要的再加載一遍。

運行結果:

當正常加載數據完成之後,用戶不斷進行旋轉屏幕,log會不斷打出:onSaveInstanceState->onDestroy->onCreate->onRestoreInstanceState,驗證我們的確是重新啓動了,但是我們沒有再次去進行數據加載。

如果在加載的時候,進行旋轉,則會發生錯誤,異常退出(退出原因:dialog.dismiss()時發生NullPointException,因爲與當前對話框綁定的FragmentManager爲null,又有興趣的可以去Debug,這個不是關鍵)。

效果圖:




4、使用Fragment來保存對象,用於恢復數據

如果重新啓動你的Activity需要恢復大量的數據,重新建立網絡連接,或者執行其他的密集型操作,這樣因爲配置發生變化而完全重新啓動可能會是一個慢的用戶體驗。並且,使用系統提供的onSaveIntanceState()的回調中,使用Bundle來完全恢復你Activity的狀態是可能是不現實的(Bundle不是設計用來攜帶大量數據的(例如bitmap),並且Bundle中的數據必須能夠被序列化和反序列化),這樣會消耗大量的內存和導致配置變化緩慢。在這樣的情況下,當你的Activity因爲配置發生改變而重啓,你可以通過保持一個Fragment來緩解重新啓動帶來的負擔。這個Fragment可以包含你想要保持的有狀態的對象的引用。

當Android系統因爲配置變化關閉你的Activity的時候,你的Activity中被標識保持的fragments不會被銷燬。你可以在你的Activity中添加這樣的fragements來保存有狀態的對象。

在運行時配置發生變化時,在Fragment中保存有狀態的對象
a) 繼承Fragment,聲明引用指向你的有狀態的對象
b) 當Fragment創建時調用setRetainInstance(boolean)
c) 把Fragment實例添加到Activity中
d) 當Activity重新啓動後,使用FragmentManager對Fragment進行恢復
代碼:

首先是Fragment:

[java] view plain copy
  1. package com.example.zhy_handle_runtime_change;  
  2.   
  3. import android.app.Fragment;  
  4. import android.graphics.Bitmap;  
  5. import android.os.Bundle;  
  6.   
  7. public class RetainedFragment extends Fragment  
  8. {  
  9.     // data object we want to retain  
  10.     private Bitmap data;  
  11.     // this method is only called once for this fragment  
  12.     @Override  
  13.     public void onCreate(Bundle savedInstanceState)  
  14.     {  
  15.         super.onCreate(savedInstanceState);  
  16.         // retain this fragment  
  17.         setRetainInstance(true);  
  18.     }  
  19.   
  20.     public void setData(Bitmap data)  
  21.     {  
  22.         this.data = data;  
  23.     }  
  24.   
  25.     public Bitmap getData()  
  26.     {  
  27.         return data;  
  28.     }  
  29. }  

比較簡單,只需要聲明需要保存的數據對象,然後提供getter和setter,注意,一定要在onCreate調用setRetainInstance(true);

然後是:FragmentRetainDataActivity

[java] view plain copy
  1. package com.example.zhy_handle_runtime_change;  
  2.   
  3. import android.app.Activity;  
  4. import android.app.DialogFragment;  
  5. import android.app.FragmentManager;  
  6. import android.graphics.Bitmap;  
  7. import android.graphics.Bitmap.Config;  
  8. import android.os.Bundle;  
  9. import android.util.Log;  
  10. import android.widget.ImageView;  
  11.   
  12. import com.android.volley.RequestQueue;  
  13. import com.android.volley.Response;  
  14. import com.android.volley.toolbox.ImageRequest;  
  15. import com.android.volley.toolbox.Volley;  
  16.   
  17. public class FragmentRetainDataActivity extends Activity  
  18. {  
  19.   
  20.     private static final String TAG = "FragmentRetainDataActivity";  
  21.     private RetainedFragment dataFragment;  
  22.     private DialogFragment mLoadingDialog;  
  23.     private ImageView mImageView;  
  24.     private Bitmap mBitmap;  
  25.   
  26.     @Override  
  27.     public void onCreate(Bundle savedInstanceState)  
  28.     {  
  29.         super.onCreate(savedInstanceState);  
  30.         setContentView(R.layout.activity_main);  
  31.         Log.e(TAG, "onCreate");  
  32.   
  33.         // find the retained fragment on activity restarts  
  34.         FragmentManager fm = getFragmentManager();  
  35.         dataFragment = (RetainedFragment) fm.findFragmentByTag("data");  
  36.         // create the fragment and data the first time  
  37.         if (dataFragment == null)  
  38.         {  
  39.             // add the fragment  
  40.             dataFragment = new RetainedFragment();  
  41.             fm.beginTransaction().add(dataFragment, "data").commit();  
  42.         }  
  43.         mBitmap = collectMyLoadedData();  
  44.         initData();  
  45.   
  46.         // the data is available in dataFragment.getData()  
  47.     }  
  48.   
  49.     /** 
  50.      * 初始化數據 
  51.      */  
  52.     private void initData()  
  53.     {  
  54.         mImageView = (ImageView) findViewById(R.id.id_imageView);  
  55.         if (mBitmap == null)  
  56.         {  
  57.             mLoadingDialog = new LoadingDialog();  
  58.             mLoadingDialog.show(getFragmentManager(), "LOADING_DIALOG");  
  59.             RequestQueue newRequestQueue = Volley  
  60.                     .newRequestQueue(FragmentRetainDataActivity.this);  
  61.             ImageRequest imageRequest = new ImageRequest(  
  62.                     "https://img-my.csdn.net/uploads/201407/18/1405652589_5125.jpg",  
  63.                     new Response.Listener<Bitmap>()  
  64.                     {  
  65.                         @Override  
  66.                         public void onResponse(Bitmap response)  
  67.                         {  
  68.                             mBitmap = response;  
  69.                             mImageView.setImageBitmap(mBitmap);  
  70.                             // load the data from the web  
  71.                             dataFragment.setData(mBitmap);  
  72.                             mLoadingDialog.dismiss();  
  73.                         }  
  74.                     }, 00, Config.RGB_565, null);  
  75.             newRequestQueue.add(imageRequest);  
  76.         } else  
  77.         {  
  78.             mImageView.setImageBitmap(mBitmap);  
  79.         }  
  80.   
  81.     }  
  82.   
  83.     @Override  
  84.     public void onDestroy()  
  85.     {  
  86.         Log.e(TAG, "onDestroy");  
  87.         super.onDestroy();  
  88.         // store the data in the fragment  
  89.         dataFragment.setData(mBitmap);  
  90.     }  
  91.   
  92.     private Bitmap collectMyLoadedData()  
  93.     {  
  94.         return dataFragment.getData();  
  95.     }  
  96.   
  97. }  


這裏在onCreate總使用了Volley去加載 了一張美女照片,然後在onDestroy中對Bitmap進行存儲,在onCreate添加一個或者恢復一個Fragment的引用,然後對Bitmap進行讀取和設置。這種方式適用於比較大的數據的存儲與恢復。

注:這裏也沒有考慮加載時旋轉屏幕,問題與上面的一致。

效果圖:


5、配置configChanges,自己對屏幕旋轉的變化進行處理

在menifest中進行屬性設置:

[html] view plain copy
  1. <activity  
  2.         android:name=".ConfigChangesTestActivity"  
  3.         android:configChanges="screenSize|orientation" >  
  4.     </activity>  
低版本的API只需要加入orientation,而高版本的則需要加入screenSize。

ConfigChangesTestActivity

[java] view plain copy
  1. package com.example.zhy_handle_runtime_change;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Arrays;  
  5.   
  6. import android.app.DialogFragment;  
  7. import android.app.ListActivity;  
  8. import android.content.res.Configuration;  
  9. import android.os.AsyncTask;  
  10. import android.os.Bundle;  
  11. import android.util.Log;  
  12. import android.widget.ArrayAdapter;  
  13. import android.widget.ListAdapter;  
  14. import android.widget.Toast;  
  15.   
  16. /** 
  17.  * @author zhy 
  18.  *  
  19.  */  
  20. public class ConfigChangesTestActivity extends ListActivity  
  21. {  
  22.     private static final String TAG = "MainActivity";  
  23.     private ListAdapter mAdapter;  
  24.     private ArrayList<String> mDatas;  
  25.     private DialogFragment mLoadingDialog;  
  26.     private LoadDataAsyncTask mLoadDataAsyncTask;  
  27.   
  28.     @Override  
  29.     public void onCreate(Bundle savedInstanceState)  
  30.     {  
  31.         super.onCreate(savedInstanceState);  
  32.         Log.e(TAG, "onCreate");  
  33.         initData(savedInstanceState);  
  34.     }  
  35.   
  36.     /** 
  37.      * 初始化數據 
  38.      */  
  39.     private void initData(Bundle savedInstanceState)  
  40.     {  
  41.   
  42.         mLoadingDialog = new LoadingDialog();  
  43.         mLoadingDialog.show(getFragmentManager(), "LoadingDialog");  
  44.         mLoadDataAsyncTask = new LoadDataAsyncTask();  
  45.         mLoadDataAsyncTask.execute();  
  46.   
  47.     }  
  48.   
  49.     /** 
  50.      * 初始化適配器 
  51.      */  
  52.     private void initAdapter()  
  53.     {  
  54.         mAdapter = new ArrayAdapter<String>(ConfigChangesTestActivity.this,  
  55.                 android.R.layout.simple_list_item_1, mDatas);  
  56.         setListAdapter(mAdapter);  
  57.     }  
  58.   
  59.     /** 
  60.      * 模擬耗時操作 
  61.      *  
  62.      * @return 
  63.      */  
  64.     private ArrayList<String> generateTimeConsumingDatas()  
  65.     {  
  66.         try  
  67.         {  
  68.             Thread.sleep(2000);  
  69.         } catch (InterruptedException e)  
  70.         {  
  71.         }  
  72.         return new ArrayList<String>(Arrays.asList("通過Fragment保存大量數據",  
  73.                 "onSaveInstanceState保存數據",  
  74.                 "getLastNonConfigurationInstance已經被棄用""RabbitMQ""Hadoop",  
  75.                 "Spark"));  
  76.     }  
  77.   
  78.     /** 
  79.      * 當配置發生變化時,不會重新啓動Activity。但是會回調此方法,用戶自行進行對屏幕旋轉後進行處理 
  80.      */  
  81.     @Override  
  82.     public void onConfigurationChanged(Configuration newConfig)  
  83.     {  
  84.         super.onConfigurationChanged(newConfig);  
  85.   
  86.         if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)  
  87.         {  
  88.             Toast.makeText(this"landscape", Toast.LENGTH_SHORT).show();  
  89.         } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)  
  90.         {  
  91.             Toast.makeText(this"portrait", Toast.LENGTH_SHORT).show();  
  92.         }  
  93.   
  94.     }  
  95.   
  96.     private class LoadDataAsyncTask extends AsyncTask<Void, Void, Void>  
  97.     {  
  98.         @Override  
  99.         protected Void doInBackground(Void... params)  
  100.         {  
  101.             mDatas = generateTimeConsumingDatas();  
  102.             return null;  
  103.         }  
  104.   
  105.         @Override  
  106.         protected void onPostExecute(Void result)  
  107.         {  
  108.             mLoadingDialog.dismiss();  
  109.             initAdapter();  
  110.         }  
  111.     }  
  112.   
  113.     @Override  
  114.     protected void onDestroy()  
  115.     {  
  116.         Log.e(TAG, "onDestroy");  
  117.         super.onDestroy();  
  118.     }  
  119.   
  120. }  

對第一種方式的代碼進行了修改,去掉了保存與恢復的代碼,重寫了onConfigurationChanged;此時,無論用戶何時旋轉屏幕都不會重新啓動Activity,並且onConfigurationChanged中的代碼可以得到調用。從效果圖可以看到,無論如何旋轉不會重啓Activity.

效果圖:


6、旋轉屏幕的最佳實踐

下面要開始今天的難點了,就是處理文章開始時所說的,當異步任務在執行時,進行旋轉,如果解決上面的問題。

首先說一下探索過程:

起初,我認爲此時旋轉無非是再啓動一次線程,並不會造成異常,我只要即使的在onDestroy裏面關閉上一個異步任務就可以了。事實上,如果我關閉了,上一次的對話框會一直存在;如果我不關閉,但是activity是一定會被銷燬的,對話框的dismiss也會出異常。真心很蛋疼,並且即使對話框關閉了,任務關閉了;用戶旋轉還是會造成重新創建任務,從頭開始加載數據。

下面我們希望有一種解決方案:在加載數據時旋轉屏幕,不會對加載任務進行中斷,且對用戶而言,等待框在加載完成之前都正常顯示:

當然我們還使用Fragment進行數據保存,畢竟這是官方推薦的:

OtherRetainedFragment

[java] view plain copy
  1. package com.example.zhy_handle_runtime_change;  
  2.   
  3. import android.app.Fragment;  
  4. import android.os.Bundle;  
  5.   
  6. /** 
  7.  * 保存對象的Fragment 
  8.  *  
  9.  * @author zhy 
  10.  *  
  11.  */  
  12. public class OtherRetainedFragment extends Fragment  
  13. {  
  14.   
  15.     // data object we want to retain  
  16.     // 保存一個異步的任務  
  17.     private MyAsyncTask data;  
  18.   
  19.     // this method is only called once for this fragment  
  20.     @Override  
  21.     public void onCreate(Bundle savedInstanceState)  
  22.     {  
  23.         super.onCreate(savedInstanceState);  
  24.         // retain this fragment  
  25.         setRetainInstance(true);  
  26.     }  
  27.   
  28.     public void setData(MyAsyncTask data)  
  29.     {  
  30.         this.data = data;  
  31.     }  
  32.   
  33.     public MyAsyncTask getData()  
  34.     {  
  35.         return data;  
  36.     }  
  37.   
  38.       
  39. }  

和上面的差別不大,唯一不同的就是它要保存的對象編程一個異步的任務了,相信看到這,已經知道經常上述問題的一個核心了,保存一個異步任務,在重啓時,繼續這個任務。

[java] view plain copy
  1. package com.example.zhy_handle_runtime_change;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Arrays;  
  5. import java.util.List;  
  6.   
  7. import android.os.AsyncTask;  
  8.   
  9. public class MyAsyncTask extends AsyncTask<Void, Void, Void>  
  10. {  
  11.     private FixProblemsActivity activity;  
  12.     /** 
  13.      * 是否完成 
  14.      */  
  15.     private boolean isCompleted;  
  16.     /** 
  17.      * 進度框 
  18.      */  
  19.     private LoadingDialog mLoadingDialog;  
  20.     private List<String> items;  
  21.   
  22.     public MyAsyncTask(FixProblemsActivity activity)  
  23.     {  
  24.         this.activity = activity;  
  25.     }  
  26.   
  27.     /** 
  28.      * 開始時,顯示加載框 
  29.      */  
  30.     @Override  
  31.     protected void onPreExecute()  
  32.     {  
  33.         mLoadingDialog = new LoadingDialog();  
  34.         mLoadingDialog.show(activity.getFragmentManager(), "LOADING");  
  35.     }  
  36.   
  37.     /** 
  38.      * 加載數據 
  39.      */  
  40.     @Override  
  41.     protected Void doInBackground(Void... params)  
  42.     {  
  43.         items = loadingData();  
  44.         return null;  
  45.     }  
  46.   
  47.     /** 
  48.      * 加載完成回調當前的Activity 
  49.      */  
  50.     @Override  
  51.     protected void onPostExecute(Void unused)  
  52.     {  
  53.         isCompleted = true;  
  54.         notifyActivityTaskCompleted();  
  55.         if (mLoadingDialog != null)  
  56.             mLoadingDialog.dismiss();  
  57.     }  
  58.   
  59.     public List<String> getItems()  
  60.     {  
  61.         return items;  
  62.     }  
  63.   
  64.     private List<String> loadingData()  
  65.     {  
  66.         try  
  67.         {  
  68.             Thread.sleep(5000);  
  69.         } catch (InterruptedException e)  
  70.         {  
  71.         }  
  72.         return new ArrayList<String>(Arrays.asList("通過Fragment保存大量數據",  
  73.                 "onSaveInstanceState保存數據",  
  74.                 "getLastNonConfigurationInstance已經被棄用""RabbitMQ""Hadoop",  
  75.                 "Spark"));  
  76.     }  
  77.   
  78.     /** 
  79.      * 設置Activity,因爲Activity會一直變化 
  80.      *  
  81.      * @param activity 
  82.      */  
  83.     public void setActivity(FixProblemsActivity activity)  
  84.     {  
  85.         // 如果上一個Activity銷燬,將與上一個Activity綁定的DialogFragment銷燬  
  86.         if (activity == null)  
  87.         {  
  88.             mLoadingDialog.dismiss();  
  89.         }  
  90.         // 設置爲當前的Activity  
  91.         this.activity = activity;  
  92.         // 開啓一個與當前Activity綁定的等待框  
  93.         if (activity != null && !isCompleted)  
  94.         {  
  95.             mLoadingDialog = new LoadingDialog();  
  96.             mLoadingDialog.show(activity.getFragmentManager(), "LOADING");  
  97.         }  
  98.         // 如果完成,通知Activity  
  99.         if (isCompleted)  
  100.         {  
  101.             notifyActivityTaskCompleted();  
  102.         }  
  103.     }  
  104.   
  105.     private void notifyActivityTaskCompleted()  
  106.     {  
  107.         if (null != activity)  
  108.         {  
  109.             activity.onTaskCompleted();  
  110.         }  
  111.     }  
  112.   
  113. }  

異步任務中,管理一個對話框,當開始下載前,進度框顯示,下載結束進度框消失,併爲Activity提供回調。當然了,運行過程中Activity不斷的重啓,我們也提供了setActivity方法,onDestory時,會setActivity(null)防止內存泄漏,同時我們也會關閉與其綁定的加載框;當onCreate傳入新的Activity時,我們會在再次打開一個加載框,當然了因爲屏幕的旋轉並不影響加載的數據,所有後臺的數據一直繼續在加載。是不是很完美~~

主Activity:

[java] view plain copy
  1. package com.example.zhy_handle_runtime_change;  
  2.   
  3. import java.util.List;  
  4.   
  5. import android.app.FragmentManager;  
  6. import android.app.ListActivity;  
  7. import android.os.Bundle;  
  8. import android.util.Log;  
  9. import android.widget.ArrayAdapter;  
  10. import android.widget.ListAdapter;  
  11.   
  12. public class FixProblemsActivity extends ListActivity  
  13. {  
  14.     private static final String TAG = "MainActivity";  
  15.     private ListAdapter mAdapter;  
  16.     private List<String> mDatas;  
  17.     private OtherRetainedFragment dataFragment;  
  18.     private MyAsyncTask mMyTask;  
  19.   
  20.     @Override  
  21.     public void onCreate(Bundle savedInstanceState)  
  22.     {  
  23.         super.onCreate(savedInstanceState);  
  24.         Log.e(TAG, "onCreate");  
  25.   
  26.         // find the retained fragment on activity restarts  
  27.         FragmentManager fm = getFragmentManager();  
  28.         dataFragment = (OtherRetainedFragment) fm.findFragmentByTag("data");  
  29.   
  30.         // create the fragment and data the first time  
  31.         if (dataFragment == null)  
  32.         {  
  33.             // add the fragment  
  34.             dataFragment = new OtherRetainedFragment();  
  35.             fm.beginTransaction().add(dataFragment, "data").commit();  
  36.         }  
  37.         mMyTask = dataFragment.getData();  
  38.         if (mMyTask != null)  
  39.         {  
  40.             mMyTask.setActivity(this);  
  41.         } else  
  42.         {  
  43.             mMyTask = new MyAsyncTask(this);  
  44.             dataFragment.setData(mMyTask);  
  45.             mMyTask.execute();  
  46.         }  
  47.         // the data is available in dataFragment.getData()  
  48.     }  
  49.   
  50.   
  51.     @Override  
  52.     protected void onRestoreInstanceState(Bundle state)  
  53.     {  
  54.         super.onRestoreInstanceState(state);  
  55.         Log.e(TAG, "onRestoreInstanceState");  
  56.     }  
  57.   
  58.   
  59.     @Override  
  60.     protected void onSaveInstanceState(Bundle outState)  
  61.     {  
  62.         mMyTask.setActivity(null);  
  63.         super.onSaveInstanceState(outState);  
  64.         Log.e(TAG, "onSaveInstanceState");  
  65.     }  
  66.   
  67.     @Override  
  68.     protected void onDestroy()  
  69.     {  
  70.         Log.e(TAG, "onDestroy");  
  71.         super.onDestroy();  
  72.   
  73.     }  
  74.     /** 
  75.      * 回調 
  76.      */  
  77.     public void onTaskCompleted()  
  78.     {  
  79.         mDatas = mMyTask.getItems();  
  80.         mAdapter = new ArrayAdapter<String>(FixProblemsActivity.this,  
  81.                 android.R.layout.simple_list_item_1, mDatas);  
  82.         setListAdapter(mAdapter);  
  83.     }  
  84.   
  85. }  

在onCreate中,如果沒有開啓任務(第一次進入),開啓任務;如果已經開啓了,調用setActivity(this);

在onSaveInstanceState把當前任務加入Fragment

我設置了等待5秒,足夠旋轉三四個來回了~~~~可以看到雖然在不斷的重啓,但是絲毫不影響加載數據任務的運行和加載框的顯示~~~~

效果圖:


可以看到我在加載的時候就三心病狂的旋轉屏幕~~但是絲毫不影響顯示效果與任務的加載~~


最後,說明一下,其實不僅是屏幕旋轉需要保存數據,當用戶在使用你的app時,忽然接到一個來電,長時間沒有回到你的app界面也會造成Activity的銷燬與重建,所以一個行爲良好的App,是有必要擁有恢復數據的能力的~~。

發佈了15 篇原創文章 · 獲贊 7 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章