web-dev-qa-db-ja.com

Android Fragments。画面の回転または構成の変更中にAsyncTaskを保持する

私はスマートフォン/タブレットアプリで作業しており、APKを1つだけ使用し、画面サイズに応じて必要に応じてリソースをロードしています。最適な設計選択は、ACL経由でフラグメントを使用することです。

このアプリは、これまでアクティビティベースのみで問題なく機能していました。これは、画面が回転したり、通信中に構成の変更が発生した場合でも動作させるために、アクティビティで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
            }
        }
    }
}
_

このコードは正常に機能しており、苦情のないユーザー数は約1万人です。したがって、このロジックを新しいフラグメントベースのデザインにコピーするだけのように思えますが、もちろん機能していません。

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の内部クラスであり、それを呼び出すFragment自体であることを考慮して、progressDialogを表示しますAsyncTask.execute()?

84
blindstuff

フラグメントは実際にこれを非常に簡単にすることができます。メソッド Fragment.setRetainInstance(boolean) を使用するだけで、構成の変更後もフラグメントインスタンスが保持されます。これは、ドキュメントの Activity.onRetainnonConfigurationInstance() の推奨代替品であることに注意してください。

何らかの理由で、保持されたフラグメントを本当に使用したくない場合は、他のアプローチを使用できます。各フラグメントには Fragment.getId() によって返される一意の識別子があることに注意してください。 Fragment.getActivity()。isChangingConfigurations() を使用して、設定変更のためにフラグメントが破棄されているかどうかを確認することもできます。したがって、AsyncTaskを停止することを決定する時点で(onStop()またはonDestroy()で最も可能性が高い)、たとえば、構成が変更されているかどうかを確認し、そうであれば、フラグメントの識別子の下で静的SparseArrayに固定することができます、次に、onCreate()またはonStart()で、利用可能なスパース配列にAsyncTaskがあるかどうかを確認します。

75
hackbod

以下に詳述する非常に包括的で実用的な例をお楽しみください。

  1. 回転は機能し、ダイアログは存続します。
  2. (この動作が必要な場合)戻るボタンを押すと、タスクとダイアログをキャンセルできます。
  3. フラグメントを使用します。
  4. デバイスが回転すると、アクティビティの下のフラグメントのレイアウトが適切に変更されます。
  5. 完全なソースコードのダウンロードと プリコンパイルされたAPK があるので、動作が希望どおりかどうかを確認できます。

編集

Brad Larsonの要求に応じて、以下のリンクされたソリューションのほとんどを再現しました。また、私はそれを投稿して以来、AsyncTaskLoaderをポイントしました。同じ問題に完全に適用できるかどうかはわかりませんが、とにかくチェックしてください。

AsyncTaskを使用して進行状況ダイアログとデバイスの回転を行います。

実用的なソリューション!

私はついにすべてが機能するようになりました。私のコードには次の機能があります。

  1. レイアウトが向きによって変化するFragment
  2. いくつかの作業を実行できるAsyncTask
  3. DialogFragmentは、進行状況バーにタスクの進行状況を表示します(単なる不確定なスピナーではありません)。
  4. 回転は、タスクを中断したりダイアログを閉じたりすることなく機能します。
  5. 戻るボタンはダイアログを閉じ、タスクをキャンセルします(ただし、この動作はかなり簡単に変更できます)。

仕事の組み合わせが他のどこにも見られるとは思いません。

基本的な考え方は次のとおりです。単一フラグメント-MainActivityを含むMainFragmentクラスがあります。 MainFragmentには水平方向と垂直方向の異なるレイアウトがあり、setRetainInstance()はfalseであるため、レイアウトを変更できます。つまり、デバイスの向きが変更されると、MainActivityMainFragmentの両方が完全に破棄され、再作成されます。

それとは別に、すべての作業を行うMyTaskAsyncTaskから拡張)があります。 MainFragmentに保存することはできません。破棄されるため、GoogleはsetRetainNonInstanceConfiguration()などの使用を推奨していません。それはとにかく常に利用できるとは限らず、せいぜいいハックです。代わりに、MyTaskを別のフラグメント、DialogFragmentというTaskFragmentに保存します。 ThisfragmentwillsetRetainInstance()をtrueに設定します、デバイスが回転してもこのフラグメントは破棄されず、MyTaskは保持されます。

最後に、TaskFragmentに誰が終了したかを通知する必要があり、作成時にsetTargetFragment(<the MainFragment>)を使用してそれを行います。デバイスを回転させてMainFragmentを破棄し、新しいインスタンスを作成したら、FragmentManagerを使用して(タグに基づいて)ダイアログを見つけ、setTargetFragment(<the new MainFragment>)を実行します。それはほとんどそれです。

他にも2つの作業が必要でした。最初にダイアログが閉じられたときにタスクをキャンセルし、次にメッセージをnullに設定すると、デバイスが回転したときにダイアログが奇妙に閉じられました。

コード

私はレイアウトをリストしません、それらはかなり明白であり、以下のプロジェクトのダウンロードで見つけることができます。

主な活動

これは非常に簡単です。このアクティビティにコールバックを追加したので、タスクがいつ終了するかがわかりますが、それは必要ないかもしれません。主にフラグメントアクティビティコールバックメカニズムを表示したかったのは、それが非常にきれいで、これまで見たことがないかもしれないからです。

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? 
    }
}

MainFragment

それは長いですが、それだけの価値があります!

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();
        }
    }

TaskFragment

    // 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();
        }
    }
}

サンプルプロジェクトをダウンロードする

ソースコード および APK です。申し訳ありませんが、ADTはプロジェクトを作成する前にサポートライブラリを追加することを主張しました。削除できると確信しています。

