AsyncTask 의 작업 원리

머리말
AsyncTask 류 는 일반적으로 안 드 로 이 드 개발 자 들 이 접촉 하 는 최초의 비동기 처리 방법 입 니 다. 현재 유행 하 는 새로운 비동기 임무 류, 예 를 들 어 RxJava 등 이 있 지만 AsyncTask 의 바 텀 실현 원 리 는 배 울 필요 가 있 습 니 다. 우리 가 다른 프레임 워 크 를 이해 하거나 자신의 디자인 프레임 워 크 를 이해 하 는 데 큰 도움 이 됩 니 다.이것 은 handler 와 스 레 드 탱크 의 방식 으로 비동기 작업 을 해서 결 과 를 메 인 스 레 드 로 되 돌려 줍 니 다.
데이터 구조
AsyncTask 의 원 리 를 설명 하기 전에 우 리 는 먼저 사용 하 는 데이터 구 조 를 복습 하거나 배 워 야 한다.이것들 은 우리 가 반드시 알 아야 할 데이터 구조 이다.
  • Executor: 다음 주석 과 중국어 해석 을 보면 현재 직렬 로 실행 할 수도 있 고 순서대로 실행 할 수도 있 으 며 어떤 유형의 실행 기 를 사 용 했 는 지 알 수 있 습 니 다.더 자세 한 설명 은 JDK 소스 코드 나 온라인 설명 을 볼 수 있 습 니 다.https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html
  • An object that executes submitted Runnable tasks. This interface provides a way of decoupling task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc. An Executor is normally used instead of explicitly creating threads. For example, rather than invoking new Thread(new(RunnableTask())).start() for each of a set of tasks, you might use:
    //         Runnable        ,
    //                              ,
    //      、     。                  。
    //             。
     Executor executor = anExecutor;
     executor.execute(new RunnableTask1());
     executor.execute(new RunnableTask2());
     //  Executor                    。
     //               ,              。
     Many Executor implementations impose some sort of limitation on how and when tasks are scheduled. The executor below serializes the submission of tasks to a second executor, illustrating a composite executor. 
    
  • BlockingQueue: 차단 대기 열, 패키지 이름 java. util. concurrent. BlockingQueue, Excutor 와 마찬가지 로 자바 jdk 에서 병렬 로 연 결 된 클래스 입 니 다.대 의 는 Runnable 작업 을 불 러 온 차단 대기 열 입 니 다. 차단 대기 열 이 가득 차 면 다시 작업 을 삽입 하면 데이터 가 가 져 갈 때 까지 차단 합 니 다.반대로 대기 열 이 비어 있 으 면 새 작업 이 삽 입 될 때 까지 작업 을 취 할 때 도 막 힙 니 다.물론 이 BlockingQueue 는 범용 이 며, < > 데 이 터 는 삽 입 된 모든 형식 입 니 다.이 예 에 서 는 Runnable 입 니 다.그 원 리 를 깊이 이해 해 야 한다 면 다른 블 로 그 를 옮 겨 보 세 요. BlockingQueue 가 대기 열 원 리 를 차단 하고 다른 실현 류 에 대해 서도 설명 합 니 다.
  • AtomicInteger FutureTask java 의 JDK 클래스 의 원자 유형의 Integer, 전체 경로 java. util. concurrent. atomic. atomic Integer, atomic 패 키 지 는 원자 류 를 대표 하 며 increment AndGet 등 함 수 를 제공 하여 대상 이 원자 방식 으로 변 하도록 보장 합 니 다.비 원자 적 해석, 예 를 들 어 변수 int a = 0;그리고 a + +;이 a + + 는 원자 조작 이 아 닙 니 다. 예 를 들 어 획득 값, 값 + 1, 할당 등 세 가지 조작 (대개 확인 해 야 합 니 다) 을 거 쳤 습 니 다. 다 중 스 레 드 에서 다른 스 레 드 에 의 해 변경 되 는 문제 가 발생 할 수 있 습 니 다.
  • Array Deque Callable: 양 끝 대기 열 은 일반 대기 열의 FIFO 특성 과 구별 되 는데 그것 이 바로 먼저 나 가 고 꼬리 부분 이 들 어가 고 머리 가 나 오 는 것 이다.2 단 대기 열 은 머리 에 삽입 하고 꼬리 에 삽입 하 며 머리 에서 꺼 내 고 꼬리 에서 꺼 낼 수 있 는 데이터 구조 입 니 다.
  • WorkerRunnable extends Callable 본 고의 실현 유형 은 WorkerRunnable 이 고 Callable 은 Runnable 과 유사 한 데이터 구조 이다.그 중에서 Runnable 은 인자 와 반환 값 이 없 는 비동기 방법 입 니 다.그러면 Callable 은 두 개의 데이터 구조 인 만큼 이것 은 분명 다 를 것 입 니 다. 그것 은 바로 반환 값 이 있 고 매개 변수 화 된 일반적인 형식 입 니 다. 되 돌아 갈 데이터 형식 을 전송 할 수 있 습 니 다. 여 기 는 Result 입 니 다.Runnable 에는 run 방법 이 있 습 니 다. Callable 에는 Call 방법 이 하나 밖 에 없습니다.
  •  public interface Callable<V>{
     	V call() throws Exception;
     }
    
  • Future Task Future 는 비동기 계산 결 과 를 저장 하고 하나의 작업 을 시작 하여 Future 대상 을 특정한 스 레 드 에 맡 길 수 있 습 니 다.Future 대상 의 소유 자 는 결 과 를 계산 한 후에 그것 을 얻 을 수 있 습 니 다.

  • 이러한 설명 을 통 해 알 수 있 듯 이 Runnable 을 계승 한 것 을 알 수 있 습 니 다. 즉, Executor 에 제출 하여 실행 할 수 있 고 일반적인 구조 체 에서 Callable 인 자 를 전달 할 수 있 습 니 다. 또한 get 방법 을 제공 하여 반환 값 을 얻 으 면 get 방법 이 막 히 고 실행 성공 을 알 수 있 습 니 다.
    A cancellable asynchronous computation. This class provides a base implementation of Future, with methods to start and cancel a computation, query to see if the computation is complete, and retrieve the result of the computation. The result can only be retrieved when the computation has completed; **the get methods will block if the computation has not yet completed**. 
    // get       ,          。
    
    //FutureTask      Callable  Runnable  。
    A FutureTask can be used to wrap a Callable or Runnable object. Because FutureTask implements Runnable, a FutureTask can be submitted to an Executor for execution.
    
     public class FutureTask<V>
    extends Object
    implements RunnableFuture<V>
    
    FutureTask(Callable<V> callable)
    Creates a FutureTask that will, upon running, execute the given Callable.
    
     	get()
    Waits if necessary for the computation to complete, and then retrieves its result.
    
  • Internal Handler extends Handler 이것 은 상당히 간단 합 니 다. Handler 는 모두 가 사용 한 적 이 있 을 것 입 니 다. 여 기 는 메 인 스 레 드 를 받 은 Looper 의 Handler 가 결 과 를 메 인 스 레 드 로 보 내 서 사용 합 니 다.만약 깊이 이해 해 야 한다 면 나의 또 다른 박문 을 찾 아 보 세 요: Handler 통신 체제 소스 코드 해석
  • 소스 코드 해독
    위의 데이터 구조 해석 을 통 해 사실은 기본적으로 소스 코드 가 뚜렷 해 졌 다.AsyncTask 는 추상 적 인 유형 이기 때문에 사용 할 때 이 종 류 를 실현 하고 추상 적 인 방법 인 doInBackground 를 실현 해 야 합 니 다. 다른 세 가지 방법 은 필요 에 따라 다시 실현 할 지 여 부 를 선택 할 수 있 습 니 다.그 중에서 doInBackground 는 온라인 스 레 드 에서 실 행 된 것 이 고 다른 세 가 지 는 모두 메 인 스 레 드 에서 실 행 된 것 입 니 다.
    public abstract class AsyncTask<Params, Progress, Result> {
        @WorkerThread
        protected abstract Result doInBackground(Params... params);
        
        @MainThread
        protected void onPreExecute() {}
        
        protected void onPostExecute(Result result) {
        }
        
        @MainThread
        protected void onProgressUpdate(Progress... values) {
        }
    

    AsyncTask 를 사용 하면 몇 가지 주의 점 이 있 습 니 다:
  • AsyncTask 류 는 주 스 레 드 에 불 러 와 야 합 니 다.
  • AsyncTask 클래스 대상 은 주 스 레 드 에서 만들어 야 합 니 다.
  • execute 방법 은 UI 스 레 드 에서 호출 되 어야 합 니 다.
  • 하나의 AsyncTask 는 한 번 만 실행 할 수 있 습 니 다. 즉, execute 방법 입 니 다.
  • execute 방법 은 기본적으로 직렬 로 실 행 됩 니 다. 병행 실행 이 필요 하 다 면 execute OnExecutor 방법 을 호출 해 야 합 니 다.(이 기능 이 잘못 되 었 는 지 모 르 기 때문에 네트워크 요청 시간 이 3s 초과 되 었 습 니 다. 시간 이 초과 되면 이 요청 이 필요 하지 않 습 니 다. 하지만 여러 개의 작업 을 추가 할 때 그 작업 이 7s 가 끝나 야 실 행 됩 니 다. 그러면 뒤의 작업 도 실 행 될 때 까지 기 다 려 야 실 행 됩 니 다.)
  • 그 다음 에 소스 코드 를 봅 니 다. 먼저, 클래스 에서 각각 두 개의 Executor 를 정 의 했 습 니 다. 하 나 는 스 레 드 탱크 이 고 병행 할 수 있 습 니 다. 그 크기 매개 변 수 는 소스 코드 의 변수 에서 볼 수 있 습 니 다.하 나 는 직렬 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);
                        /**
     * 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 volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    private static class SerialExecutor implements Executor {
    //    ,            
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;
    
        public synchronized void execute(final Runnable r) {
        /**
        * 	offer(E e)               ,   Runnable,   Runnable   r.run ,   scheduleNext                   。
    Inserts the specified element at the end of this deque.
        */
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }
    
        protected synchronized void scheduleNext() {
        //Retrieves and removes the head of the queue represented by this deque (in other words, the first element of this deque), or returns null if this deque is empty.
        //poll                    
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }
    

    그 후에 우 리 는 AsyncTask 의 구조 함 수 를 살 펴 보 았 다.우선 비동기 작업 을 만 들 었 고 메 인 스 레 드 에서 만 들 라 고 요구 합 니 다.왜 메 인 스 레 드 에 만들어 야 합 니까? 이 유 는 WorkerRunnable 의 반환 값, 즉 post Result (result) 입 니 다. 아래 에 도 post Result 의 소스 코드 가 붙 어 있 습 니 다. 그 중에서 메 시 지 를 보 낸 handler 는 Internal Handler 이 고 Internal Handler 는 메 인 스 레 드 looper 즉 getMain Looper 가 만 든 메 인 스 레 드 의 handler 를 초기 화 합 니 다.따라서 AsyncTask 를 초기 화하 면 getHandler 를 통 해 Handler 를 만 들 고 작업 을 수행 할 결 과 를 Internal Handler 를 통 해 메 인 스 레 드 로 전송 하기 때문에 이 handler 대상 은 메 인 스 레 드 에서 만들어 야 합 니 다. 또한 Internal Handler 는 정적 클래스 이 고 정적 구성원 은 클래스 를 불 러 올 때 초기 화 되 기 때문에..AsyncTask 의 클래스 는 주 스 레 드 에 불 러 와 야 합 니 다.
       /**
         * Creates a new asynchronous task. This constructor must be invoked on the UI thread.        ,       UI    
         */
        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);
                    }
                }
            };
        }
            private Result postResult(Result result) {
            @SuppressWarnings("unchecked")
            Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                    new AsyncTaskResult<Result>(this, result));
            message.sendToTarget();
            return result;
        }
    

    그 다음 에 진정한 실행 방법 execute OnExecutor 입 니 다. 그 매개 변 수 는 Executor 입 니 다. 즉, 실행 기 는 외부 에서 들 어 옵 니 다. AsyncTask 호출 Executor () 방법 이 실행 기 에 들 어가 지 않 으 면 기본 실행 기 sDefaultExecutor, 즉 SerialExecutor 입 니 다.또한 현재 상태 에 따라 현재 Running 상태 에 있 으 면 이상 Cannot execute task: "the task is already running. 즉, 제4 조 주의 점 을 던 집 니 다.
     @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();
    
            mWorker.mParams = params;
            exec.execute(mFuture);
    
            return this;
        }
    

    테스트 프로그램
    말 이 너무 많은 것 은 아래 의 실례 테스트 프로그램 이다.이곳 은 책의 예 를 본 떠 쓴 것 이다.MyAsyncTask 는 AsyncTask 의 실현 클래스 로 서 doInBackground 방법 을 실현 합 니 다. 작업 은 sleep 1s 이 고 반환 값 은 현재 작업 이름 이 며 onPost Execute 방법 을 다시 쓰 고 실행 이 끝 난 시간 을 인쇄 합 니 다.
    private class MyAsynctask extends AsyncTask {
    private String name;
    
    public myAsynctask(String name) {
        this.name = name;
    }
    
    @Override
    protected String doInBackground(String... strings) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return this.name;
    }
    
    @Override
    protected void onPostExecute(String s) {
        super.onPostExecute(s);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        Log.e(TAG, s + " onPostExecute: time=" + simpleDateFormat.format(new Date()));
    }
    

    } 실행 코드 는 MainActivity 의 onCreate 방법 에서 실 행 됩 니 다. 여기 서 execute 방법 을 호출 합 니 다. 매개 변 수 는 빈 String 입 니 다.위의 분석 에 따 르 면 직렬 로 실 행 된 것 으로 보인다.(현재 AsyncTask 의 과거 버 전 은 고려 하지 않 고 새 소스 코드 테스트 만 합 니 다.) 그 후에 우 리 는 병행 실행 하 는 방법 executo OnExecutor 를 호출 하여 AsyncTask 내부 의 스 레 드 풀 에 전 송 됩 니 다.
            //    
            new myAsynctask("task1").execute("");
            new myAsynctask("task2").execute("");
            new myAsynctask("task3").execute("");
            new myAsynctask("task4").execute("");
            new myAsynctask("task5").execute("");
            //    
            //new myAsynctask("task6").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
            //new myAsynctask("task7").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
            //new myAsynctask("task8").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
            //new myAsynctask("task9").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
            //new myAsynctask("task10").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
    

    직렬 실행 결 과 는 다음 과 같 습 니 다.
    03-07 05:57:58.547 7099-7099/com.example.demo E/MainActivity: task1 onPostExecute: time=2020-03-07 05:57:58:547
    03-07 05:57:59.506 7099-7099/com.example.demo E/MainActivity: task2 onPostExecute: time=2020-03-07 05:57:59:506
    03-07 05:58:00.507 7099-7099/com.example.demo E/MainActivity: task3 onPostExecute: time=2020-03-07 05:58:00:507
    03-07 05:58:01.508 7099-7099/com.example.demo E/MainActivity: task4 onPostExecute: time=2020-03-07 05:58:01:508
    03-07 05:58:02.509 7099-7099/com.example.demo E/MainActivity: task5 onPostExecute: time=2020-03-07 05:58:02:509
    

    병행 실행 결 과 는 다음 과 같다.
    03-07 06:00:42.716 7352-7352/com.example.demo E/MainActivity: task8 onPostExecute: time=2020-03-07 06:00:42:716
    03-07 06:00:42.719 7352-7352/com.example.demo E/MainActivity: task6 onPostExecute: time=2020-03-07 06:00:42:719
    03-07 06:00:42.720 7352-7352/com.example.demo E/MainActivity: task7 onPostExecute: time=2020-03-07 06:00:42:720
    03-07 06:00:43.717 7352-7352/com.example.demo E/MainActivity: task9 onPostExecute: time=2020-03-07 06:00:43:717
    03-07 06:00:43.720 7352-7352/com.example.demo E/MainActivity: task10 onPostExecute: time=2020-03-07 06:00:43:720
    

    어?무슨 귀신?이게 무슨 이유 일 까요?원본 코드 를 보고 방금 demo 프로그램 을 열 고 AsyncTask 에서 현재 demo 의 원본 코드 를 확인 합 니 다.이곳 은 주로 스 레 드 탱크 를 이야기 해 야 한다.
     // We keep only a single pool thread around all the time.
     //          ,    static   
        // We let the pool grow to a fairly large number of threads if necessary,
        //      ,                        
        // but let them time out quickly. In the unlikely case that we run out of threads,
        //
        // we fall back to a simple unbounded-queue executor.
        // This combination ensures that:
        // 1. We normally keep few threads (1) around.
        //            
        // 2. We queue only after launching a significantly larger, but still bounded, set of threads.
        //        ,            
        // 3. We keep the total number of threads bounded, but still allow an unbounded set
        //    of tasks to be queued.
    
           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;
             /**
         * 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_COUNT 는 현재 환경 을 대표 하 는 cpu 핵심 수 입 니 다. 여기 서 사용 하 는 시 뮬 레이 터, cpu 인 자 는 2 CORE 입 니 다.POOL_SIZE 는 핵심 스 레 드 수 를 나타 내 며 계산 후 3 KEEP 입 니 다.ALIVE 대표 스 레 드 유지 시간 1s MAXIMUMPOOL_SIZE 는 최대 스 레 드 수, 즉 작업 이 많 을 때 확 장 된 최대 스 레 드 수 를 의미 합 니 다. 여 기 는 5 입 니 다.
    다른 부분 소스 코드 는 안 드 로 이 드 6.1 의 소스 코드 를 보 았 기 때문에 본인 의 시 뮬 레이 터 는 안 드 로 이 드 5.1 에서 만 들 었 기 때 문 입 니 다. 즉, AsyncTask 의 스 레 드 탱크 에 관 한 코드 는 모두 5.1 의 소스 코드 에 대한 설명 과 정 의 를 봐 야 합 니 다. cpu 핵심 수도 구체 적 인 시 뮬 레이 터 나 실제 컴퓨터 와 관련 이 있 습 니 다.내 컴퓨터 의 핵심 수 는 2 이기 때문에 프로그램 중앙 선 정 도 는 3 개 로 유지 되 었 다. 이것 은 방금 의 집행 결 과 를 설명 한 것 이다.물론 안 드 로 이 드 와 모 바 일 하드웨어 기술 의 발전 에 따라 현재 의 새로운 버 전의 AsyncTask 는 모두 이곳 에 대한 정 의 를 바 꾸 었 다. 예 를 들 어 안 드 로 이 드 API 29 의 AsyncTask 는 스 레 드 탱크 에 대한 변수 정의 에 큰 변화 가 생 겼 고 cpu 와 관련 이 없다.private static final int CORE_POOL_SIZE = 1; private static final int MAXIMUM_POOL_SIZE = 20; private static final int BACKUP_POOL_SIZE = 5; private static final int KEEP_ALIVE_SECONDS = 3; 따라서 구체 적 인 실행 안 드 로 이 드 시스템 과 하드웨어 에 따라 구체 적 인 분석 이 필요 하 다.관심 있 는 동 화 는 서로 다른 플랫폼 의 시 뮬 레이 터 나 실제 컴퓨터 에서 효 과 를 볼 수 있다.
    총결산
    비록 현재 이미 많은 새로운 비동기 임 무 를 처리 할 수 있 는 프레임 워 크 가 나 타 났 지만 AsyncTask 는 일반적인 작은 비동기 프로젝트 에서 자주 적응 되 고 이와 관련 된 Android 기술 과 데이터 구조 기술 은 우리 가 배 울 만 한 가치 가 있 으 며 모든 기술 구 조 는 선행 기술 을 바탕 으로 대응 하 는 장단 점 에 따라 확장 된다.도움 이 된다 면 좋아요 좀 눌 러 주세요.문제 가 있 으 면 소통 에 답 할 수 있다.

    좋은 웹페이지 즐겨찾기