Android非同期処理類AsyncTask

5664 ワード

しかし、Androidの開発に触れた人は、UIスレッドが時間をかけて操作できないことを知っています.そうしないと、ANRが発生する可能性があります.では、この問題を回避するためにAndroidは、ネットワークリクエスト、データベースの読み取りなどの時間のかかるタスクを処理するためにAsyncTaskを提供しています.では、次にAsyncTaskがどのように実現したのかを見てみましょう.
まず、AsyncTaskの使用を見てみましょう.
public classMainActivity extends AppCompatActivity {
public static finalStringTAG="MainActivity";
@Override
protected voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//      
new MyTask().execute("");
//      
new MyTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECU  TOR,"");
}
  private static classMyTask extends AsyncTask {
  //         (UIThread)
@Override
protected voidonPreExecute() {
super.onPreExecute();
Log.e(TAG,"onPreExecute:"+Thread.currentThread().getName());
}
//          (WorkThread)     onPostExecute
@Override
protected String doInBackground(String... params) {
//      
try{
for(inti =0;i <5;i++) {
Thread.sleep(1000);
onProgressUpdate(i*20);
}
}catch(InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG,"doInBackground:"+Thread.currentThread().getName() );
return null;
}
//          (WorkThread)
@Override
protected voidonProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
Log.e(TAG,"onProgressUpdate:"+Thread.currentThread().getName() );
}
//           (UIThread)
@Override
protected voidonPostExecute(String s) {
super.onPostExecute(s);
Log.e(TAG,"onPostExecute:"+Thread.currentThread().getName());
}
}
}

ソース分析
AsyncTaskはどのように働いていますか?
まず、execute()とexecuteOnExecuter()の方法を見てみましょう.
@MainThread
public final AsyncTask execute(Params... params) {    
return executeOnExecutor(sDefaultExecutor, params);}

このメソッドではexecuteOnExecutor()というメソッドが実行されますが、このメソッドでは何をしましたか?
@MainThread
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()-->execであることがわかる.execute(mFuture); execはexecuteメソッドによって導入されたsDefaultExecuterというスレッドプールです.では、このスレッドプールの定義と実装を見てみましょう.
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
/*** An {@link Executor} that executes tasks one at a time in serial * order.  This serialization is global to a particular process.*/
public static final Executor SERIAL_EXECUTOR = new   SerialExecutor();

private static class SerialExecutor implements Executor {
 final ArrayDeque mTasks = new   ArrayDeque();   
 Runnable mActive;  
public synchronized void execute(final Runnable r) { 
mTasks.offer(new Runnable() { 
public void run() { 
try {  
r.run(); 
} finally {
 scheduleNext();                
}            
}       
});       
 if (mActive == null) {            
scheduleNext();       
 }    
}   
 protected synchronized void scheduleNext() {   
 if ((mActive = mTasks.poll()) != null) { 
  THREAD_POOL_EXECUTOR.execute(mActive);   
 }   
 }
}

このスレッドプールはシリアルの実装です.まず、その実装を分析します.
final ArrayDeque mTasks = new ArrayDeque();
これは、AsyncTaskタスクを格納する線形の双方向キューであり、新しいタスクがある場合はmTasksを呼び出す.offer()(このメソッドで指定される要素、このdequeキューの最後)次にscheduleNext()を実行します.このメソッドでは、キュー内で先頭に並んだタスクTHREAD_が実行されるPOOL_EXECUTOR.execute(mActive); 次はTHREAD_POOL_EXECUTORの定義:
/** * An {@link Executor} that can be used to execute tasks in parallel. */
public static final Executor THREAD_POOL_EXECUTOR  = new   ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

スレッドプールのコア容量の容量はcpuコア数+1であることがわかる.スレッドプールの最大容量はcpuコア数*2+1です.過剰なアイドルスレッドの生存時間は1秒である.実行するタスクキューの初期容量:128個.以上の解析から分かるように,シリアル実行タスクでもパラレル実行でも,その違いはexecuteOnExecutor()に渡されるスレッドプールの違いであり,シリアルはsDefaultExecutorであり,もちろん自分で定義したシリアルを指定することもでき,パラレルは自分でスレッドプールを指定することで実行できる.
タスクのシリアルとパラレルの実行を分析した後、doInBackground()がいつ実行を開始したのか、onPostExecute()に実行結果を返す方法を見てみましょう.まず、AsyncTaskの構造方法でWorkerRunnable implements CallableとFutureTaskを定義し、CallableとFutureを理解していない人はここを見てください.構築方法におけるWorkerRunnableの実装を見てみましょう
mWorker = new WorkerRunnable() {   
 public Result call() throws Exception {        
mTaskInvoked.set(true);              
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 
 //      
 Result result = doInBackground(mParams);  
 Binder.flushPendingCommands();        
return postResult(result);  
  }};

上記のコードでは、実行後にpostResult(result)メソッドに結果を渡す処理を見つけることができます.このメソッドの実装を見てみましょう.
private Result postResult(Result result) {   
@SuppressWarnings("unchecked")    
Message message =       getHandler().obtainMessage(MESSAGE_POST_RESULT,        
new AsyncTaskResult(this, result));  
message.sendToTarget(); 
return result;
}

この方法を見て分かるように、AsyncTaskの原理はhandler+Threadをカプセル化することです.
まとめ:
AsyncTask内部にはThreadとHandlerがカプセル化されており、AsyncTaskを呼び出すexecuteOnExecutorメソッドはタスクの並列実行を実現したりスレッドプールをカスタマイズしたりすることができ、executeメソッドを呼び出すとタスクのシリアル実行を実現することができる.