66
Timmmm

私は最近 記事を投稿しました 保持されたFragmentsを使用して設定変更を処理する方法を説明しました。回転の変化に対してAsyncTaskを適切に保持する問題を解決します。

TL; DRは、AsyncTask内でFragmentをホストし、FragmentsetRetainInstance(true)を呼び出し、AsyncTaskを報告します保持されているActivityを介して、Fragment(または@Timmmmで説明されているアプローチを使用することを選択した場合はターゲットFragment)に戻る進捗/結果。

16
Alex Lockwood

私の最初の提案は、内部AsyncTasksを避けることです。これについて尋ねた質問と答えを読むことができます: Android:AsyncTaskの推奨事項:プライベートクラスまたはパブリッククラス?

その後、私は非内部の使用を開始しました...今、私は多くの利点があります。

2番目は、実行中のAsyncTaskの参照をApplicationクラスに保持します- http://developer.Android.com/reference/Android/app/Application.html

AsyncTaskを開始するたびに、アプリケーションで設定し、終了したらnullに設定します。

フラグメント/アクティビティが開始されると、AsyncTaskが実行されているかどうかを確認し(アプリケーションでnullかどうかを確認する)、内部の参照を必要なもの(アクティビティ、フラグメントなど、コールバックを実行できるように)に設定します。

これで問題が解決します:決まった時間に実行しているAsyncTaskが1つだけの場合は、単純な参照を追加できます。

AsyncTask<?,?,?> asyncTask = null;

それ以外の場合、Aplicationにはそれらへの参照を含むHashMapがあります。

進行ダイアログはまったく同じ原則に従うことができます。

13
neteinstein

このために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

    }
}

これは向きの変更をうまく処理しているようで、バックグラウンドタスクは回転中も継続されます。

注意すべきいくつかの点:

  1. OnCreateでasynctaskloaderに再アタッチすると、onLoadFinished()で以前の結果でコールバックされます(リクエストが完了したことが既に通知されていた場合でも)。これはほとんどの場合、実際には良い動作ですが、時には扱いにくい場合があります。これを処理する方法はたくさんあると思いますが、onLoadFinishedでloader.abandon()と呼びました。次に、onCreateのチェックを追加して、ローダーがまだ破棄されていない場合にのみローダーに再接続します。結果のデータが再び必要な場合は、その必要はありません。ほとんどの場合、データが必要です。

これをhttp呼び出しに使用する方法について詳しく説明します here

4
Matt Wolfe

Marshmallow AsyncTaskに基づいた非常に小さなオープンソースのバックグラウンドタスクライブラリを作成しましたが、次のような追加機能があります。

  1. 構成が変更されてもタスクを自動的に保持します。
  2. UIコールバック(リスナー);
  3. デバイスが回転したときにタスクを再起動またはキャンセルしません(ローダーが行うように)。

ライブラリは、ユーザーインターフェイスなしで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();
    }
}
3
Rolf ツ

私のアプローチは委任設計パターンを使用することです。一般に、AysncTask.doInBackground()メソッドで、AsyncTask(委任者)からBusinessDAO(委任者)に実際のビジネスロジック(インターネットまたはデータベースなどからデータを読み取る)を分離できます。 、実際のタスクをBusinessDAOに委任し、BusinessDAOでシングルトンプロセスメカニズムを実装します。これにより、BusinessDAO.doSomething()を複数回呼び出すと、毎回実行されタスク結果を待機する実際のタスクが1つだけトリガーされます。アイデアは、委任者(つまりAsyncTask)の代わりに、構成変更中に委任(つまりBusinessDAO)を保持することです。

  1. 独自のアプリケーションを作成/実装します。目的は、ここでBusinessDAOを作成/初期化することです。したがって、BusinessDAOのライフサイクルはアクティビティスコープではなくアプリケーションスコープです。MyApplicationを使用するには、AndroidManifest.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;
      }
    
    }
    
  2. 既存のActivity/Fragementはほとんど変更されていませんが、AsyncTaskを内部クラスとして実装し、Activity/FragementのAsyncTask.execute()を使用します。違いは、AsyncTaskが実際のタスクをBusinessDAOに委任することです。初期化されて実行され、BusinessDAO.doSomething()を2回呼び出しますが、BusinessDAO.doSomething()への2回目の呼び出しは新しい実行中タスクをトリガーせず、代わりに現在実行中のタスクが終了するのを待ちます。

    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.
        }
      }
    
      ... ...
    }
    
  3. 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%確信はありません。さらに、サンプルコードスニペットを擬似コードと見なすべきです。設計レベルからいくつかの手がかりを与えようとしています。フィードバックや提案を歓迎し、感謝しています。

1
yorkw

誰かがこのスレッドへの道を見つけたら、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メソッドとビューページングに関する問題を知っているからです。さらに、インスタンスを保持しない場合、異なるリソースのセットを使用してアクティビティを再作成できます(つまり、新しい向きの異なるビューレイアウト)

1
OceanLife

AsyncTaskを静的フィールドにすることができます。コンテキストが必要な場合は、アプリケーションコンテキストを出荷する必要があります。これにより、メモリリークを回避できます。そうしないと、アクティビティ全体への参照を保持できます。

1
Boude

以下の例を参照してください、保持されたフラグメントを使用してバックグラウンドタスクを保持する方法:

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;
            }
        }

    }
}
0
DeepakPanwar

この問題を解決するためにsameplコードを記述します

最初のステップは、Applicationクラスの作成です。

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);
}
}

AndroidManifest.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がアプリのコンテキストから初期化されます。タスクが終了すると、変数はnullに設定され、メモリから削除されます。

私にとっては十分です。

0
Kenumir