Android 비동기 메시지 처리 메커니즘 (4) AsyncTask 소스 코드 분석

지난 장 에서 우 리 는 추상 적 인 AsyncTask 의 기본 사용 (주소:http://blog.csdn.net/wangyongge85/article/details/47988569), 다음은 AsyncTask 소스 코드 내용 을 문답 방법 으로 분석 하 겠 습 니 다. 소스 코드 버 전 은 API 22 입 니 다.
1. AsyncTask 를 실례 화하 고 호출 execute(Params... params) 은 어떤 작업 을 완 성 했 습 니까?
우 리 는 실례 화 대상 을 알 았 을 때 먼저 부모 류 의 구조 기 를 호출 합 니 다. 그러면 우 리 는 먼저 AsyncTask 의 구조 기 와 관련 된 내용 과 관련 된 소스 코드 를 살 펴 보 겠 습 니 다.

// AsyncTask       ,    Callable(    call  ),         
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
    Params[] mParams;
}
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
// AsyncTask   ,   UI     
public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            //          
            mTaskInvoked.set(true);
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            return postResult(doInBackground(mParams));
        }
    };
    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 occured while executing doInBackground()",
                        e.getCause());
            } catch (CancellationException e) {
                postResultIfNotInvoked(null);
            }
        }
    };
}

우 리 는 AsyncTask 구조 기 가 주로 두 대상 을 초기 화 하 는 것 을 보 았 다. mWorker 와 mFuture.mWorker 는 실제 적 으로 Callable 대상 이 고 구성원 변수 인 mParams (첫 번 째 일반적인 매개 변수 배열 유형) 를 가지 고 있 습 니 다. 우 리 는 Callable 인터페이스 가 실현 하 는 스 레 드 에 반환 값 이 있다 는 것 을 알 고 있 습 니 다. 즉, call() 방법의 반환 값 postResult(doInBackground(mParams)) 을 호출 했 습 니 다. 여 기 는 doInBackground(mParams) 방법 이 아직 실행 되 지 않 았 습 니 다.mFuture 는 Future Task 대상 으로 mWorker 를 사용 하여 초기 화 합 니 다.Future Task 는 Future 인터페이스 와 Runnable 인터페이스의 실현 클래스 로 관련 된 callable 대상 인 mWorker 를 제어 할 수 있 으 며 이 대상 의 어떠한 방법 도 실행 되 지 않 았 습 니 다.
AsyncTask 류 를 예화 한 후에 우 리 는 보통 call() 방법 을 호출 합 니 다. 그러면 자 연 스 럽 게 execute(Params... params) 관련 소스 코드 를 봐 야 합 니 다.

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

// execute        
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();
        //     
        mWorker.mParams = params;
        //     mFuture
        exec.execute(mFuture);
        return this;
    }
