android 비동기 메시지 메커니즘 원본 차원 에서 분석(2)

AsyncTask
AsyncTask 가 뭐야?
AsyncTask 는 경량급 비동기 작업 클래스 로 온라인 스 레 드 에서 배경 작업 을 수행 한 다음 에 실 행 된 진도 와 결 과 를 메 인 스 레 드 에 전달 하고 메 인 스 레 드 에서 UI 를 업데이트 할 수 있 습 니 다.
AsyncTask 와 같은 종류의 성명 은 다음 과 같 습 니 다.

public abstract class AsyncTask<Params, Progress, Result> 
이것 은 Params,Progress,Result 세 개의 범 형 파 라미 터 를 제 공 했 고 아래 에서 이 세 개의 범 형 파라미터 의 구체 적 인 의 미 를 자세히 분석 할 것 이다.
AsyncTask 는 네 가지 핵심 방법 을 제공 했다.
onPreExecute()
이 방법 은 메 인 스 레 드 에서 실 행 됩 니 다.비동기 작업 이 실 행 될 때 까지 이 방법 은 호출 됩 니 다.보통 진도 항목 을 다운로드 하 는 초기 화 등 준비 작업 에 사 용 됩 니 다.
doInBackground(Params… params)
이 방법 은 하위 스 레 드 에서 실 행 됩 니 다.비동기 작업 을 수행 하 는 데 사 용 됩 니 다.이 params 는 AsyncTask 의 첫 번 째 매개 변수 형식 입 니 다.이 방법 에서 publicProgress 방법 을 호출 하여 작업 진 도 를 업데이트 할 수 있 으 며,publicProgress 는 onProgressUpdate 방법 을 호출 합 니 다.
onProgressUpdate(Progress… values)
이 방법 은 주 스 레 드 에서 실 행 됩 니 다.values 의 유형 은 AsyncTask 에서 들 어 오 는 두 번 째 매개 변수 형식 입 니 다.배경 작업 의 실행 진도 가 바 뀌 었 을 때 이 방법 을 실행 합 니 다.
onPostExecute(Result result)
이 방법 은 주 스 레 드 에서 실 행 됩 니 다.doInBackground 방법 이 실 행 된 후에 이 방법 이 호출 됩 니 다.그 중에서 result 의 유형 은 AsyncTask 가 들 어 오 는 세 번 째 매개 변수 형식 입 니 다.그 값 은 doInBackground 방법의 반환 값 입 니 다.
이 어 AsyncTask 에서 가장 흔히 볼 수 있 는 용법 을 살 펴 보 자.이 사례 는 핸드폰 메모리 에 있 는 cache 디 렉 터 리 에 그림 을 다운로드 하면 다운로드 시작 시 진행 상자 가 팝 업 되 고 다운로드 과정 에서 다운로드 진 도 를 표시 하 며 다운로드 가 완료 되면 진행 상 자 를 닫 는 것 이다.성공 하면 다운로드 에 성공 한 Toast 가 나타 나 고 실패 하면 실패 한 Toast 가 팝 업 된다.

class MyAsyncTask extends AsyncTask<Void,Integer,Boolean> {
 @Override
 protected void onPreExecute() {
 progressDialog = new ProgressDialog(MainActivity.this);
 progressDialog.setTitle("    ");
 progressDialog.setMax(100);
 progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
 progressDialog.setCancelable(true);
 progressDialog.show();
 }

  @Override
protected Boolean doInBackground(Void... params) {
 HttpURLConnection conn = null;
 try {
  conn = (HttpURLConnection) params[0].openConnection();
  conn.setRequestMethod("GET");
  conn.setConnectTimeout(5000);
  conn.setReadTimeout(5000);
  final int contentLength;
  if (conn.getResponseCode() == 200) {
  contentLength = conn.getContentLength();
  File file = new File(getCacheDir(),"hh.jpg");
  InputStream is= null;
  FileOutputStream fos= new FileOutputStream(file);
  is = conn.getInputStream();
  int len = 0;
  long totalSize = 0;
  byte[] bytes = new byte[102];
  while ((len = is.read(bytes))!=-1) {
   fos.write(bytes,0,len);
   totalSize += len*100;
   int progress = (int) (totalSize/contentLength);
   publishProgress(progress);
  }
 }

 } catch (IOException e) {
  e.printStackTrace();
 }
  return true;
}

