The best solution for Android screen Rotation Processing AsyncTask and ProgressDialog

Source: Internet
Author: User

The best solution for Android screen Rotation Processing AsyncTask and ProgressDialog

 

1. Overview

As we all know, when the screen direction and configChanges are not explicitly specified for an Activity, the user will restart when the screen is rotated. Of course, Android provides several solutions to deal with this situation:

A. for a small amount of data, you can save and restore it through onSaveInstanceState () and onRestoreInstanceState.

Android will call the onSaveInstanceState () method before destroying your Activity. Therefore, you can store application status data in this method. Then you can restore the data in the onCreate () or onRestoreInstanceState () method.

B. If there is a large amount of data, use Fragment to maintain the objects to be restored.

C. Handle configuration changes by yourself.

Note: The getLastNonConfigurationInstance () has been discarded and is replaced by method 2.

2. Difficulties

Assume that the current Activity enables an asynchronous thread to clamp data in onCreate. Of course, to give the user a good experience, there will be a ProgressDialog. When the data load is complete, ProgressDialog disappears and data is set.

Here, if the screen is rotated after the asynchronous data is loaded, it will not be difficult to use the methods a and B, but it is nothing more than to save and restore the data.

However, if a thread is being loaded and rotated, the following problems may occur:

A) At this time, the data is not loaded. When onCreate restarts, the thread will be started again. However, the previous thread may still be running, and the existing control may be updated, causing an error.

B) the code for disabling ProgressDialog is in onPostExecutez of the thread. However, if the previous thread has been killed, the previous ProgressDialog cannot be closed.

C) Google does not recommend ProgressDialog. Here we will use the officially recommended DialogFragment to create my loading box. If you do not know about it, see the "DialogFragment" dialog box on the Android official recommendation page. This actually brings us a big problem. DialogFragment is Fragment, which is bound to the lifecycle of the current Activity, and the Activity will be destroyed when we rotate the screen, of course, it will also affect DialogFragment.

Below I will use several examples, respectively using the above three methods, and how to best solve the above problems.

3. Use onSaveInstanceState () and onRestoreInstanceState () to save and restore data.

Code:

 

