アンドロイドの断片。画面の回転または構成の変更中にAsyncTaskを保持する
質問
私はスマートフォン/タブレットアプリで作業しており、1つのAPKのみを使用し、画面サイズに応じて必要に応じてリソースをロードしています。
このアプリは、今だけの活動ベースであることまで正常に動作しています。これは、画面が回転したり、通信中に構成の変更が発生した場合でも動作させるために、アクティビティでAsyncTasksとProgressDialogsを処理する方法のモッククラスです。
私は活動のレクリエーションを避けるためにマニフェストを変更しませんが、私がそれをやりたくない理由はたくさんありますが、主に公式のドク
public class Login extends Activity {
static ProgressDialog pd;
AsyncTask<String, Void, Boolean> asyncLoginThread;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.login);
//SETUP UI OBJECTS
restoreAsyncTask();
}
@Override
public Object onRetainNonConfigurationInstance() {
if (pd != null) pd.dismiss();
if (asyncLoginThread != null) return (asyncLoginThread);
return super.onRetainNonConfigurationInstance();
}
private void restoreAsyncTask();() {
pd = new ProgressDialog(Login.this);
if (getLastNonConfigurationInstance() != null) {
asyncLoginThread = (AsyncTask<String, Void, Boolean>) getLastNonConfigurationInstance();
if (asyncLoginThread != null) {
if (!(asyncLoginThread.getStatus()
.equals(AsyncTask.Status.FINISHED))) {
showProgressDialog();
}
}
}
}
public class LoginThread extends AsyncTask<String, Void, Boolean> {
@Override
protected Boolean doInBackground(String... args) {
try {
//Connect to WS, recieve a JSON/XML Response
//Place it somewhere I can use it.
} catch (Exception e) {
return true;
}
return true;
}
protected void onPostExecute(Boolean result) {
if (result) {
pd.dismiss();
//Handle the response. Either deny entry or launch new Login Succesful Activity
}
}
}
}
このコードは正常に動作しています、私は苦情なしで約10.000人のユーザーを持っているので、このロジックを新しいフラグメントベースのデザインにコピーす
ここにLoginFragmentがあります:
public class LoginFragment extends Fragment {
FragmentActivity parentActivity;
static ProgressDialog pd;
AsyncTask<String, Void, Boolean> asyncLoginThread;
public interface OnLoginSuccessfulListener {
public void onLoginSuccessful(GlobalContainer globalContainer);
}
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
//Save some stuff for the UI State
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setRetainInstance(true);
//If I setRetainInstance(true), savedInstanceState is always null. Besides that, when loading UI State, a NPE is thrown when looking for UI Objects.
parentActivity = getActivity();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
loginSuccessfulListener = (OnLoginSuccessfulListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnLoginSuccessfulListener");
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
RelativeLayout loginLayout = (RelativeLayout) inflater.inflate(R.layout.login, container, false);
return loginLayout;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//SETUP UI OBJECTS
if(savedInstanceState != null){
//Reload UI state. Im doing this properly, keeping the content of the UI objects, not the object it self to avoid memory leaks.
}
}
public class LoginThread extends AsyncTask<String, Void, Boolean> {
@Override
protected Boolean doInBackground(String... args) {
try {
//Connect to WS, recieve a JSON/XML Response
//Place it somewhere I can use it.
} catch (Exception e) {
return true;
}
return true;
}
protected void onPostExecute(Boolean result) {
if (result) {
pd.dismiss();
//Handle the response. Either deny entry or launch new Login Succesful Activity
}
}
}
}
}
私は使うことができません onRetainNonConfigurationInstance()
フラグメントではなくアクティビティから呼び出される必要があるため、同じことが言えます getLastNonConfigurationInstance()
.私は答えなしでここでいくつかの同様の質問を読みました。
私は、このようなものを断片的に適切に整理するためにいくつかの作業が必要になるかもしれないことを理解しています。
構成変更中にAsyncTaskを保持する適切な方法は何でしょうか、まだ実行中の場合は、AsyncTaskがFragmentの内部クラスであり、AsyncTaskを呼び出すのはFragment自体であることを考慮して、progressDialogを表示します。execute()?
解決
フラグメントは実際にこれをはるかに簡単にすることができます。メソッドを使用するだけです 断片だsetRetainInstance(ブール値) fragmentインスタンスを構成の変更間も保持する。これは、以下の推奨される代替品であることに注意してください 活動。o n r e t a i n nonconfigurationinstance() ドキュメントで。
何らかの理由で保持されたフラグメントを本当に使用したくない場合は、他のアプローチを取ることができます。各フラグメントには、次のように返される一意の識別子があることに注意してください 断片だgetId().また、設定の変更のためにフラグメントが破棄されているかどうかを調べることもできます 断片だgetActivity()。isChangingConfigurations().したがって、AsyncTask(onStop()またはonDestroy()の可能性が最も高い)を停止することにした時点で、たとえば、構成が変更されているかどうかを確認し、その場合はfragmentのidの下の静的SparseArrayに貼り付けてから、onCreate()またはonStart()でasynctaskがあるかどうかを確認できますスパース配列で利用可能。
他のヒント
私はあなたが以下に詳述されている私の非常に包括的で実用的な例を楽しむと思います。
- 回転が機能し、ダイアログが存続します。
- 戻るボタンを押すと、タスクとダイアログをキャンセルできます(この動作が必要な場合)。
- フラグメントを使用します。
- デバイスが回転すると、アクティビティの下にあるフラグメントのレイアウトが適切に変更されます。
- 完全なソースコードのダウンロードがあり、 プリコンパイルされたAPK したがって、動作があなたが望むものであるかどうかを見ることができます。
編集
Brad Larsonが要求したように、私は以下のリンクされた解決策のほとんどを再現しました。また、私はそれを投稿して以来、私は指摘されています AsyncTaskLoader
.私はそれが同じ問題に完全に適用可能であるかどうかはわかりませんが、とにかくそれをチェックアウトする必要があります。
を使用して AsyncTask
プログレスダイアログとデバイスの回転を使用します。
実用的な解決策!
私は最終的に動作するようにすべてを持っています。私のコードには次の機能があります:
- A
Fragment
そのレイアウトは向きに応じて変化します。 - An
AsyncTask
あなたはいくつかの仕事をすることができます。 - A
DialogFragment
これは、進行状況バー(不確定なスピナーだけでなく)にタスクの進行状況を示しています。 - 回転は、タスクを中断したり、ダイアログを閉じたりすることなく機能します。
- 戻るボタンはダイアログを閉じてタスクをキャンセルします(ただし、この動作はかなり簡単に変更できます)。
私は仕事性の組み合わせが他のどこにも見られるとは思わない。
基本的な考え方は以下の通りである。があります。 MainActivity
単一のフラグメントを含むクラス - MainFragment
. MainFragment
水平方向と垂直方向のための異なるレイアウトを持っており、 setRetainInstance()
レイアウトを変更できるようにfalseです。これは、デバイスの向きが変更されたときに、両方のことを意味します MainActivity
と MainFragment
完全に破壊され、再作成されます。
別に私達は持っています MyTask
(から拡張された AsyncTask
)これはすべての作業を行います。私たちはそれを保存することはできません MainFragment
それは破壊され、Googleは次のようなものを使用することを推奨していないためです setRetainNonInstanceConfiguration()
.それはとにかく常に利用可能ではなく、せいぜい醜いハックです。代わりに私たちは店 MyTask
別の断片では、 DialogFragment
と呼ばれる TaskFragment
. これは... 断片 ウィル 持っている setRetainInstance()
trueに設定すると、デバイスが回転すると、このフラグメントは破棄されません。 MyTask
が保持される。
最後に、私たちは伝える必要があります TaskFragment
誰がそれが終了したときに通知するために、我々はそれを使用して行います setTargetFragment(<the MainFragment>)
私たちがそれを作成するとき。デバイスが回転しているとき、 MainFragment
が破棄され、新しいインスタンスが作成されると、我々は使用します FragmentManager
(そのタグに基づいて)ダイアログを見つけて、次のようにします setTargetFragment(<the new MainFragment>)
.それはかなりそれです。
私がする必要があった他の二つのことがありました:最初にダイアログが閉じられたときにタスクをキャンセルし、次にdismissメッセージをnullに設定します。
コード
私はレイアウトをリストしません、彼らはかなり明白であり、あなたは以下のプロジェクトのダウンロードでそれらを見つけることができます。
メインアクティビティ
これはかなり簡単です。このアクティビティにコールバックを追加して、タスクがいつ終了したかを知るようにしましたが、それは必要ないかもしれません。主に、fragment-activityコールバックメカニズムは非常にきちんとしていて、これまで見たことがないかもしれないので、私はちょうどfragment-activityコールバックメカニ
public class MainActivity extends Activity implements MainFragment.Callbacks
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onTaskFinished()
{
// Hooray. A toast to our success.
Toast.makeText(this, "Task finished!", Toast.LENGTH_LONG).show();
// NB: I'm going to blow your mind again: the "int duration" parameter of makeText *isn't*
// the duration in milliseconds. ANDROID Y U NO ENUM?
}
}
メインフラグメント
それは長いですが、それだけの価値があります!
public class MainFragment extends Fragment implements OnClickListener
{
// This code up to onDetach() is all to get easy callbacks to the Activity.
private Callbacks mCallbacks = sDummyCallbacks;
public interface Callbacks
{
public void onTaskFinished();
}
private static Callbacks sDummyCallbacks = new Callbacks()
{
public void onTaskFinished() { }
};
@Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
if (!(activity instanceof Callbacks))
{
throw new IllegalStateException("Activity must implement fragment's callbacks.");
}
mCallbacks = (Callbacks) activity;
}
@Override
public void onDetach()
{
super.onDetach();
mCallbacks = sDummyCallbacks;
}
// Save a reference to the fragment manager. This is initialised in onCreate().
private FragmentManager mFM;
// Code to identify the fragment that is calling onActivityResult(). We don't really need
// this since we only have one fragment to deal with.
static final int TASK_FRAGMENT = 0;
// Tag so we can find the task fragment again, in another instance of this fragment after rotation.
static final String TASK_FRAGMENT_TAG = "task";
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// At this point the fragment may have been recreated due to a rotation,
// and there may be a TaskFragment lying around. So see if we can find it.
mFM = getFragmentManager();
// Check to see if we have retained the worker fragment.
TaskFragment taskFragment = (TaskFragment)mFM.findFragmentByTag(TASK_FRAGMENT_TAG);
if (taskFragment != null)
{
// Update the target fragment so it goes to this fragment instead of the old one.
// This will also allow the GC to reclaim the old MainFragment, which the TaskFragment
// keeps a reference to. Note that I looked in the code and setTargetFragment() doesn't
// use weak references. To be sure you aren't leaking, you may wish to make your own
// setTargetFragment() which does.
taskFragment.setTargetFragment(this, TASK_FRAGMENT);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
return inflater.inflate(R.layout.fragment_main, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
// Callback for the "start task" button. I originally used the XML onClick()
// but it goes to the Activity instead.
view.findViewById(R.id.taskButton).setOnClickListener(this);
}
@Override
public void onClick(View v)
{
// We only have one click listener so we know it is the "Start Task" button.
// We will create a new TaskFragment.
TaskFragment taskFragment = new TaskFragment();
// And create a task for it to monitor. In this implementation the taskFragment
// executes the task, but you could change it so that it is started here.
taskFragment.setTask(new MyTask());
// And tell it to call onActivityResult() on this fragment.
taskFragment.setTargetFragment(this, TASK_FRAGMENT);
// Show the fragment.
// I'm not sure which of the following two lines is best to use but this one works well.
taskFragment.show(mFM, TASK_FRAGMENT_TAG);
// mFM.beginTransaction().add(taskFragment, TASK_FRAGMENT_TAG).commit();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == TASK_FRAGMENT && resultCode == Activity.RESULT_OK)
{
// Inform the activity.
mCallbacks.onTaskFinished();
}
}
タスクフラグメント
// This and the other inner class can be in separate files if you like.
// There's no reason they need to be inner classes other than keeping everything together.
public static class TaskFragment extends DialogFragment
{
// The task we are running.
MyTask mTask;
ProgressBar mProgressBar;
public void setTask(MyTask task)
{
mTask = task;
// Tell the AsyncTask to call updateProgress() and taskFinished() on this fragment.
mTask.setFragment(this);
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Retain this instance so it isn't destroyed when MainActivity and
// MainFragment change configuration.
setRetainInstance(true);
// Start the task! You could move this outside this activity if you want.
if (mTask != null)
mTask.execute();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_task, container);
mProgressBar = (ProgressBar)view.findViewById(R.id.progressBar);
getDialog().setTitle("Progress Dialog");
// If you're doing a long task, you probably don't want people to cancel
// it just by tapping the screen!
getDialog().setCanceledOnTouchOutside(false);
return view;
}
// This is to work around what is apparently a bug. If you don't have it
// here the dialog will be dismissed on rotation, so tell it not to dismiss.
@Override
public void onDestroyView()
{
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
// Also when we are dismissed we need to cancel the task.
@Override
public void onDismiss(DialogInterface dialog)
{
super.onDismiss(dialog);
// If true, the thread is interrupted immediately, which may do bad things.
// If false, it guarantees a result is never returned (onPostExecute() isn't called)
// but you have to repeatedly call isCancelled() in your doInBackground()
// function to check if it should exit. For some tasks that might not be feasible.
if (mTask != null) {
mTask.cancel(false);
}
// You don't really need this if you don't want.
if (getTargetFragment() != null)
getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_CANCELED, null);
}
@Override
public void onResume()
{
super.onResume();
// This is a little hacky, but we will see if the task has finished while we weren't
// in this activity, and then we can dismiss ourselves.
if (mTask == null)
dismiss();
}
// This is called by the AsyncTask.
public void updateProgress(int percent)
{
mProgressBar.setProgress(percent);
}
// This is also called by the AsyncTask.
public void taskFinished()
{
// Make sure we check if it is resumed because we will crash if trying to dismiss the dialog
// after the user has switched to another app.
if (isResumed())
dismiss();
// If we aren't resumed, setting the task to null will allow us to dimiss ourselves in
// onResume().
mTask = null;
// Tell the fragment that we are done.
if (getTargetFragment() != null)
getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_OK, null);
}
}
MyTask
// This is a fairly standard AsyncTask that does some dummy work.
public static class MyTask extends AsyncTask<Void, Void, Void>
{
TaskFragment mFragment;
int mProgress = 0;
void setFragment(TaskFragment fragment)
{
mFragment = fragment;
}
@Override
protected Void doInBackground(Void... params)
{
// Do some longish task. This should be a task that we don't really
// care about continuing
// if the user exits the app.
// Examples of these things:
// * Logging in to an app.
// * Downloading something for the user to view.
// * Calculating something for the user to view.
// Examples of where you should probably use a service instead:
// * Downloading files for the user to save (like the browser does).
// * Sending messages to people.
// * Uploading data to a server.
for (int i = 0; i < 10; i++)
{
// Check if this has been cancelled, e.g. when the dialog is dismissed.
if (isCancelled())
return null;
SystemClock.sleep(500);
mProgress = i * 10;
publishProgress();
}
return null;
}
@Override
protected void onProgressUpdate(Void... unused)
{
if (mFragment == null)
return;
mFragment.updateProgress(mProgress);
}
@Override
protected void onPostExecute(Void unused)
{
if (mFragment == null)
return;
mFragment.taskFinished();
}
}
}
サンプルプロジェクトのダウンロード
ここにあります ソースコード と をダウンロードしてください。.申し訳ありませんが、ADTはプロジェクトを作成する前にサポートライブラリを追加することを主張しました。私はあなたがそれを削除することができます確信しています。
私は最近、 記事を掲載しました retainedを使用した構成変更の処理方法の説明 Fragment
s.それは保持の問題を解決します AsyncTask
回転を越えてうまく変化します。
TL;DRはあなたのホストを使用することです AsyncTask
aの中 Fragment
, 、コール setRetainInstance(true)
上の Fragment
, 、および報告 AsyncTask
'sの進捗状況/結果はそれに戻っています Activity
(またはそれはターゲットです Fragment
, 、あなたが@Timmmmによって記述されたアプローチを使用することを選択した場合)を介して保持されます Fragment
.
私の最初の提案は 内部非同期タスクを避ける, 、あなたは私がこれと答えについて尋ねた質問を読むことができます: アンドロイド:AsyncTaskの推奨事項:プライベートクラスまたはパブリッククラス?
その後、私はnon-inner andを使い始めました。..今、私は多くの利点を見ています。
2つ目は、実行中のAsyncTaskの参照を次の場所に保持することです。 Application
クラス - http://developer.android.com/reference/android/app/Application.html
AsyncTaskを起動するたびに、アプリケーションに設定し、終了するとnullに設定します。
Fragment/activityが起動すると、AsyncTaskが実行されているかどうかを確認し(アプリケーションでnullかどうかを確認して)、内部の参照を必要なもの(activity、fragmentなど)に設定して
これはあなたの問題を解決します:特定の時点で実行されているAsyncTaskが1つしかない場合は、簡単な参照を追加できます:
AsyncTask<?,?,?> asyncTask = null;
それ以外の場合は、それらへの参照を持つHashMapを適用してください。
進行状況ダイアログは、まったく同じ原則に従うことができます。
このためにAsyncTaskLoadersを使用する方法を思いつきました。それは非常に使いやすく、より少ないオーバーヘッドIMOを必要とします。.
基本的には、次のようなAsyncTaskLoaderを作成します:
public class MyAsyncTaskLoader extends AsyncTaskLoader {
Result mResult;
public HttpAsyncTaskLoader(Context context) {
super(context);
}
protected void onStartLoading() {
super.onStartLoading();
if (mResult != null) {
deliverResult(mResult);
}
if (takeContentChanged() || mResult == null) {
forceLoad();
}
}
@Override
public Result loadInBackground() {
SystemClock.sleep(500);
mResult = new Result();
return mResult;
}
}
次に、ボタンがクリックされたときに上記のAsyncTaskLoaderを使用するアクティビティで:
public class MyActivityWithBackgroundWork extends FragmentActivity implements LoaderManager.LoaderCallbacks<Result> {
private String username,password;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.mylayout);
//this is only used to reconnect to the loader if it already started
//before the orientation changed
Loader loader = getSupportLoaderManager().getLoader(0);
if (loader != null) {
getSupportLoaderManager().initLoader(0, null, this);
}
}
public void doBackgroundWorkOnClick(View button) {
//might want to disable the button while you are doing work
//to prevent user from pressing it again.
//Call resetLoader because calling initLoader will return
//the previous result if there was one and we may want to do new work
//each time
getSupportLoaderManager().resetLoader(0, null, this);
}
@Override
public Loader<Result> onCreateLoader(int i, Bundle bundle) {
//might want to start a progress bar
return new MyAsyncTaskLoader(this);
}
@Override
public void onLoadFinished(Loader<LoginResponse> loginLoader,
LoginResponse loginResponse)
{
//handle result
}
@Override
public void onLoaderReset(Loader<LoginResponse> responseAndJsonHolderLoader)
{
//remove references to previous loader resources
}
}
これは向きの変更をうまく処理しているようで、バックグラウンドタスクは回転中も続行されます。
注意すべきいくつかのこと:
- OnCreateでasynctaskloaderに再接続すると、以前の結果でonLoadFinished()に呼び出されます(要求が完了したとすでに通知されていた場合でも)。これは実際にはほとんどの場合良い動作ですが、処理が難しい場合があります。これを処理する方法はたくさんあると思いますが、私がしたことはローダーと呼ばれていました。onloadfinishedでabandon()を放棄します。次に、oncreateのチェックインを追加して、ローダーがまだ放棄されていない場合にのみローダーに再アタッチしました。結果のデータが再び必要な場合は、それを実行したくないでしょう。ほとんどの場合、データが必要です。
私はhttp呼び出しのためにこれを使用することの詳細を持っています ここに
私はMarshmallowに大きく基づいている非常に小さなオープンソースのバックグラウンドタスクライブラリを作成しました AsyncTask
しかし、次のような追加機能を備えています:
- 構成の変更間でタスクを自動的に保持する;
- UIコールバック(リスナー);
- デバイスが回転したときにタスクを再起動またはキャンセルしません(ローダーのように);
ライブラリは内部的にaを使用します Fragment
ユーザインタフェースを持たず、設定の変更に伴って保持される(setRetainInstance(true)
).
あなたはGitHubでそれを見つけることができます: https://github.com/NeoTech-Software/Android-Retainable-Tasks
最も基本的な例(バージョン0.2.0):
この例では、非常に限られた量のコードを使用して、タスクを完全に保持します。
タスク:
private class ExampleTask extends Task<Integer, String> {
public ExampleTask(String tag){
super(tag);
}
protected String doInBackground() {
for(int i = 0; i < 100; i++) {
if(isCancelled()){
break;
}
SystemClock.sleep(50);
publishProgress(i);
}
return "Result";
}
}
活動内容:
public class Main extends TaskActivityCompat implements Task.Callback {
@Override
public void onClick(View view){
ExampleTask task = new ExampleTask("activity-unique-tag");
getTaskManager().execute(task, this);
}
@Override
public Task.Callback onPreAttach(Task<?, ?> task) {
//Restore the user-interface based on the tasks state
return this; //This Activity implements Task.Callback
}
@Override
public void onPreExecute(Task<?, ?> task) {
//Task started
}
@Override
public void onPostExecute(Task<?, ?> task) {
//Task finished
Toast.makeText(this, "Task finished", Toast.LENGTH_SHORT).show();
}
}
私のアプローチは委任設計パターンを使用することです、一般的に、実際のビジネスロジック(インターネットやデータベースなどからデータを読み取る)をasynctask(delegator)からBusinessDAO(delegate)にaysnctaskで分離することができます。doInBackground()メソッドは、businessdaoに実際のタスクを委任し、businessdaoでシングルトンプロセスメカニズムを実装するので、BusinessDAOへの複数の呼び出し。doSomething()は、毎回実行され、タスクの結果を待っている1つの実際のタスクをトリガーします。アイデアは、デリゲートを保持することです(つまりBusinessdao)構成変更中に、デリゲータの代わりに(つまりAsyncTask)。
私たち自身のアプリケーションを作成/実装する目的は、ここでBusinessDAOを作成/初期化することです。businessdaoのライフサイクルはアプリケーションスコープであり、アクティビティスコープではありません。androidmanifestを変更する必要があることに注意してください。MyApplicationを使用するxml:
public class MyApplication extends android.app.Application { private BusinessDAO businessDAO; @Override public void onCreate() { super.onCreate(); businessDAO = new BusinessDAO(); } pubilc BusinessDAO getBusinessDAO() { return businessDAO; } }
既存のActivity/Fragementはほとんど変更されていませんが、asynctaskを内部クラスとして実装し、AsyncTaskを含みます。Activity/Fragementからexecute()を実行すると、AsyncTaskが実際のタスクをbusinessdaoに委任するため、構成の変更中に2番目のAsyncTaskが初期化されて実行され、BusinessDAOが呼び出されます。doSomething()2回目、ただし、BusinessDAOへの2回目の呼び出し。doSomething()は新しい実行中のタスクをトリガーせず、現在の実行中のタスクが終了するのを待ちます:
public class LoginFragment extends Fragment { ... ... public class LoginAsyncTask extends AsyncTask<String, Void, Boolean> { // get a reference of BusinessDAO from application scope. BusinessDAO businessDAO = ((MyApplication) getApplication()).getBusinessDAO(); @Override protected Boolean doInBackground(String... args) { businessDAO.doSomething(); return true; } protected void onPostExecute(Boolean result) { //Handle task result and update UI stuff. } } ... ... }
BusinessDAO内では、シングルトンプロセスメカニズムを実装します。:
public class BusinessDAO { ExecutorCompletionService<MyTask> completionExecutor = new ExecutorCompletionService<MyTask(Executors.newFixedThreadPool(1)); Future<MyTask> myFutureTask = null; public void doSomething() { if (myFutureTask == null) { // nothing running at the moment, submit a new callable task to run. MyTask myTask = new MyTask(); myFutureTask = completionExecutor.submit(myTask); } // Task already submitted and running, waiting for the running task to finish. myFutureTask.get(); } // If you've never used this before, Callable is similar with Runnable, with ability to return result and throw exception. private class MyTask extends Callable<MyTask> { public MyAsyncTask call() { // do your job here. return this; } } }
これが機能するかどうかは100%確信していませんが、サンプルコードスニペットは擬似コードと見なす必要があります。私はちょうどあなたにデザインレベルからいくつかの手がかりを与えようとしています。任意のフィードバックや提案は歓迎され、高く評価されています。
AsyncTaskを静的フィールドにすることができます。コンテキストが必要な場合は、アプリケーションコンテキストを出荷する必要があります。そうしないと、アクティビティ全体への参照を保持します。
誰かがこのスレッドへの道を見つけた場合、私はクリーンなアプローチが非同期タスクを実行することであることを発見しました app.Service
(START_STICKYで開始)、実行中のサービスを繰り返し再作成して、サービス(したがって非同期タスク)がまだ実行されているかどうかを確認します;
public boolean isServiceRunning(String serviceClassName) {
final ActivityManager activityManager = (ActivityManager) Application.getContext().getSystemService(Context.ACTIVITY_SERVICE);
final List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
for (RunningServiceInfo runningServiceInfo : services) {
if (runningServiceInfo.service.getClassName().equals(serviceClassName)){
return true;
}
}
return false;
}
そうであれば、再追加します DialogFragment
(または何でも)、ダイアログが閉じられていることを確認しない場合。
これは、あなたが使用している場合に特に関連しています v4.support.*
(執筆時点で)彼らはとの問題を知っているので、ライブラリ setRetainInstance
メソッドとビューのページング。 さらに、インスタンスを保持しないことで、別のリソースセット(つまり、)を使用してアクティビティを再作成できます。新しい向きの別のビューレイアウト)
私はこの問題を解決するためにsameplコードを書く
最初のステップはアプリケーションクラスを作ることです:
public class TheApp extends Application {
private static TheApp sTheApp;
private HashMap<String, AsyncTask<?,?,?>> tasks = new HashMap<String, AsyncTask<?,?,?>>();
@Override
public void onCreate() {
super.onCreate();
sTheApp = this;
}
public static TheApp get() {
return sTheApp;
}
public void registerTask(String tag, AsyncTask<?,?,?> task) {
tasks.put(tag, task);
}
public void unregisterTask(String tag) {
tasks.remove(tag);
}
public AsyncTask<?,?,?> getTask(String tag) {
return tasks.get(tag);
}
}
ることができます。.xml
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:name="com.example.tasktest.TheApp">
活動中のコード:
public class MainActivity extends Activity {
private Task1 mTask1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTask1 = (Task1)TheApp.get().getTask("task1");
}
/*
* start task is not running jet
*/
public void handletask1(View v) {
if (mTask1 == null) {
mTask1 = new Task1();
TheApp.get().registerTask("task1", mTask1);
mTask1.execute();
} else
Toast.makeText(this, "Task is running...", Toast.LENGTH_SHORT).show();
}
/*
* cancel task if is not finished
*/
public void handelCancel(View v) {
if (mTask1 != null)
mTask1.cancel(false);
}
public class Task1 extends AsyncTask<Void, Void, Void>{
@Override
protected Void doInBackground(Void... params) {
try {
for(int i=0; i<120; i++) {
Thread.sleep(1000);
Log.i("tests", "loop=" + i);
if (this.isCancelled()) {
Log.e("tests", "tssk cancelled");
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onCancelled(Void result) {
TheApp.get().unregisterTask("task1");
mTask1 = null;
}
@Override
protected void onPostExecute(Void result) {
TheApp.get().unregisterTask("task1");
mTask1 = null;
}
}
}
アクティビティの向きが変更された場合、変数mTaskはアプリコンテキストからinitedされます。タスクが終了すると、変数はnullに設定され、メモリから削除されます。
私のためにそれは十分です。
以下の例、保持されたフラグメントを使用してバックグラウンドタスクを保持する方法を見てください:
public class NetworkRequestFragment extends Fragment {
// Declare some sort of interface that your AsyncTask will use to communicate with the Activity
public interface NetworkRequestListener {
void onRequestStarted();
void onRequestProgressUpdate(int progress);
void onRequestFinished(SomeObject result);
}
private NetworkTask mTask;
private NetworkRequestListener mListener;
private SomeObject mResult;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Try to use the Activity as a listener
if (activity instanceof NetworkRequestListener) {
mListener = (NetworkRequestListener) activity;
} else {
// You can decide if you want to mandate that the Activity implements your callback interface
// in which case you should throw an exception if it doesn't:
throw new IllegalStateException("Parent activity must implement NetworkRequestListener");
// or you could just swallow it and allow a state where nobody is listening
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retain this Fragment so that it will not be destroyed when an orientation
// change happens and we can keep our AsyncTask running
setRetainInstance(true);
}
/**
* The Activity can call this when it wants to start the task
*/
public void startTask(String url) {
mTask = new NetworkTask(url);
mTask.execute();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// If the AsyncTask finished when we didn't have a listener we can
// deliver the result here
if ((mResult != null) && (mListener != null)) {
mListener.onRequestFinished(mResult);
mResult = null;
}
}
@Override
public void onDestroy() {
super.onDestroy();
// We still have to cancel the task in onDestroy because if the user exits the app or
// finishes the Activity, we don't want the task to keep running
// Since we are retaining the Fragment, onDestroy won't be called for an orientation change
// so this won't affect our ability to keep the task running when the user rotates the device
if ((mTask != null) && (mTask.getStatus == AsyncTask.Status.RUNNING)) {
mTask.cancel(true);
}
}
@Override
public void onDetach() {
super.onDetach();
// This is VERY important to avoid a memory leak (because mListener is really a reference to an Activity)
// When the orientation change occurs, onDetach will be called and since the Activity is being destroyed
// we don't want to keep any references to it
// When the Activity is being re-created, onAttach will be called and we will get our listener back
mListener = null;
}
private class NetworkTask extends AsyncTask<String, Integer, SomeObject> {
@Override
protected void onPreExecute() {
if (mListener != null) {
mListener.onRequestStarted();
}
}
@Override
protected SomeObject doInBackground(String... urls) {
// Make the network request
...
// Whenever we want to update our progress:
publishProgress(progress);
...
return result;
}
@Override
protected void onProgressUpdate(Integer... progress) {
if (mListener != null) {
mListener.onRequestProgressUpdate(progress[0]);
}
}
@Override
protected void onPostExecute(SomeObject result) {
if (mListener != null) {
mListener.onRequestFinished(result);
} else {
// If the task finishes while the orientation change is happening and while
// the Fragment is not attached to an Activity, our mListener might be null
// If you need to make sure that the result eventually gets to the Activity
// you could save the result here, then in onActivityCreated you can pass it back
// to the Activity
mResult = result;
}
}
}
}