  @Override
 protected void onProgressUpdate(Integer... values) {
  progressDialog.setProgress(values[0]);

  }

  @Override
 protected void onPostExecute(Boolean aBoolean) {
  progressDialog.dismiss();
  if (result) {
  Toast.makeText(context, "    ", Toast.LENGTH_SHORT).show();
  } else {
  Toast.makeText(context, "    ", Toast.LENGTH_SHORT).show();
   }
  }
 }
이 종 류 는 주로 파일 의 다운로드 과정 을 모 의 하 는 데 사 용 됩 니 다.그림 url 주소 로 인 자 를 입력 하고 배경 프로 세 스 인 자 는 Integer 형식 이 며 배경 작업 의 반환 결 과 는 bollean 형식 입 니 다.위 다운로드 작업 을 수행 하려 면 다음 과 같은 방식 으로 수행 할 수 있 습 니 다.

//       ,       doInBackground   
URL url = new URL("http://192.168.43.21:8080/ditu.jpg");
   new MyAsyncTask().execute(url);
소스 코드 분석
우선 AsyncTask 의 구조 방법 부터 시작 하 겠 습 니 다.

 /**
  * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
  */
 public AsyncTask() {
  mWorker = new WorkerRunnable<Params, Result>() {
   public Result call() throws Exception {
    mTaskInvoked.set(true);

    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    //noinspection unchecked
    Result result = doInBackground(mParams);
    Binder.flushPendingCommands();
    return postResult(result);
   }
  };

  mFuture = new FutureTask<Result>(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 occurred while executing doInBackground()",
       e.getCause());
    } catch (CancellationException e) {
     postResultIfNotInvoked(null);
    }
   }
  };
 }
1.주석 에서 말 한 바 와 같이 이 구조 방법 은 UI 스 레 드 에서 호출 되 어야 합 니 다.
2.구조 함 수 는 주로 두 개의 인 스 턴 스 를 만 들 었 습 니 다.하 나 는 WorkerRunnable 이 고 Callback 대상 입 니 다.다른 하 나 는 Future Task 입 니 다.그 매개 변 수 는 앞에서 만 든 WorkerRunnable 대상 입 니 다.
다음은 AsyncTask 의 execute()방법 을 살 펴 보 겠 습 니 다.이 방법 은 전체 비동기 작업 의 입구 입 니 다.

@MainThread
 public final AsyncTask<Params, Progress, Result> execute(Params... params) {
  return executeOnExecutor(sDefaultExecutor, params);
 }
  @MainThread
 public final AsyncTask<Params, Progress, Result> 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()  
  onPreExecute();
  //      AsyncTask             ,   doInBackground(mParams) 
  mWorker.mParams = params;
  //*******************
  exec.execute(mFuture);

  return this;
 }
우 리 는 onPreExecute()가 호출 되 는 것 을 보 았 습 니 다.배경 작업 을 수행 하기 전에 호출 되 었 고 메 인 스 레 드 에서 호출 되 었 음 을 증명 합 니 다.이 때 우 리 는 인 스 턴 스 코드 의 진도 표시 상자 의 디 스 플레이 동작 을 실 행 했 습 니 다.이 화면 은 다음 과 같 습 니 다. 

이 방법 에서 exec.execute(mFuture)를 작 동 했 습 니 다.우선 exec 가 무엇 인지 알 아야 합 니 다.아래 소스 코드 에서 우 리 는 답 을 얻 을 수 있다.

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
위의 코드 와 execute OnExecutor 가 들 어 오 는 매개 변수 에 따 르 면,우 리 는 이곳 의 exec 가 사실은 SerialExecutor 라 는 것 을 보 았 다.그러면 SerialExecutor 의 execute 방법 을 살 펴 보 자.

public synchronized void execute(final Runnable r) {
   mTasks.offer(new Runnable() {
    public void run() {
     try {
      r.run();
     } finally {
      scheduleNext();
     }
    }
   });
   if (mActive == null) {
    scheduleNext();
   }
  }
