|
As we all know, Activity when not explicitly specify the screen orientation and configChanges, rotate the screen when the user restarts. Of course, deal with this situation, Android gives several options:
a, if it is a small amount of data, you can onSaveInstanceState () and onRestoreInstanceState () to save and restore.
Android calls onSaveInstanceState () method in your Activity destruction before, so you can store data about the state of the application in this method. Then you can restore it onCreate () or onRestoreInstanceState () method.
b, if it is a lot of data, use the Fragment holding the object needs to be restored.
c, self-managing configuration changes.
NOTE: getLastNonConfigurationInstance () has been deprecated by the above method two alternatives.
2. Difficulties
Assuming the current Activity onCreate started in an asynchronous thread to a folder in the data, of course, in order to give the user a good experience, there will be a ProgressDialog, when the data load is complete, ProgressDialog disappear settings data.
Here, if the asynchronous data after completion of loading, rotate the screen, using the a, b both methods will not be difficult, simply save data and restore data.
However, if the thread is loaded when the rotation, there will be the following questions:
a) At this point the data has not finished loading, when onCreate restarted, it will start the thread again; but the thread may still be running, and may be updated already nonexistent control, resulting in an error.
b) Close ProgressDialog code thread onPostExecutez, but if you have to kill the thread can not close until ProgressDialog.
Official c) Google does not recommend the use of ProgressDialog, here we will use the official recommended DialogFragment to create my load box, which, in fact, gives us a big problem, DialogFragment it plainly is Fragment, and the current Activity Life Periodic binding occurs, we rotate the screen will cause the destruction of Activity, of course, also affect the DialogFragment.
Here I will use a few examples, respectively, using the above three ways, and how best to 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;
/ **
* Without considering the load situation rotation, intended to avoid such a situation, the examples that follow will introduce solutions
*
* /
public class SavedInstanceStateUsingActivity extends ListActivity
{
private static final String TAG = "MainActivity";
private ListAdapter mAdapter;
private ArrayList < String> mDatas;
private DialogFragment mLoadingDialog;
private LoadDataAsyncTask mLoadDataAsyncTask;
@Override
public 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.execute ();
} Else
{
initAdapter ();
}
}
/ **
* Initialize adapter
* /
private void initAdapter ()
{
mAdapter = new ArrayAdapter < String> (
SavedInstanceStateUsingActivity.this,
android.R.layout.simple_list_item_1, mDatas);
setListAdapter (mAdapter);
}
@Override
protected void onRestoreInstanceState (Bundle state)
{
super.onRestoreInstanceState (state);
Log.e (TAG, "onRestoreInstanceState");
}
@Override
protected void onSaveInstanceState (Bundle outState)
{
super.onSaveInstanceState (outState);
Log.e (TAG, "onSaveInstanceState");
outState.putSerializable ( "mDatas", mDatas);
}
/ **
* Analog consuming operation
*
* @return
* /
private ArrayList < String> generateTimeConsumingDatas ()
{
try
{
Thread.sleep (2000);
} Catch (InterruptedException e)
{
}
return new ArrayList < String> (Arrays.asList ( "save large amounts of data through Fragment"
"OnSaveInstanceState save data"
"GetLastNonConfigurationInstance has been abandoned", "RabbitMQ", "Hadoop",
"Spark"));
}
private class LoadDataAsyncTask extends AsyncTask < Void, Void, Void>
{
@Override
protected Void doInBackground (Void ... params)
{
mDatas = generateTimeConsumingDatas ();
return null;
}
@Override
protected void onPostExecute (Void result)
{
mLoadingDialog.dismiss ();
initAdapter ();
}
}
@Override
protected void onDestroy ()
{
Log.e (TAG, "onDestroy");
super.onDestroy ();
}
}
Interface to a ListView, onCreate start an asynchronous task to load data used here Thread.sleep simulates a time-consuming operation; when the user rotates the screen to restart occurs, it will onSaveInstanceState for storage of data, the data in onCreate recovery, eliminating unnecessary re-load it again.
The result:
After the data is loaded correctly, the user continues to rotate the screen, log will continue to play: onSaveInstanceState-> onDestroy-> onCreate-> onRestoreInstanceState, verify that we are indeed re-started, but we did not go for data loading again.
If at the time of loading, rotation, an error occurs, abnormal exit (Exit Reason: NullPointException occurs () dialog.dismiss, because with the current dialog FragmentManager bound is null, there are interested can go to Debug, this not critical).
4. Use the Fragment to save the object that is used to recover data
If you restart your Activity recover large amounts of data need to re-establish a network connection, or perform other intensive operations, such as configuration changes completely restart might be a slow user experience. Further, using the system provided onSaveIntanceState () callback, use the Bundle to fully restore the state of your Activity is likely to be impractical (Bundle is not designed to carry large amounts of data (such as bitmap), and the data must be in the Bundle serialized and deserialized), it will consume large amounts of memory and cause configuration changes slowly. In this case, when your Activity because the configuration changes and reboot, you can maintain a Fragment to ease the burden of re-start. The Fragment can contain references to objects you want to maintain a stateful.
When the Android system because configuration changes off your Activity, when your Activity is identified to keep the fragments will not be destroyed. You can add such fragements in your Activity in to save stateful objects.
When the configuration is changed, the state of preservation of the object at run time in Fragment
a) inheritance Fragment, declare a reference point to your stateful objects
b) call setRetainInstance (boolean) is created when the Fragment
c) adding to the Activity in the instance Fragment
d) When the Activity restarts, use FragmentManager recovery of Fragment
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 retain
private Bitmap data;
// This method is only called once for this fragment
@Override
public void onCreate (Bundle savedInstanceState)
{
super.onCreate (savedInstanceState);
// Retain this fragment
setRetainInstance (true);
}
public void setData (Bitmap data)
{
this.data = data;
}
public Bitmap getData ()
{
return data;
}
}
Relatively simple, only need to declare the need to save the data object, and then provide getter and setter, pay attention, be sure to 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 FragmentRetainDataActivity extends Activity
{
private static final String TAG = "FragmentRetainDataActivity";
private RetainedFragment dataFragment;
private DialogFragment mLoadingDialog;
private ImageView mImageView;
private Bitmap mBitmap;
@Override
public void onCreate (Bundle savedInstanceState)
{
super.onCreate (savedInstanceState);
setContentView (R.layout.activity_main);
Log.e (TAG, "onCreate");
// Find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager ();
dataFragment = (RetainedFragment) fm.findFragmentByTag ( "data");
// Create the fragment and data the first time
if (dataFragment == null)
{
// Add the fragment
dataFragment = 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 (
"Upload / 2015_08 / 150808110773613.jpg",
new Response.Listener < Bitmap> ()
{
@Override
public void onResponse (Bitmap response)
{
mBitmap = response;
mImageView.setImageBitmap (mBitmap);
// Load the data from the web
dataFragment.setData (mBitmap);
mLoadingDialog.dismiss ();
}
}, 0, 0, Config.RGB_565, null);
newRequestQueue.add (imageRequest);
} Else
{
mImageView.setImageBitmap (mBitmap);
}
}
@Override
public void onDestroy ()
{
Log.e (TAG, "onDestroy");
super.onDestroy ();
// Store the data in the fragment
dataFragment.setData (mBitmap);
}
private Bitmap collectMyLoadedData ()
{
return dataFragment.getData ();
}
}
There is always a Volley used to load the onCreate a beautiful picture, and then in the middle of the Bitmap onDestroy storage, or add a recovery in onCreate a Fragment of reference, and then to read and set Bitmap. This method is applicable to large storage and recovery of data.
Note: There is no consideration of rotation of the screen when loading, consistent with the above issues.
5, configuration configChanges, own screen rotation processing changes
The property is set in menifest be:
< Activity
android: name = ". ConfigChangesTestActivity"
android: configChanges = "screenSize | orientation">
< / Activity>
Low version of the API only need to add orientation, while the high version is necessary 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;
/ **
*
* /
public class ConfigChangesTestActivity extends ListActivity
{
private static final String TAG = "MainActivity";
private ListAdapter mAdapter;
private ArrayList < String> mDatas;
private DialogFragment mLoadingDialog;
private LoadDataAsyncTask mLoadDataAsyncTask;
@Override
public 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.execute ();
}
/ **
* Initialize adapter
* /
private void initAdapter ()
{
mAdapter = new ArrayAdapter < String> (ConfigChangesTestActivity.this,
android.R.layout.simple_list_item_1, mDatas);
setListAdapter (mAdapter);
}
/ **
* Analog consuming operation
*
* @return
* /
private ArrayList < String> generateTimeConsumingDatas ()
{
try
{
Thread.sleep (2000);
} Catch (InterruptedException e)
{
}
return new ArrayList < String> (Arrays.asList ( "save large amounts of data through Fragment"
"OnSaveInstanceState save data"
"GetLastNonConfigurationInstance has been abandoned", "RabbitMQ", "Hadoop",
"Spark"));
}
/ **
* When the configuration is changed, it will not be restarted Activity. But this will be a callback method, the user to be processed after screen rotation
* /
@Override
public 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 < Void, Void, Void>
{
@Override
protected Void doInBackground (Void ... params)
{
mDatas = generateTimeConsumingDatas ();
return null;
}
@Override
protected void onPostExecute (Void result)
{
mLoadingDialog.dismiss ();
initAdapter ();
}
}
@Override
protected void onDestroy ()
{
Log.e (TAG, "onDestroy");
super.onDestroy ();
}
}
The first way is the code has been modified to remove the preservation and restoration of the code, rewrote onConfigurationChanged; In this case, whenever the user to rotate the screen will not be restarted Activity, and onConfigurationChanged code can be called. You can see from the renderings, anyway rotation not restart Activity.
6, the best practice of rotating screen
Below to start today's difficulties, is handling the article said at the beginning, when the asynchronous task execution, rotation, if solve the above problems.
First talk about the exploration process:
At first, I think this time the rotation is nothing more than a re-start threads, and will not cause an alarm, even if I just closed on the inside in onDestroy an asynchronous task on it. In fact, if I closed the last time the dialog will always exist; if I do not shut down, but activity will certainly be destroyed, dismiss the dialog box will be abnormal. Really very boring, and even if the dialog box is closed, the task turned off; the user rotates or cause to re-create the task from scratch loading data.
Now we hope to have a solution: Rotate the screen when loading data, does not load the task is interrupted and the user, wait boxes are displayed properly prior to loading is completed:
Fragment Of course, we also use the data to be saved, after all, this is the official recommended:
OtherRetainedFragment
package com.example.zhy_handle_runtime_change;
import Android.app.Fragment;
import android.os.Bundle;
/ **
* Save the object Fragment
*
*
* /
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
@Override
public void onCreate (Bundle savedInstanceState)
{
super.onCreate (savedInstanceState);
// Retain this fragment
setRetainInstance (true);
}
public void setData (MyAsyncTask data)
{
this.data = data;
}
public MyAsyncTask getData ()
{
return data;
}
}
And the difference between the above is not the only difference is that it objects to save an asynchronous programming task, I believe to see this, it is known a regular core of the above-mentioned problems, save an asynchronous task, in the restart, to continue the task .
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 < Void, Void, Void>
{
private FixProblemsActivity activity;
/ **
* Completion
* /
private boolean isCompleted;
/ **
* Progress box
* /
private LoadingDialog mLoadingDialog;
private List < String> items;
public MyAsyncTask (FixProblemsActivity activity)
{
this.activity = activity;
}
/ **
* Initially, shows the loading boxes
* /
@Override
protected void onPreExecute ()
{
mLoadingDialog = new LoadingDialog ();
mLoadingDialog.show (activity.getFragmentManager (), "LOADING");
}
/ **
* Loading data
* /
@Override
protected Void doInBackground (Void ... params)
{
items = loadingData ();
return null;
}
/ **
* Loading completion callback Current Activity
* /
@Override
protected void onPostExecute (Void unused)
{
isCompleted = true;
notifyActivityTaskCompleted ();
if (mLoadingDialog! = null)
mLoadingDialog.dismiss ();
}
public List < String> getItems ()
{
return items;
}
private List < String> loadingData ()
{
try
{
Thread.sleep (5000);
} Catch (InterruptedException e)
{
}
return new ArrayList < String> (Arrays.asList ( "save large amounts of data through Fragment"
"OnSaveInstanceState save data"
"GetLastNonConfigurationInstance has been abandoned", "RabbitMQ", "Hadoop",
"Spark"));
}
/ **
* Set Activity, Activity will always change because
*
* @param Activity
* /
public void setActivity (FixProblemsActivity activity)
{
// If the destruction of a Activity, Activity will be on a binding DialogFragment destruction
if (activity == null)
{
mLoadingDialog.dismiss ();
}
// Set to the current Activity
this.activity = activity;
// Open a box and wait for the current Activity bound
if (activity! = null &&! isCompleted)
{
mLoadingDialog = new LoadingDialog ();
mLoadingDialog.show (activity.getFragmentManager (), "LOADING");
}
// If completed, the notification Activity
if (isCompleted)
{
notifyActivityTaskCompleted ();
}
}
private void notifyActivityTaskCompleted ()
{
if (null! = activity)
{
activity.onTaskCompleted ();
}
}
}
Asynchronous tasks, manage a dialog when the download before the start, progress box shows the progress of the download ends box disappears, and provide a callback for the Activity. Of course, running Activity constantly reboot, we also provide setActivity method, when onDestory, will setActivity (null) prevent memory leaks, and we will close the load box the bound; when the incoming new Activity onCreate we will open again in a loaded box, of course, because the rotation of the screen does not affect the loading of data, all the data continues in the background loading. It is not 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 < String> mDatas;
private OtherRetainedFragment dataFragment;
private MyAsyncTask mMyTask;
@Override
public void onCreate (Bundle savedInstanceState)
{
super.onCreate (savedInstanceState);
Log.e (TAG, "onCreate");
// Find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager ();
dataFragment = (OtherRetainedFragment) fm.findFragmentByTag ( "data");
// Create the fragment and data the first time
if (dataFragment == null)
{
// Add the fragment
dataFragment = 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.execute ();
}
// The data is available in dataFragment.getData ()
}
@Override
protected void onRestoreInstanceState (Bundle state)
{
super.onRestoreInstanceState (state);
Log.e (TAG, "onRestoreInstanceState");
}
@Override
protected void onSaveInstanceState (Bundle outState)
{
mMyTask.setActivity (null);
super.onSaveInstanceState (outState);
Log.e (TAG, "onSaveInstanceState");
}
@Override
protected void onDestroy ()
{
Log.e (TAG, "onDestroy");
super.onDestroy ();
}
/ **
* Callback
* /
public void onTaskCompleted ()
{
mDatas = mMyTask.getItems ();
mAdapter = new ArrayAdapter < String> (FixProblemsActivity.this,
android.R.layout.simple_list_item_1, mDatas);
setListAdapter (mAdapter);
}
}
In onCreate, if the task is not turned on (first entered), open the task; if you have opened the call setActivity (this);
Add to the current task in onSaveInstanceState Fragment
I set the wait five seconds, enough to rotate back and forth three or four of the ~ ~ ~ ~ can see that although constantly restart, but did not affect the task of loading data loading operation and display box ~ ~ ~ ~
I can see the loading screen is rotated three heart mad ~ ~ but did not affect the results show that with the task of loading ~~
Finally, explain, in fact, not only is the screen rotation need to save the data, when users use your app, suddenly received a call, for a long time did not return to your app interface will cause the destruction and reconstruction of Activity, it is a behavior good App, it is necessary to have the ability to recover data ~. |
|
|
|