Android AsyncTaskの実現原理と使用テクニックの共有

11551 ワード

なぜAsyncTaskを使うのか
Appを書くには、主スレッドが大量のCPUタイムスライスを必要とするタスクを実行できないという原則があります.例えば、大量の複雑な浮動小数点演算、大きなディスクIO操作、ネットワークsocketなど、主スレッドのユーザーへの応答が鈍くなり、ANRになることもあります.これらはアプリケーションのユーザー体験を悪くしますが、時にはこれらの時間のかかるタスクを実行する必要があります.通常、AsyncTaskまたはnew Threadを使用して処理することができます.これにより、タスクを作業スレッドに入れて実行し、メインスレッドのタイムスライスを占有することはありません.そのため、メインスレッドはユーザーの操作にタイムリーに応答します.new Threadを使用してタスクを実行する場合、途中でタスクの実行をキャンセルしたり、タスクの実行結果を返したりする必要がある場合は、追加のコードを自分で維持する必要があります.一方、AsyncTaskはconcurrentラックパッケージに基づいて提供される同時クラスによって実現され、上記の2つのニーズがカプセル化されています.これもAsyncTaskを選択した理由です.
AsyncTaskの使い方
AsyncTaskの使用例を簡単に紹介します.まず、AsyncTaskが抽象クラスであるため、doInBackgroundメソッドを書き換える必要があるクラスDemoAsyncTaskがAsyncTaskを継承するように新規作成します.
private class DemoAsyncTask extends AsyncTask {
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected Void doInBackground(String... params) {
        return null;
    }   

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
    }

    @Override
    protected void onProgressUpdate(Void... values) {
        super.onProgressUpdate(values);
    }

    @Override
    protected void onCancelled(Void aVoid) {
        super.onCancelled(aVoid);
    }

    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

DemoAsyncTask task = new DemoAsyncTask();
task.execute("demo test AsyncTask");
//task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, "test");
//myTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "test");

