86

私は 1 つの APK のみを使用してスマートフォン/タブレット アプリに取り組んでおり、画面サイズに応じて必要に応じてリソースをロードします。最適な設計の選択は、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
            }
        }
    }
}

このコードは正常に動作しています。約 10,000 人のユーザーが不満を持っていないため、このロジックを新しいフラグメント ベースの設計にコピーするのが理にかなっているように見えましたが、もちろん、動作していません。

ログインフラグメントは次のとおりです。

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 が Fragment の内部クラスであり、AsyncTask.execute を呼び出すのは Fragment 自体であることを考慮して、構成の変更中に AsyncTask を保持し、まだ実行中の場合は、progressDialog を表示する適切な方法は何でしょうか。 ()?

4

12 に答える 12

75

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

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

于 2011-12-18T06:57:39.253 に答える
66

以下に詳述する、非常に包括的で実用的な例をお楽しみいただけると思います。

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

編集

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

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

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

私はついにすべてを機能させることができました。私のコードには次の機能があります。

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

この働きやすさの両立は他ではなかなかないと思います。

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

これとは別に、すべての作業を行うMyTask(から拡張された) があります。AsyncTaskそれは破棄されるため、保存できません。MainFragmentまた、Google はsetRetainNonInstanceConfiguration(). とにかく、それは常に利用できるわけではなく、せいぜい醜いハックです。代わりにMyTask、別のフラグメントであるDialogFragmentと呼ばれるに保存しTaskFragmentます。このフラグメントtrueにsetRetainInstance()設定されているため、デバイスが回転しても、このフラグメントは破棄されずに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? 
    }
}

メインフラグメント

長いけどやる価値あり!

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

マイタスク

    // 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 は、私がプロジェクトを作成できるようになる前に、サポート ライブラリを追加することを主張しました。きっと取り外せます。

于 2012-09-06T15:39:55.670 に答える
16

私は最近、保持された s を使用して構成の変更を処理する方法を説明する記事を投稿しFragmentました。AsyncTask回転の変化をうまく保持するという問題を解決します。

TL;DR は、 host yourAsyncTaskを a 内で使用し、をFragment呼び出しsetRetainInstance(true)、の進行状況/結果を、保持された.FragmentAsyncTaskActivityFragmentFragment

于 2013-04-30T16:46:35.993 に答える
13

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

その後、私は非インナーを使い始めました...今ではたくさんのメリットがあります。

2つ目は、実行中のAsyncTaskの参照をApplicationクラスに保持することです-http ://developer.android.com/reference/android/app/Application.html

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

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

これにより、問題が解決します。特定の時間に実行されているAsyncTaskが1つしかない場合は、簡単な参照を追加できます。

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

それ以外の場合は、アプリケーションにそれらへの参照を含むHashMapがあります。

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

于 2011-12-15T14:41:51.917 に答える
4

これに 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 呼び出しでこれを使用する方法の詳細については、こちらを参照してください。

于 2013-11-03T08:15:17.007 に答える
1

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

于 2012-07-24T10:16:09.367 に答える
1

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

于 2011-12-17T18:41:21.103 に答える
1

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

  1. 独自のアプリケーションを作成/実装します。目的は、ここで BusinessDAO を作成/初期化することです。これにより、BusinessDAO のライフサイクルがアクティビティ スコープではなく、アプリケーション スコープになります。AndroidManifest.xml を変更して MyApplication を使用する必要があることに注意してください。

    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. 既存のアクティビティ/フラグメントはほとんど変更されていませんが、AsyncTask を内部クラスとして実装し、アクティビティ/フラグメントから AsyncTask.execute() を使用しています。現在の違いは、AsyncTask が実際のタスクを BusinessDAO に委任することです。そのため、構成の変更中に、2 番目の AsyncTask初期化および実行され、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% 確信が持てません。さらに、サンプル コード スニペットは疑似コードと見なす必要があります。設計レベルから手がかりを提供しようとしているだけです。フィードバックや提案は大歓迎です。

于 2011-12-16T22:16:45.983 に答える
0

この問題を解決するために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 に設定され、メモリから削除されます。

私にとっては十分です。

于 2013-12-03T20:41:26.037 に答える