`execute(Params... params) 직접 호출 execute(). 이 방법 은 첫 번 째 매개 변 수 는 SerialExecutor 대상 sDefaultExecutor 이 고 두 번 째 매개 변 수 는 우리 가 정의 한 Params 입 니 다. 그러면 이 방법 을 상세 하 게 분석 해 보 겠 습 니 다.먼저 임 무 를 처음 수행 하 는 지 여 부 를 판단 하고 첫 번 째 가 아니라면 이상 을 던 집 니 다 (케이스 블록 에 break 문 이 없 음 을 주의 하 십시오).그렇지 않 으 면 아래로 실 행 될 것 이다.먼저 작업 을 실행 상태 로 표시 합 니 다.이 어 우리 가 덮어 쓰 는 executeOnExecutor(Executor exec,Params... params) 방법 을 호출 했다. 이것 은 왜 이것 이 첫 번 째 로 실행 을 시작 하 는 방법 인지 설명 한다. 또한 우 리 는 onPreExecute() UI 를 초기 화 하 는 데 자주 사용 되 는 지 알 고 있다. 반드시 메 인 스 레 드 에서 호출 해 야 하기 때문에 onPreExecute() 메 인 스 레 드 에서 호출 해 야 한다.그리고 우 리 는 현재 여전히 메 인 스 레 드 에 있 음 을 관찰 했다.마지막 으로 SerialExecutor 대상 sDefaultExecutor execute() 를 실행 합 니 다.그렇다면 이 방법 은 또 어떤 일 을 할 것 인가?우 리 는 이 방법 에 들 어가 서 탐구 하 자마자

private static class SerialExecutor implements Executor {
    //       AsyncTask  
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    public synchronized void execute(final Runnable r) {
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    //    mFuture    mWorker call()  
                    r.run();
                } finally {
                    //                  ,         
                    scheduleNext();
                }
            }
        });
        //      AsyncTask     ,
        if (mActive == null) {
            scheduleNext();
        }
    }

    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

정적 내부 클래스 SerialExecutor execute(mFuture) 방법 은 다음 과 같은 내용 을 순서대로 수행 합 니 다. 먼저 mTask execute() 를 호출 하여 Runnable 인 스 턴 스 를 추가 하고 이 인 스 턴 스 offer() 방법 에서 전 달 된 매개 변수 mFuture run() 방법 을 호출 하 는 동시에 finally 블록 에서 실행 run() 합 니 다. 이 단 계 는 대기 열 mTask 에 Runnable 인 스 턴 스 를 추가 합 니 다.이 어 mActive 가 null 인지 아 닌 지 를 판단 하고 첫 번 째 는 당연히 null 이 므 로 실행 scheduleNext() 합 니 다.scheduleNext() 역시 SerialExecutor 클래스 의 방법 입 니 다. 이 방법 은 실 행 됩 니 다 scheduleNext(). 그 중에서 mActive 는 대기 열 에서 꺼 낸 Runnable 대상 입 니 다.THREAD_POOL_EXECUTOR 는 스 레 드 탱크 대상 으로 스 레 드 탱크 의 스 레 드 를 사용 하여 mActive 를 실행 합 니 다.mActive 에서 방금 넣 은 mFuture THREAD_POOL_EXECUTOR.execute(mActive) 방법 을 실행 합 니 다. 이 방법의 코드 는 다음 과 같 습 니 다.

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    //     call()  
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            runner = null;
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

이 방법 은 실제로 Callable 대상 run() 을 호출 하 는데 이 Callable 대상 은 무엇 일 까?AsyncTask 에서 실례 화 된 mWorker 대상 을 기억 하 십 니까...그래, 이 건 mWorker 대상 call() 방법 을 수행 하 는 거 야. 드디어 돌 아 왔 어!!!!call() 방법 에서 실행 call() 되 기 때문에 이 방법 은 백 스테이지 스 레 드 에서 실행 하 는 방법 이다.
sDefaultExecutor 는 정적 구성원 변수 로 AsyncTask 대상 을 만 들 때마다 이 변수 에 있 는 mTasks 대기 열 에 추 가 됩 니 다. 또한 AsyncTask 대상 은 직렬 로 실 행 됩 니 다 (실현 원 리 는 try / finnally 블록 을 통 해 이 루어 집 니 다. 코드 는 위의 SerialExecutor doInBackground(mParams) 와 같 습 니 다.
아직 execute() 결 과 를 어떻게 되 돌려 야 할 지 모 르 겠 으 니 이 문 제 를 어떻게 해결 하 는 지 살 펴 보 자.
2. 어떻게 doInBackground(mParams) 결 과 를 되 돌려 줍 니까?
AsyncTask 에서 실례 화 된 mWorker 대상 의 doInBackground(mParams) 방법 이 post Result (doInBackground (mParams) 로 돌아 간 다 는 것 을 알 고 있 습 니 다. call() 소스 코드 와 관련 된 다른 소스 코드 를 살 펴 보 겠 습 니 다.

// post()  
private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

// getHandler()  
private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }

//      InternalHandler
private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    //           
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
//      AsyncTaskResult
private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        final Data[] mData;

        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }

위 에 모두 네 부분 이 있 는데 첫 번 째 부분 은 postResult() 소스 코드 이다. 이 방법 에서 먼저 postResult() (소스 코드 는 두 번 째 부분) 를 호출 하여 Internal Handler 대상 (소스 코드 는 세 번 째 부분) 을 되 돌려 주 었 다.한편, Internal Handler 대상 은 Handler 대상 입 니 다.메시지 큐 에 메 시 지 를 보 냅 니 다.그리고 Handler 대상 getHandler() 에서 정 보 를 처리 합 니 다.
세 번 째 부분 에 서 는 Internal Handler 류 소스 코드 를 제 시 했 으 며, 보 낸 메 시 지 는 이러한 종류의 obtainMessage() 에서 처 리 될 것 이다.이 방법 에서 먼저 AsyncTask Result 대상 (원본 코드 는 네 번 째 부분) 을 얻 습 니 다. AsyncTask Result 대상 은 현재 AsyncTask 대상 과 sendToTarget() 전달 대상 을 저장 하 는 데 사 용 됩 니 다. 여기 서 전달 하 는 것 은 바로 handleMessage() 결 과 를 되 돌려 줍 니 다. 그러면 우 리 는 결 과 를 Message 대상 에 추가 합 니 다.
AsyncTask 도 Handler 메커니즘 을 통 해 비동기 처 리 를 하 는 것 을 볼 수 있 습 니 다. 하위 스 레 드 에서 작업 을 처리 한 후 실행 결 과 를 Message 에 추가 한 다음 주 스 레 드 에서 Looper 대상 을 통 해 이 Message 를 꺼 내 해당 Handler 대상 handleMessage() 을 통 해 처리 합 니 다.postResult() 에서 우리 가 설정 한 Message 의 what 는 doInBackground() 이기 때문에 handleMessage() 에서 첫 번 째 케이스 블록 을 실행 합 니 다. postResult() 에서 결과 배열 의 첫 번 째 값 만 받 습 니 다.그럼 계속 MESSAGE_POST_RESULT 소스 코드 를 보 겠 습 니 다.

private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

작업 이 멈 추 지 않 으 면 덮어 쓰 는 방법 handleMessage() 을 사용 할 수 있 습 니 다.result.mTask.finish(result.mData[0]) 케이스 블록 이 하나 더 있 습 니 다. 그럼 이 건 언제 실 행 됩 니까?
3. 호출 finish() 은 어떻게 메 인 스 레 드 에 퀘 스 트 진 도 를 보 냅 니까?
우 리 는 finish() 에서 호출 onPostExecute(result) 할 때 현재 작업 진 도 를 메 인 스 레 드 에 보 내 는 것 을 알 고 있 습 니 다. 그러면 이 방법의 소스 코드 를 살 펴 보 겠 습 니 다.

protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }

어디서 많이 본 것 같 지 않 아 요?이 방법 은 handleMessage() 방법 과 마찬가지 로 메시지 대기 열 에 메 시 지 를 보 냅 니 다. 이 메시지 의 what 속성 은 publishProgress() 이 므 로 doInBackground() 의 두 번 째 케이스 블록, 즉 우리 가 덮어 쓴 publishProgress() 을 실행 하면 UI 를 업데이트 할 수 있 습 니 다.
4. 임 무 를 어떻게 병행 합 니까?
SerialExecutor 대상 을 통 해 순서 (직렬) 로 만 임 무 를 수행 할 수 있다 는 것 을 알 고 있 습 니 다. 그러면 어떻게 병행 을 실현 합 니까?호출 postResult() 이 실제로 실 행 된 것 은 MESSAGE_POST_PROGRESS 이라는 것 을 알 고 있 습 니 다. 이 방법의 첫 번 째 매개 변 수 는 기본적으로 SerialExecutor 대상 입 니 다.따라서 우 리 는 호출 handleMessage() 방법 을 통 해 병행 집행 을 실현 할 수 있다. 전 제 는 우리 가 이 방법 에서 의 첫 번 째 매개 변 수 는 병행 실행 스 레 드 를 전달 하 는 스 레 드 탱크 이다.이 스 레 드 탱크 는 AsyncTask 류 자체 스 레 드 탱크 onProgressUpdate() 를 사용 할 수 있 습 니 다. 소스 코드 는 다음 과 같 습 니 다.

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

이 스 레 드 탱크 의 최대 스 레 드 개 수 는 128 이 며, 물론 사용자 정의 스 레 드 탱크 도 사용 할 수 있 습 니 다.
Android 3.0 이후 기본 적 인 상황 에서 AsyncTask 는 하나의 작업 만 수행 할 수 있 습 니 다. 우 리 는 기본 설정 을 유연 하 게 바 꾸 어 AsyncTask 가 여러 작업 을 병행 할 수 있 도록 할 수 있 습 니 다.

좋은 웹페이지 즐겨찾기