簡単な分析の下で
上はAsyncTaskの最も簡単な使用方法で、私たちが書き直した方法の中で、onInBackground方法は作業スレッドで実行され、他の方法はすべてメインスレッドで実行され、またその実行方法Androidは私たちに2つの方法を提供し、上にリストされています.
  • 1.最初の方法はデフォルトのExecutorを使用して私たちのタスクを実行します.実はSERIAL_です.EXECUTOR,SERIAL_EXECUTOR私达も実は方法を通じてカスタマイズすることができて、Androidは私达のデフォルトの実现を助けて私达の任务を1つずつ実行して、つまり単一のスレッドの、AsyncTaskの任务の実行について単一のスレッドの実现かそれともマルチスレッドの実现についてもう1段のとても面白い歴史があって、比較的に早いバージョンは単一のスレッドの実现で、Android 2から.Xから、Googleはまたそれをマルチスレッド実装に変更しましたが、Googleは、マルチスレッド実装では、スレッドの安全を保証する必要がある追加の作業が開発者に残されていることを発見しました.0から、またデフォルトの実装を単一スレッドに変更しました.今日はFramworkコードバージョンが21(Android 5.0)であることを示します.
  • 2.また、マルチスレッド実装インタフェース、すなわち、私たちが書いた最後の行のコードAsyncTaskも提供する.THREAD_POOL_EXECUTOR.
    public final AsyncTask execute(Params... params) {
      return executeOnExecutor(sDefaultExecutor, params);
    }
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    

  • 実はみんながふだん仕事の学习の中でいつもAsyncTaskを使うことを信じて、私达は直接AsyncTask类のソースコードを见に行きます
    public abstract class AsyncTask {
    ....
    }
    

    AsyncTask抽象クラスには、3つの汎用パラメータタイプがあり、1つ目はあなたが伝達する必要があるパラメータタイプであり、2つ目はタスク完了進捗のタイプが一般的にIntegerであり、3つ目はタスク完了結果の戻りタイプであり、これらのパラメータがすべて必要ではない場合があり、Voidに設定する必要はありません.またResultは1つしかありませんが、Paramsは複数あります.AsyncTaskのデフォルトコンストラクタが2つのオブジェクト,mWorker,mFutureを初期化していることがわかります.
    private final WorkerRunnable mWorker;
    private final FutureTask mFuture;
        mWorker = new WorkerRunnable() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
    
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));
            }
        };
    
        mFuture = new FutureTask(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occured while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    

    mWokerはCallbackインタフェースを実現し、CallbackインタフェースはJDK 1である.5高度なコンカレントフレームパッケージに組み込まれたインタフェースで、汎用的な戻り値を持つことができます.
    public interface Callable {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
    }
    

    FutureTaskはRunnableFutureインタフェースを実現し、RunnableFutureインタフェースはJDKが提供したもので、名前を見るとRunnableとFutureインタフェースを継承していることがわかり、mFutureTaskはFutureTaskのインスタンスであり、Executerクラスインスタンスexecuteに直接使用することができる.AsyncTaskのexecuteメソッドを引き続き見てみましょう.
    public final AsyncTask     execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }
    public final AsyncTask    executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }
    
        mStatus = Status.RUNNING;
    
        onPreExecute();
    
        mWorker.mParams = params;
        exec.execute(mFuture);
    
        return this;
    }
    

    まずonPreExecute()メソッドを呼び出すが、このときメインスレッド(厳密にはAsyncTaskが実行するスレッドを呼び出す)、その後exec.execute(mFuture)は、exec処理にタスクを渡し、execute mFutureはinvoke mWokerであり、postResult(doInBackground(mParams))を呼び出します.このとき、ワークスレッドプールで実行され、メインスレッドがブロックされません.そしてmHandlerにMESSAGE_を送信POST_RESULTメッセージを呼び出し、finishメソッドを呼び出します.isCancelledの場合、onCancelledをコールバックします.そうでない場合、onPostExecuteをコールバックします.
    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult(this, result));
        message.sendToTarget();
        return result;
    }
    private static final InternalHandler sHandler = new InternalHandler();
    private static class InternalHandler extends Handler {
        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
    

    今、私たちはAsyncTaskのタスクを実行する過程をすべて終えました.その中で私たちに暴露されたいくつかのコールバック方法も行きました.今振り返ってみると、AsyncTaskはJDK 1.5に提供された高度な同時特性にすぎず、concurrentラックパッケージで作られたパッケージであり、開発者が非同期タスクを処理するのに便利である.もちろん、タスクの実行進捗のフィードバック、タスクの実行原子性の保証など、多くの詳細な処理方法を学ぶ価値がある.
    AsyncTaskのちょっとしたテクニックを使う
    「QQQQAndroidインストールパッケージのダウンロードを開始するには、ボタンをクリックし、ダウンロードの進捗状況をフィードバックするダイアログボックスを表示します」という例を示します.まずダイアログボックスを初期化します.進捗を表示するため、Githubの上の進捗バーNumberProgressbarを表示します.タスクを起動するボタンは*circlebutton*、クールなアニメーションのボタンを使用します.Githubの上には非常に良いオープンソースプロジェクトがたくさんあります.もちろん、クールなコントロールはその一部です.後で、流行しているコントロールの実現原理を学ぶ機会があります.今日はとりあえず主義を持ってきました~~.
  • 1.まず、プログレスバープロンプトダイアログボックスを初期化します.builder = new AlertDialog.Builder( MainActivity.this); LayoutInflater inflater = LayoutInflater.from(MainActivity.this); mDialogView = inflater.inflate(R.layout.progress_dialog_layout, null); mNumberProgressBar = (NumberProgressBar)mDialogView.findViewById(R.id.number_progress_bar); builder.setView(mDialogView); mDialog = builder.create();
  • 2.イベントをクリックするボタンを設定します.findViewById(R.id.circle_btn).setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { dismissDialog(); mNumberProgressBar.setProgress(0); myTask = new MyAsyncTask(); myTask.execute(qqDownloadUrl); } });
  • 3.DownloadAsyncTask実現、ちょっと長いです.private class DownloadAsyncTask extends AsyncTask{
      @Override
      protected void onPreExecute() {
          super.onPreExecute();
          mDialog.show();
    
      }
    
      @Override
      protected void onPostExecute(String aVoid) {
          super.onPostExecute(aVoid);
          dismissDialog();
      }
    
      @Override
      protected void onProgressUpdate(Integer... values) {
          super.onProgressUpdate(values);
    
          mNumberProgressBar.setProgress(values[0]);
      }
    
      @Override
      protected void onCancelled(String aVoid) {
          super.onCancelled(aVoid);
          dismissDialog();
      }
    
      @Override
      protected void onCancelled() {
          super.onCancelled();
          dismissDialog();
      }
    
      @Override
      protected String doInBackground(String... params) {
          String urlStr = params[0];
          FileOutputStream output = null;
          try {
              URL url = new URL(urlStr);
              HttpURLConnection connection = (HttpURLConnection)url.openConnection();
              String qqApkFile = "qqApkFile";
              File file = new File(Environment.getExternalStorageDirectory() + "/" + qqApkFile);
              if (file.exists()) {
                  file.delete();
              }
              file.createNewFile();
              InputStream input = connection.getInputStream();
              output = new FileOutputStream(file);
              int total = connection.getContentLength();
              if (total <= 0) {
                  return null;
              }
              int plus = 0;
              int totalRead = 0;
              byte[] buffer = new byte[4*1024];
              while((plus = input.read(buffer)) != -1){
                  output.write(buffer);
                  totalRead += plus;
                  publishProgress(totalRead * 100 / total);
                  if (isCancelled()) {
                      break;
                  }
              }
              output.flush();
          } catch (MalformedURLException e) {
              e.printStackTrace();
              if (output != null) {
                  try {
                      output.close();
                  } catch (IOException e2) {
                      e2.printStackTrace();
                  }
              }
          } catch (IOException e) {
              e.printStackTrace();
              if (output != null) {
                  try {
                      output.close();
                  } catch (IOException e2) {
                      e2.printStackTrace();
                  }
              }
          } finally {
              if (output != null) {
                  try {
                      output.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
          return null;
      }
    
    }という簡単なダウンロードファイルは基本的に実現され、これまでテクニックとは言えませんでしたが、Activityがバックグラウンドでタスクを実行している場合、時間がかかる可能性があります.ユーザーは「戻る」をクリックしてActivityを終了するか、Appを終了するか、バックグラウンドタスクはすぐに終了しないという問題があります.AsyncTaskの内部にActivityのメンバー変数の参照がある場合、Activityの回収遅延が発生し、しばらくの間メモリ漏洩が発生するため、次の手順4を追加する必要があります.
  • 4.onPauseでアプリケーションが終了するかどうかを判断し、AsyncTask実行をキャンセルするかどうかを決定します.@Override protected void onPause() { super.onPause(); if (myTask != null && isFinishing()) { myTask.cancel(false); } } このように我々の非同期タスクはActivityが終了すると、タスクの実行もキャンセルされ、順調にシステムに破棄されて回収され、第4歩は多くの場合漏れ、一般的に致命的な問題はありませんが、問題が発生すると、調査が難しいので、符号化規範に従う必要があります.

  • 小結
    AsyncTaskの基本的な実現原理はすでに明らかになった.同時に、AsyncTaskを使って注意しなければならない小さなテクニックを紹介した.