우 리 는 하위 스 레 드 에서 r.run()을 실행 하 는 것 을 보 았 습 니 다.execute()방법 에 따라 들 어 오 는 매개 변 수 를 통 해 알 수 있 듯 이 이곳 의 r 는 구조 방법 에서 처음 만 든 Future Task 입 니 다.물론 다음 에는 Future Task 의 run()방법 을 봐 야 합 니 다.

public void run() {
  if (state != NEW ||
   !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
   return;
  try {
   Callable<V> c = callable;
   if (c != null && state == NEW) {
    V result;
    boolean ran;
    try {
    //****************************
     result = c.call();
     ran = true;
    } catch (Throwable ex) {
     result = null;
     ran = false;
     setException(ex);
    }
    if (ran)
     set(result);
   }
  } finally {
   // runner must be non-null until state is settled to
   // prevent concurrent calls to run()
   runner = null;
   // state must be re-read after nulling runner to prevent
   // leaked interrupts
   int s = state;
   if (s >= INTERRUPTING)
    handlePossibleCancellationInterrupt(s);
  }
 }
여기 result=c.call()방법 이 있 는데 여기 c 는 Callable c=callable 입 니 다.들 어 오 는 callable,callable 은 우리 가 처음에 AsyncTask 구조 방법 에서 들 어 온 WorkerRunnable 입 니 다.따라서 WorkerRunnable 의 call()방법 을 살 펴 봐 야 합 니 다.이 call()방법 은 주로 doInBackground(mParams)와 post Result(result)방법 을 호출 했 습 니 다.

mWorker = new WorkerRunnable<Params, Result>() {
 public Result call() throws Exception {
  mTaskInvoked.set(true);
       Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    //noinspection unchecked
  //*********************************
  Result result = doInBackground(mParams);
  Binder.flushPendingCommands();
  return postResult(result);
 }
1.여기 서 doInBackground(mParams)방법의 호출 을 볼 수 있 고 이 때 는 하위 스 레 드 에 있 기 때문에 시간 이 걸 릴 수 있 습 니 다.위의 사례 에서 doInBackground 방법 에서 코드 가 실 행 됩 니 다.PublishProgress()방법 을 실행 할 때 진행 정 보 를 onProgressUpdate()방법 에 계속 전달 합 니 다.(뒤의 분석 에서 PublishProgress()의 실행 이 왜 onProgressUpdate()방법 을 호출 하 는 지 설명 합 니 다)이 방법 을 업데이트 UI 를 실행 하도록 합 니 다.여기까지 실 행 될 때,사례 중의 다운로드 화면 은 다음 과 같 습 니 다.이때 진도 가 50%에 이 르 렀 습 니 다.

2.방법의 마지막 에 post Result(result)방법 을 실 행 했 습 니 다.이 안의 result 매개 변 수 는 바로 우리 doInBackground(mParams)의 반환 값 입 니 다.이 방법의 주요 역할 은 Internal Handler 인 스 턴 스 를 만 들 고 what=MESSAGE 를 보 내 는 것 입 니 다.POST_RESULT 의 소식,우 리 는 이어서 이 방법의 소스 코드 를 보 았 다.

private Result postResult(Result result) {
  @SuppressWarnings("unchecked")
  Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
    new AsyncTaskResult<Result>(this, result));
  message.sendToTarget();
  return result;
 }
 //message   sendToTarget(),    handler         
 public void sendToTarget() {
  target.sendMessage(this);
 }
handler 를 잘 아 는 친구 들 은 target.sendmessage(this)방법 이 메시지 대기 열 에 메 시 지 를 보 내 는 것 이라는 것 을 알 고 있 습 니 다.이 메 시 지 는 message 자체 입 니 다.message 에는 MESSAGE 가 들 어 있 습 니 다.POST_RESULT 와 new AsyncTask Result(this,result)대상 은 getHandler 에서 보 내 고 마지막 으로 handle Message()(이 메 시 지 를 처리 합 니 다)를 찾 아야 합 니 다.따라서 이 handler 를 찾 아야 합 니 다.

private static Handler getHandler() {
  synchronized (AsyncTask.class) {
   if (sHandler == null) {
   //        sHandler InternalHandler  
    sHandler = new InternalHandler();
   }
   return sHandler;
  }
 }
위의 getHandler()방법 에 따 르 면 이 handler 대상 이 Internal Handler 라 는 것 을 알 수 있 습 니 다.다음 임 무 는 Internal Handler 의 handler Message()방법 을 분석 하 는 것 입 니 다.이 방법의 역할 은 post Result(Result result)방법 중의 sHandler 가 보 낸 서로 다른 메 시 지 를 판단 하여 서로 다른 논 리 를 수행 하 는 것 입 니 다.

@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;
   }
}
//result.mTask.finish(result.mData[0])    
 private void finish(Result result) {
  if (isCancelled()) {
   onCancelled(result);
  } else {
   onPostExecute(result);
  }
  mStatus = Status.FINISHED;
 }