Package com. example. zhy_handle_runtime_change; import java. util. arrayList; import java. util. arrays; import android. app. dialogFragment; import android. app. listActivity; import android. OS. asyncTask; import android. OS. bundle; import android. util. log; import android. widget. arrayAdapter; import android. widget. listAdapter;/*** avoid this situation intentionally because it does not take into account the rotation during loading, the solution * @ author zhy **/public class SavedInstanceStateUsingActivity extends ListActivity {private static final String TAG = MainActivity; private ListAdapter mAdapter; private ArrayList
 
  
MDatas; private DialogFragment mLoadingDialog; private LoadDataAsyncTask mLoadDataAsyncTask; @ Overridepublic void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); Log. e (TAG, onCreate); initData (savedInstanceState);}/*** initialization data */private void initData (Bundle savedInstanceState) {if (savedInstanceState! = Null) mDatas = savedInstanceState. getStringArrayList (mDatas); if (mDatas = null) {mLoadingDialog = new LoadingDialog (); mLoadingDialog. show (getFragmentManager (), LoadingDialog); mLoadDataAsyncTask = new LoadDataAsyncTask();mLoadDataAsyncTask.exe cute ();} else {initAdapter () ;}/ *** initialize adapter */private void initAdapter () {mAdapter = new ArrayAdapter
  
   
(SavedInstanceStateUsingActivity. this, android. r. layout. simple_list_item_1, mDatas); setListAdapter (mAdapter) ;}@ Overrideprotected void onRestoreInstanceState (Bundle state) {super. onRestoreInstanceState (state); Log. e (TAG, onRestoreInstanceState) ;}@ Overrideprotected void onSaveInstanceState (Bundle outState) {super. onSaveInstanceState (outState); Log. e (TAG, onSaveInstanceState); outState. putSerializable (mDatas, mDatas);}/*** simulate time-consuming operations ** @ return */private ArrayList
   
    
GenerateTimeConsumingDatas () {try {Thread. sleep (2000);} catch (InterruptedException e) {} return new ArrayList
    
     
(Arrays. asList (a large amount of data is saved through Fragment, onSaveInstanceState to save data, getLastNonConfigurationInstance has been discarded, RabbitMQ, Hadoop, Spark);} private class LoadDataAsyncTask extends AsyncTask
     
      
{@ Overrideprotected Void doInBackground (Void... params) {mDatas = generateTimeConsumingDatas (); return null ;}@ Overrideprotected void onPostExecute (Void result) {mLoadingDialog. dismiss (); initAdapter () ;}@ Overrideprotected void onDestroy () {Log. e (TAG, onDestroy); super. onDestroy ();}}
     
    
   
  
 


 

The interface is a ListView. In onCreate, enable an asynchronous task to load data. Here Thread is used. sleep simulates a time-consuming operation. When a user rotates the screen and restarts, data is stored in onSaveInstanceState and restored in onCreate, it eliminates the need to load it again.

Running result:

After loading the data normally, the user continuously rotates the screen, and the log will continuously output: onSaveInstanceState-> onDestroy-> onCreate-> onRestoreInstanceState, verifying that we have restarted, however, we did not load the data again.

If rotation is performed during loading, an error occurs and the system exits abnormally (exit reason: dialog. nullPointException occurs when dismiss () occurs, because the FragmentManager bound to the current dialog box is null, and debugging is not critical if you are interested ).

:

 

 

4. Use Fragment to save objects and restore Data

To restart your Activity, you need to restore a large amount of data, establish a network connection, or perform other intensive operations, in this way, a full restart may be a slow user experience due to configuration changes. In addition, the onSaveIntanceState () provided by the system is called back, it may be unrealistic to use Bundle to completely restore the status of your Activity (Bundle is not designed to carry a large amount of data (such as bitmap ), in addition, the data in the Bundle must be serialized and deserialized. This will consume a lot of memory and cause slow configuration changes. In this case, when your Activity is restarted due to configuration changes, you can maintain a Fragment to ease the burden of restart. This Fragment can contain the reference of the stateful object you want to maintain.

When the Android system disables your Activity due to configuration changes, the fragments marked in your Activity will not be destroyed. You can add such fragements in your Activity to save stateful objects.

Objects in the state are saved in Fragment when the configuration changes during runtime.
A) inherit Fragment and declare that the reference points to your stateful object
B) When Fragment is created, setRetainInstance (boolean) is called)
C) add the Fragment instance to the Activity.
D. Use FragmentManager to restore Fragment after the Activity is restarted.
Code:

First, Fragment:

 

package com.example.zhy_handle_runtime_change;import android.app.Fragment;import android.graphics.Bitmap;import android.os.Bundle;public class RetainedFragment extends Fragment{// data object we want to retainprivate Bitmap data;// this method is only called once for this fragment@Overridepublic void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);// retain this fragmentsetRetainInstance(true);}public void setData(Bitmap data){this.data = data;}public Bitmap getData(){return data;}}

It is relatively simple. You only need to declare the data objects to be saved, and then provide getter and setter. Note that you must call setRetainInstance (true) in onCreate );

 

Then: FragmentRetainDataActivity

 

