The Android would create your Activity again to load the proper configuration including layout. The lifecycle methods(onCreate - onStart - onResume) will be called again and you can get data onCreate() if you have saved any data onSavedInstance(). However, your AsyncTask lost pipeline to your Activity and the code of executing the AsyncTask will be called again. This is not definitely what we want.
Let's have a look at code how to avoid it:I'm going to create a simple app to display weather using Open Weather API for demo. We need one Activity, one Fragment for UI ListView first.
MainActivity.javaimport android.support.v7.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (savedInstanceState == null) { getSupportFragmentManager().beginTransaction() .add(R.id.container, new MainFragment()) .commit(); } } }AppCompatActivity is new class introduced begining AppCompat library v21.1.1. ActionBar Activity has been deprecated. MainFragment.java
public class MainFragment extends Fragment { private ProgressBar mProgressBar; private ListView mListView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); mProgressBar = (ProgressBar)rootView.findViewById(R.id.progressBar); mListView = (ListView)rootView.findViewById(android.R.id.list); return rootView; } }res/layout/fragment_main.xml
It's just normal implementation so far. From now, it's important part. Let's create AsyncTask. But it should not lost the pipe line to the MainActivity. How can we achieve this?
How about we use a Fragment without UI? If then, the Fragment will not be affected by device rotation. Fortunately you can use a Fragment without layout. We can create a dummy Fragment and make it retained calling setRetained(true)
. This can only be used with fragments not in the back stack like our case. The fragment lifecycle will be slightly different when an activity is recreated, which means onDestory() will not be called but onDetach() will be still called. And then we put the AsyncTask inside of the dummy Fragment.
public class TaskFragment extends Fragment { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } private class WeatherTask extends AsyncTask{ @Override protected ArrayList doInBackground(String... params) { String cityName = params[0]; return WeatherFetcher.getForecastJson(cityName); } } }
WeatherFetcher.getForecastJson
is to get JSON data using Open Weather API and parse to return an ArrayList of weather forecast.
Now, we have to communicate MainFragment to display forecast list. You can use an interface to communicate between Activity and Fragment or Fragments. We can define an Interface inside of DummyFragment for this:
public class TaskFragment extends Fragment { ...... public static interface Callback { public void onReady(TaskFragment fragment); public void onPreExecute(); public void onProgressUpdate(Integer... values); public void onPostExecute(ArrayListThe interface has almost similar method to AsyncTask. Of course, you can use different prototype depending on your needs. Now, you have to implement DummyFragment.Callback in MainFragment to communicated with DummyFragment.forecasts); public void onCancelled(); } }
public class MainFragment extends Fragment implements TaskFragment.Callback{ @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); //if (savedInstanceState != null){ //Recover your data here //mProgressBar.setProgress(savedInstanceState.getInt(KEY_PROGRESS)); //} FragmentManager fm = getActivity().getSupportFragmentManager(); mTaskFragment= (TaskFragment)fm.findFragmentByTag(ASYNC_TASK); if (mTaskFragment == null){ mTaskFragment = new TaskFragment(); mTaskFragment.setTargetFragment(this, 0); fm.beginTransaction().add(mTaskFragment, ASYNC_TASK).commit(); } if (!mTaskFragment.isRunning()){ mTaskFragment.start("Sydney"); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); //Save your data here //outState.putInt(KEY_PROGRESS, mProgressBar.getProgress()); } @Override public void onPreExecute() { mProgressBar.setVisibility(View.VISIBLE); } @Override public void onProgressUpdate(Integer... values) { //Do something } @Override public void onPostExecute(ArrayListforecasts) { if (forecasts != null) { if (mAdapter == null) { mAdapter = new ForecastAdapter(getActivity(), forecasts); mListView.setAdapter(mAdapter); } else { mAdapter.setItems(forecasts); } } mProgressBar.setVisibility(View.GONE); } @Override public void onCancelled() { mProgressBar.setVisibility(View.GONE); } }
You have to call callback methods in the DummyFragment.
public class TaskFragment extends Fragment { private boolean isRunning = false; private Callback mCallback = null; private WeatherTask mTask = null; @Override public void onAttach(Activity activity) { super.onAttach(activity); if (!(getTargetFragment() instanceof Callback)) { throw new IllegalStateException("Target fragment must implement the Callback interface."); } mCallback = (Callback)getTargetFragment(); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } public void start(String cityName){ if (!isRunning){ mTask = new WeatherTask(); mTask.execute(cityName); } } public void cancel() { if (mCallback != null) mCallback.onCancelled(); if (mTask != null) { mTask.cancel(false); mTask = null; } isRunning = false; } public boolean isRunning() { return isRunning; } private class WeatherTask extends AsyncTask{ @Override protected ArrayList doInBackground(String... params) { if (!isCancelled()) { String cityName = params[0]; return WeatherFetcher.getForecastJson(cityName); } return null; } @Override protected void onPreExecute() { if (mCallback!=null) mCallback.onPreExecute(); isRunning = true; } @Override protected void onProgressUpdate(Integer... values) { if (mCallback!=null) mCallback.onProgressUpdate(values); isRunning = true; } @Override protected void onPostExecute(ArrayList forecasts) { if (mCallback!=null) mCallback.onPostExecute(forecasts); isRunning = false; } @Override protected void onCancelled() { if (mCallback!=null) mCallback.onCancelled(); isRunning = false; } } }
Lifecycle of the fragments
Lifecylcle of the MainFragment and TaskFragment will be like below when a device is rotated.
MainFragment.onStop()You can view the source code on Github.
TaskFragment.onStop()
MainFragment.onDestroy()
MainFragment.onDetach()
TaskFragment.onDetach()
MainFragment.onAttach()
MainFragment.onCreate()
TaskFragment.onAttach()
MainFragment.onActivityCreated()
MainFragment.onStart()
TaskFragment.onStart()
MainFragment.onResume()
TaskFragment.onResume()
Comments
Post a Comment