//result.mTask.onProgressUpdate(result.mData)
 @MainThread
 protected void onProgressUpdate(Progress... values) {
 }
위의 소스 코드 를 통 해 알 수 있 듯 이 msg 가 가지 고 있 는 what 정보 에 따라 서로 다른 정 보 를 처리 합 니 다.what=MESSAGEPOST_RESULT 시,최종 적 으로 onCanceled(result)또는 onPost Execute(result)를 실행 합 니 다.이 두 가지 방법 은 모두 메 인 스 레 드 에 있 습 니 다.MESSAGEPOST_RESULT 는 방금 정 보 를 얻 었 을 때 들 어 온 것 이다.여기 서 실 행 될 때 저희 가 사례 를 다운로드 한 코드 는 onPost Execute(result)에 도 착 했 습 니 다.저 희 는 진도 상자 의 숨겨 진 동작 을 실 행 했 습 니 다.실 행 된 화면 은 다음 과 같 습 니 다. 

하지만 MESSAGEPOST_PROGRESS 인 자 는 어디서 들 려 왔 습 니까?맞습니다.바로 Publish Progress(Progress..values)입 니 다.

@WorkerThread
protected final void publishProgress(Progress... values) {
 if (!isCancelled()) {
    getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
     new AsyncTaskResult<Progress>(this, values)).sendToTarget();
  }
 }
여기 MESSAGE 들 어 갑 니 다.POST_프로그램,그리고 이 방법 은 WorkerThread 에서 호출 됩 니 다.이렇게 하면 onProgressUpdate(Progress...values)방법 이 순조롭게 실 행 될 수 있 습 니 다.전체 프로 세 스 종료
총결산
1.AsyncTask 의 대상 은 주 스 레 드 에서 만 만 만 들 수 있 습 니 다.
2.execute()방법 은 UI 스 레 드 에서 만 실 행 됩 니 다.
3.프로그램 에서 onPreExecute,onPost Execute,doInBackground,onProgressUpdate 방법 을 직접 호출 하지 마 십시오.
4.하나의 AsyncTask 대상 은 execute 방법 을 한 번 만 실행 할 수 있 습 니 다.실행 시 오 류 를 보고 할 수 있 습 니까?execute 방법 을 실행 할 때 다음 코드 로 호출 하여 이 결론 을 증명 합 니 다.

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)");
   }
  }
5.Android 1.6 이전에 AsyncTask 는 직렬 로 임 무 를 수행 하 였 으 며,1.6 이후 에는 온라인 풀 에서 임 무 를 병행 처리 하 는 것 으로 바 뀌 었 으 며,3.0 이후 에는 온라인 풀 에서 직렬 로 임 무 를 수행 하 는 것 으로 바 뀌 었 다.
AsyncTask 에는 두 개의 스 레 드 풀(SerialExecutor 와 THREADPOOL_EXECUTOR)와 handler(Internal Handler).
그 중에서 SerialExecutor 는 작업 의 줄 서기 에 사 용 됩 니 다.위의 코드 에서 우 리 는 AsyncTask.execute 를 실행 하 는 과정 에서 execute 방법 을 호출 하 는 것 을 보 았 습 니 다.

private static class SerialExecutor implements Executor {
  final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
  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);
   }
  }
 }
우선 Future Task 대상 을 대기 열 mTask 에 삽입 하 는 것 을 보 았 습 니 다.이때 실행 중인 AsyncTask 활동 이 없 으 면 schedule Next()를 호출 합 니 다.다음 작업 을 수행 합 니 다.작업 이 완료 되면 다음 작업 을 수행 합 니 다.이 를 통 해 AsyncTask 는 기본적으로 직렬 로 실 행 됩 니 다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기