Package com. example. zhy_handle_runtime_change; import android. app. activity; import android. app. dialogFragment; import android. app. fragmentManager; import android. graphics. bitmap; import android. graphics. bitmap. config; import android. OS. bundle; import android. util. log; import android. widget. imageView; import com. android. volley. requestQueue; import com. android. volley. response; import com. android. volley. toolbox. imageRequest; import com. android. volley. toolbox. volley; public class extends Activity {private static final String TAG = custom; private extends dataFragment; private extends mLoadingDialog; private ImageView mImageView; private Bitmap mBitmap; @ Overridepublic void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); Log. e (TAG, onCreate); // find the retained fragment on activity restartsFragmentManager fm = getFragmentManager (); dataFragment = (RetainedFragment) fm. findFragmentByTag (data); // create the fragment and data the first timeif (dataFragment = null) {// add the fragmentdataFragment = new RetainedFragment (); fm. beginTransaction (). add (dataFragment, data ). commit ();} mBitmap = collectMyLoadedData (); initData (); // the data is available in dataFragment. getData ()}/*** initialization data */private void initData () {mImageView = (ImageView) findViewById (R. id. id_imageView); if (mBitmap = null) {mLoadingDialog = new LoadingDialog (); mLoadingDialog. show (getFragmentManager (), LOADING_DIALOG); RequestQueue newRequestQueue = Volley. newRequestQueue (FragmentRetainDataActivity. this); ImageRequest imageRequest = new ImageRequest (http://img.my.csdn.net/uploads/201407/18/1405652589_5125.jpg,new Response. listener
 
  
() {@ Overridepublic void onResponse (Bitmap response) {mBitmap = response; mImageView. setImageBitmap (mBitmap); // load the data from the webdataFragment. setData (mBitmap); mLoadingDialog. dismiss () ;}, 0, 0, Config. RGB_565, null); newRequestQueue. add (imageRequest);} else {mImageView. setImageBitmap (mBitmap) ;}@ Overridepublic void onDestroy () {Log. e (TAG, onDestroy); super. onDestroy (); // store the data in the fragmentdataFragment. setData (mBitmap);} private Bitmap collectMyLoadedData () {return dataFragment. getData ();}}
 


 

Here, onCreate always uses Volley to load a beauty photo, store Bitmap in onDestroy, and add or restore a reference to Fragment in onCreate, then, read and set Bitmap. This method is applicable to storage and recovery of relatively large data.

Note: rotating the screen during loading is not considered here. The problem is the same as above.

:

5. Configure configChanges to handle the screen rotation changes.

Set attributes in menifest:

 

            
For earlier versions of APIS, you only need to add orientation, and for later versions, you need to add screenSize.

 

ConfigChangesTestActivity

 

Package com. example. zhy_handle_runtime_change; import java. util. arrayList; import java. util. arrays; import android. app. dialogFragment; import android. app. listActivity; import android. content. res. configuration; import android. OS. asyncTask; import android. OS. bundle; import android. util. log; import android. widget. arrayAdapter; import android. widget. listAdapter; import android. widget. toast;/*** @ author zhy **/public class ConfigChangesTestActivity extends ListActivity {private static final String TAG = MainActivity; private ListAdapter mAdapter; private ArrayList
 
  
MDatas; private DialogFragment mLoadingDialog; private LoadDataAsyncTask mLoadDataAsyncTask; @ Overridepublic void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); Log. e (TAG, onCreate); initData (savedInstanceState);}/*** initialization data */private void initData (Bundle savedInstanceState) {mLoadingDialog = new LoadingDialog (); mLoadingDialog. show (getFragmentManager (), LoadingDialog); mLoadDataAsyncTask = new LoadDataAsyncTask();mLoadDataAsyncTask.exe cute () ;}/ *** initialize adapter */private void initAdapter () {mAdapter = new ArrayAdapter
  
   
(ConfigChangesTestActivity. this, android. R. layout. simple_list_item_1, mDatas); setListAdapter (mAdapter);}/*** simulate time-consuming operations ** @ return */private ArrayList
   
    
GenerateTimeConsumingDatas () {try {Thread. sleep (2000);} catch (InterruptedException e) {} return new ArrayList
    
     
(Arrays. asList (using Fragment to save a large amount of data, onSaveInstanceState to save data, getLastNonConfigurationInstance has been discarded, RabbitMQ, Hadoop, Spark);}/*** when the configuration changes, activity is not restarted. However, this method will be called back, and the user will perform processing after rotating the screen */@ Overridepublic void onConfigurationChanged (Configuration newConfig) {super. onConfigurationChanged (newConfig); if (newConfig. orientation = Configuration. ORIENTATION_LANDSCAPE) {Toast. makeText (this, landscape, Toast. LENGTH_SHORT ). show ();} else if (newConfig. orientation = Configuration. ORIENTATION_PORTRAIT) {Toast. makeText (this, portrait, Toast. LENGTH_SHORT ). show () ;}} private class LoadDataAsyncTask extends AsyncTask
     
      
{@ Overrideprotected Void doInBackground (Void... params) {mDatas = generateTimeConsumingDatas (); return null ;}@ Overrideprotected void onPostExecute (Void result) {mLoadingDialog. dismiss (); initAdapter () ;}@ Overrideprotected void onDestroy () {Log. e (TAG, onDestroy); super. onDestroy ();}}
     
    
   
  
 

The code in the first method is modified, the saved and recovered code is removed, and onConfigurationChanged is rewritten. At this time, the Activity is not restarted no matter when the user rotates the screen, the code in onConfigurationChanged can be called. As you can see, no matter how you rotate, the Activity will not be restarted.

:

6. Best practices for rotating screens

The following is the difficulty to start today, that is, to handle what was mentioned at the beginning of the article. When an asynchronous task is being executed, it is rotated to solve the above problem.

First, let's talk about the exploration process:

At first, I thought that the rotation was nothing more than starting a thread again, and it would not cause exceptions. I only needed to close the previous asynchronous task in onDestroy. In fact, if I close it, the last dialog box will always exist; if I do not close it, but the activity will be destroyed, and the dismiss of the dialog box will also cause an exception. It really hurts, and even if the dialog box is closed, the task is closed. User rotation will re-create the task and load data from the beginning.

Here we hope to have a solution: rotating the screen when loading data does not interrupt the loading task, and for users, waiting for the box to be properly displayed before loading is complete:

Of course, we also use Fragment for data storage. After all, this is officially recommended:

OtherRetainedFragment

 

Package com. example. zhy_handle_runtime_change; import android. app. fragment; import android. OS. bundle; /*** Save the object's Fragment *** @ author zhy **/public class OtherRetainedFragment extends Fragment {// data object we want to retain // save an asynchronous task private MyAsyncTask data; // this method is only called once for this fragment @ Overridepublic void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); // retain this fragmentsetRetainInstance (true);} public void setData (MyAsyncTask data) {this. data = data;} public MyAsyncTask getData () {return data ;}}

It is not very different from the above. The only difference is that the object to be saved is programmed as an asynchronous task. I believe this is the core of the above problems, save an asynchronous task and continue the task at restart.

 

 

Package com. example. zhy_handle_runtime_change; import java. util. ArrayList; import java. util. Arrays; import java. util. List; import android. OS. AsyncTask; public class MyAsyncTask extends AsyncTask
 
  
{Private FixProblemsActivity;/*** complete? */private boolean isCompleted;/*** progress box */private LoadingDialog mLoadingDialog; private List
  
   
Items; public MyAsyncTask (FixProblemsActivity) {this. when activity = activity;}/*** starts, the loading box */@ Overrideprotected void onPreExecute () {mLoadingDialog = new LoadingDialog (); mLoadingDialog is displayed. show (activity. getFragmentManager (), LOADING);}/*** load data */@ Overrideprotected Void doInBackground (Void... params) {items = loadingData (); return null;}/*** call back the current Activity after loading completion */@ Overrideprotected void onPostExecut E (Void unused) {isCompleted = true; yyactivitytaskcompleted (); if (mLoadingDialog! = Null) mLoadingDialog. dismiss ();} public List
   
    
GetItems () {return items;} private List
    
     
LoadingData () {try {Thread. sleep (5000);} catch (InterruptedException e) {} return new ArrayList
     
      
(Arrays. asList (using Fragment to save a large amount of data, onSaveInstanceState to save data, getLastNonConfigurationInstance has been discarded, RabbitMQ, Hadoop, Spark);}/*** set Activity, because the Activity will always change ** @ param activity */public void setActivity (FixProblemsActivity activity) {// if the previous Activity is destroyed, destroy the DialogFragment bound to the previous Activity if (activity = null) {mLoadingDialog. dismiss ();} // set it to the current Activitythis. activity = activity; // open a wait box bound to the current Activity if (act Ivity! = Null &&! IsCompleted) {mLoadingDialog = new LoadingDialog (); mLoadingDialog. show (activity. getFragmentManager (), LOADING);} // if the execution is complete, Activityif (isCompleted) {policyactivitytaskcompleted () ;}} private void policyactivitytaskcompleted () {if (null! = Activity) {activity. onTaskCompleted ();}}}
     
    
   
  
 

In an asynchronous task, manage a dialog box. Before the download starts, the Progress box is displayed. The download end progress box disappears and a callback is provided for the Activity. Of course, during the running process, the Activity is continuously restarted, and we also provide the setActivity method. When onDestory is used, setActivity (null) will prevent memory leakage, and we will also close the bound loading box; when onCreate passes in a new Activity, we will open a loading box again. Of course, because the rotation of the screen does not affect the loaded data, all the background data will continue to be loaded. Is it perfect ~~

 

Main Activity:

 

Package com. example. zhy_handle_runtime_change; import java. util. list; import android. app. fragmentManager; import android. app. listActivity; import android. OS. bundle; import android. util. log; import android. widget. arrayAdapter; import android. widget. listAdapter; public class FixProblemsActivity extends ListActivity {private static final String TAG = MainActivity; private ListAdapter mAdapter; private List
 
  
MDatas; private OtherRetainedFragment dataFragment; private MyAsyncTask mMyTask; @ Overridepublic void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); Log. e (TAG, onCreate); // find the retained fragment on activity restartsFragmentManager fm = getFragmentManager (); dataFragment = (OtherRetainedFragment) fm. findFragmentByTag (data); // create the fragment and data the first timeif (da TaFragment = null) {// add the fragmentdataFragment = new OtherRetainedFragment (); fm. beginTransaction (). add (dataFragment, data ). commit ();} mMyTask = dataFragment. getData (); if (mMyTask! = Null) {mMyTask. setActivity (this);} else {mMyTask = new myasynctask(this?#datafragment.setdata(mmytask=~mmytask.exe cute ();} // the data is available in dataFragment. getData ()} @ Overrideprotected void onRestoreInstanceState (Bundle state) {super. onRestoreInstanceState (state); Log. e (TAG, onRestoreInstanceState) ;}@ Overrideprotected void onSaveInstanceState (Bundle outState) {mMyTask. setActivity (null); super. onSaveInstanceState (outState); Log. e (TAG, onSaveInstanceState) ;}@ Overrideprotected void onDestroy () {Log. e (TAG, onDestroy); super. onDestroy ();}/*** callback */public void onTaskCompleted () {mDatas = mMyTask. getItems (); mAdapter = new ArrayAdapter
  
   
(FixProblemsActivity. this, android. R. layout. simple_list_item_1, mDatas); setListAdapter (mAdapter );}}
  
 

In onCreate, if the task is not enabled (first entry), enable the task; if the task is enabled, call setActivity (this );

 

Add the current task to Fragment in onSaveInstanceState

I set it to wait for 5 seconds. It's enough to rotate three or four times ~~~~ It can be seen that the data loading task is continuously restarted, but it does not affect the running of the Data Loading task and the display of the loading box ~~~~

:

We can see that when I loaded the screen, the screen was turned around ~~ However, the display effect and Task Loading are not affected at all ~~

 

Finally, it is explained that not only does screen rotation need to save data, but when a user is using your app, he suddenly receives a call, if you do not return to your app interface for a long time, the Activity will be destroyed and rebuilt. Therefore, it is necessary for an App with good behavior to have the ability to recover data ~~.

 

Reference documents:

Http://developer.android.com/guide/topics/resources/runtime-changes.html

 

Http://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/


If you have any questions, please leave a message.

 

Download source code


 

 

 

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.