Android AsyncTask 사용 및 소스 코드 분석

19806 단어 AndroidAsyncTask
종합 하여 서술 하 다
Android 에서 우 리 는 시간 이 걸 리 는 작업 을 해 야 합 니 다.이 작업 을 하위 스 레 드 에 두 고 진행 할 것 입 니 다.하위 스 레 드 작업 이 끝 난 후에 우 리 는 Handler 를 통 해 메 시 지 를 보 내 고 UI 에 업데이트 작업 을 하 라 고 통지 할 수 있다(구체 적 인 사용 과 원 리 는 볼 수 있다Android 의 메시지 메커니즘-Handler 의 작업 과정 이 글)물론 우리 의 조작 을 간소화 하기 위해 Android 1.5 이후 AsyncTask 류 를 제공 합 니 다.하위 스 레 드 처리 가 완 료 된 결 과 를 UI 스 레 드 로 되 돌 릴 수 있 습 니 다.그 후에 우 리 는 이러한 결과 에 따라 일련의 UI 작업 을 할 수 있 습 니 다.
AsyncTask 의 사용 방법
실제로 AsyncTask 내 부 는 Handler 와 스 레 드 탱크 를 한 번 밀봉 한 것 이다.그것 은 경량급 비동기 작업 클래스 로 배경 작업 이 온라인 풀 에서 진행 된다.그 후에 우 리 는 작업 수행 결 과 를 메 인 스 레 드 에 전달 할 수 있 습 니 다.이때 우 리 는 메 인 스 레 드 에서 UI 를 조작 할 수 있 습 니 다.
AsyncTask 는 추상 적 인 범 형 류 이기 때문에 우 리 는 하위 클래스 를 만들어 AsyncTask 의 추상 적 인 방법 을 실현 합 니 다.AsyncTask 에서 세 개의 범 형 매개 변 수 를 제 공 했 습 니 다.다음은 이 세 개의 범 형 매개 변 수 를 살 펴 보 겠 습 니 다.
1.Params:AsyncTask 를 실행 할 때 전달 하 는 매개 변 수 는 배경 스 레 드 에서 사 용 됩 니 다.
2.Progress:배경 작업 수행 진도 의 유형
3.Result:백 엔 드 퀘 스 트 수행 완료 후 돌아 오 는 결과 유형 입 니 다.
상기 세 개의 일반적인 매개 변 수 를 우리 가 사용 할 필요 가 없 을 때 Void 로 대체 할 수 있 습 니 다.Activity 라 이 프 사이클 과 유사 하 게 AsyncTask 에서 도 우리 에 게 몇 가지 방법 을 제공 해 주 었 습 니 다.우 리 는 이 몇 가지 방법 을 다시 써 서 전체 비동기 임 무 를 완성 합 니 다.우리 가 주로 사용 하 는 방법 은 네 가지 가 있다.
1.onPreExecute():이 방법 은 비동기 작업 전에 실 행 됩 니 다.주로 매개 변수 나 UI 초기 화 작업 에 사 용 됩 니 다.
2.doInBackground(Params...params):이 방법 은 온라인 풀 에서 실 행 됩 니 다.params 인 자 는 비동기 작업 시 입력 한 인 자 를 표시 합 니 다.이 방법 에서 우 리 는 Publish Progress 를 통 해 작업 진 도 를 알 립 니 다.
3.onProgressUpdate(Progress..values):배경 작업 의 진도 가 바 뀌 었 을 때 이 방법 을 사용 합 니 다.이 방법 에서 UI 의 진 도 를 보 여 드릴 수 있 습 니 다.values 매개 변 수 는 작업 진 도 를 표시 합 니 다.
4.post Result(Result result):비동기 작업 이 끝 난 후에 실 행 됩 니 다.result 인 자 는 비동기 작업 이 끝 난 후에 돌아 오 는 결과 입 니 다.
위의 네 가지 방법 중 doInBackground 만 하위 스 레 드 에서 실 행 됩 니 다.나머지 세 가지 방법 은 모두 메 인 스 레 드 에서 실 행 됩 니 다.그 중에서...매개 변수의 수량 이 일정 하지 않 음 을 나타 내 는 배열 형식의 매개 변수 입 니 다.
다음은 하나의 예 를 들 어 AsyncTask 의 용법 을 살 펴 보 겠 습 니 다.여기 서 우 리 는 하나의 다운로드 기능 으로 인터넷 에서 두 개의 파일 을 다운로드 합 니 다.우리 먼저 효과 시범 을 보 자.
효과 시범

코드 분석
우리 가 하 는 다운로드 작업 때문에,우선 네트워크 접근 권한 과 sd 카드 관련 권한 을 추가 해 야 합 니 다.

<!--  SD            -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!--  SD        -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--        -->
<uses-permission android:name="android.permission.INTERNET"/>
다음은 Activity 코드 를 살 펴 보 겠 습 니 다.

package com.example.ljd.asynctask;

import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

 private DownloadAsyncTask mDownloadAsyncTask;

 private Button mButton;
 private String[] path = {
   "http://msoftdl.360.cn/mobilesafe/shouji360/360safesis/360MobileSafe_6.2.3.1060.apk",
   "http://dlsw.baidu.com/sw-search-sp/soft/7b/33461/freeime.1406862029.exe",
 };

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  mButton = (Button)findViewById(R.id.button);
  mButton.setOnClickListener(this);
 }

 @Override
 protected void onDestroy() {
  if (mDownloadAsyncTask != null){
   mDownloadAsyncTask.cancel(true);
  }
  super.onDestroy();
 }

 @Override
 public void onClick(View v) {

  mDownloadAsyncTask = new DownloadAsyncTask();
  mDownloadAsyncTask.execute(path);
 }

 class DownloadAsyncTask extends AsyncTask<String,Integer,Boolean>{

  private ProgressDialog mPBar;
  private int fileSize;  //       
  @Override
  protected void onPreExecute() {
   super.onPreExecute();
   mPBar = new ProgressDialog(MainActivity.this);
   mPBar.setProgressNumberFormat("%1d KB/%2d KB");
   mPBar.setTitle("  ");
   mPBar.setMessage("    ,   ...");
   mPBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
   mPBar.setCancelable(false);
   mPBar.show();
  }

  @Override
  protected Boolean doInBackground(String... params) {
   //    
   for (int i=0;i<params.length;i++){
    try{
     if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
      URL url = new URL(params[i]);
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      //      
      conn.setConnectTimeout(5000);
      //         
      fileSize = conn.getContentLength();
      InputStream is = conn.getInputStream();
      //      
      String fileName = path[i].substring(path[i].lastIndexOf("/") + 1);
      File file = new File(Environment.getExternalStorageDirectory(), fileName);
      FileOutputStream fos = new FileOutputStream(file);
      BufferedInputStream bis = new BufferedInputStream(is);
      byte[] buffer = new byte[1024];
      int len ;
      int total = 0;
      while((len =bis.read(buffer))!=-1){
       fos.write(buffer, 0, len);
       total += len;
       publishProgress(total);
       fos.flush();
      }
      fos.close();
      bis.close();
      is.close();
     }
     else{
      return false;
     }
    }catch (IOException e){
     e.printStackTrace();
     return false;
    }
   }

   return true;
  }

  @Override
  protected void onPostExecute(Boolean aBoolean) {
   super.onPostExecute(aBoolean);
   mPBar.dismiss();
   if (aBoolean){
    Toast.makeText(MainActivity.this,"    ",Toast.LENGTH_SHORT).show();
   } else {
    Toast.makeText(MainActivity.this,"    ",Toast.LENGTH_SHORT).show();
   }
  }

  @Override
  protected void onProgressUpdate(Integer... values) {
   super.onProgressUpdate(values);
   mPBar.setMax(fileSize / 1024);
   mPBar.setProgress(values[0]/1024);
  }
 }

}

상기 코드 중 몇 가지 주의 가 필요 합 니 다.
1.AsyncTask 의 execute 방법 은 주 스 레 드 에서 실행 되 어야 합 니 다.
2.AsyncTask 대상 마다 execute 방법 을 한 번 만 수행 할 수 있 습 니 다.
3.우리 의 Activity 가 삭 제 될 때 취소 작업 을 해 야 합 니 다.그 중에서 boolean 형의 매개 변수 인 mayInterrupt IfRunning 은 배경 작업 을 중단 할 지 여 부 를 표시 합 니 다.
마지막 레이아웃 코드 는 매우 간단 합 니 다.버튼 만 있 을 뿐 입 니 다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:padding="@dimen/activity_vertical_margin"
  android:orientation="vertical"
  tools:context="com.example.ljd.asynctask.MainActivity">

  <Button
    android:id="@+id/button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="download"/>
</LinearLayout>

여기 에는 또 한 가지 설명 이 필요 합 니 다.서로 다른 안 드 로 이 드 버 전에 서 AsyncTask 를 여러 번 수 정 했 기 때문에 여러 개의 AsyncTask 대상 을 통 해 여러 번 execute 방법 을 실 행 했 을 때 이들 의 실행 순 서 는 직렬 인지 병렬 인지 시스템 의 서로 다른 버 전에 따라 차이 가 나 면 구체 적 으로 분석 하지 않 습 니 다.
AsyncTask 소스 코드 분석
여기 서 우 리 는 Android 6.0 의 AsyncTask 소스 코드 를 이용 하여 분석 하 는데 서로 다른 시스템 의 AsyncTask 코드 에 대해 어느 정도 차이 가 있 을 것 이다.AsyncTask 대상 을 만 든 후에 우 리 는 execute 방법 을 통 해 모든 임 무 를 수행 할 수 있 습 니 다.그럼 여기 서 execute 방법 에 있 는 코드 부터 볼 게 요.

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
  return executeOnExecutor(sDefaultExecutor, params);
}
이 execute 방법의 코드 가 이렇게 간단 한 것 을 볼 수 있 습 니 다.execute OnExecutor 방법 을 실행 하고 AsyncTask 대상 으로 돌아 갈 뿐 입 니 다.다음은 execution Executor 라 는 방법 을 살 펴 보 겠 습 니 다.

@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();

  mWorker.mParams = params;
  exec.execute(mFuture);

  return this;
}

이 방법 에서 우 리 는 먼저 AsyncTask 가 실행 한 상 태 를 판단 한다.AsyncTask 가 임 무 를 수행 하고 있 거나 임 무 를 이미 알 고 완성 했다 면 이상 을 던 져 주 고 위 에서 말 한 모든 AsyncTask 대상 은 execute 방법 을 한 번 만 수행 할 수 있 습 니 다.이 어 현재 작업 상 태 를 실행 중인 후 onPreExecute 방법 으로 변경 합 니 다.이것 은 위 에서 우리 가 다시 쓴 네 가지 방법 중 onPreExecute 방법 이 가장 먼저 가리 키 는 것 을 설명 한다.다음은 mWorker 와 mFuture 라 는 두 개의 전역 변 수 를 살 펴 보 겠 습 니 다.
mFuture 는 Future Task 대상 이 고 mWorker 는 WorkerRunnable 대상 이 며 WorkerRunnable 은 AsyncTask 에서 Callable 인 터 페 이 스 를 실현 하 는 추상 적 인 내부 클래스 로 WorkerRunnable 에서 하나의 Params[]만 정의 합 니 다.

private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;

......

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
  Params[] mParams;
}

mFuture 와 mWorker 는 AsyncTask 의 구조 방법 에서 초기 화 되 었 습 니 다.AsyncTask 의 구조 방법 을 살 펴 보 자.

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);
      }
    }
  };
}

여기 서 먼저 WorkerRunnable 대상 mWorker 를 만 들 고 Callable 인터페이스의 call 방법 을 실현 하 였 으 며,이 call 의 내용 에 대해 서 는 뒤에서 상세 하 게 설명 할 것 입 니 다.그리고 mWorker 를 통 해 Future Task 대상 mFuture 를 만 들 고 그 안에 있 는 done 방법 을 다시 씁 니 다.AsyncTask 에 있 는 cancel 방법 을 호출 할 때 Future Task 에서 이 done 방법 을 호출 합 니 다.여기 서 mWorker 와 mFuture 를 소개 한 후에 우 리 는 다시 고 개 를 돌려 execution Executor 방법 을 살 펴 보 자.여기 서 우리 가 들 어 온 params 인 자 를 통 해 mWorker 의 mParams 를 초기 화 합 니 다.아래 exec 는 execute 에 들 어 오 는 sDefaultExecutor 이 며,sDefaultExecutor 의 execute 방법 을 실행 합 니 다.다음은 이 sDefaultExecutor 대상 을 다시 한 번 살 펴 보 겠 습 니 다.

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 ThreadFactory sThreadFactory = new ThreadFactory() {
  private final AtomicInteger mCount = new AtomicInteger(1);

  public Thread newThread(Runnable r) {
    return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
  }
};

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);

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) {
    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);
    }
  }
}

여기 서 먼저 이 코드 를 전체적으로 소개 하 겠 습 니 다.이 코드 의 주요 기능 은 두 개의 스 레 드 탱크 SerialExecutor 와 THREAD 를 만 드 는 것 입 니 다.POOL_EXECUTOR。SerialExecutor 스 레 드 탱크 는 작업 의 줄 을 서 는 데 사용 되 며,THREADPOOL_EXECUTOR 는 임 무 를 수행 하 는 데 사 용 됩 니 다.sDefaultExecutor 는 SerialExecutor 스 레 드 탱크 입 니 다.
SerialExecutor 코드 에 들 어가 서 내용 을 살 펴 보 겠 습 니 다.SerialExecutor 의 execute 방법 에 있 는 인자 Runnable 은 우리 가 들 어 오 는 mFuture 입 니 다.execute 방법 에서 Runnable 대상 을 만 들 고 이 대기 열 을 대상 의 끝 에 삽입 합 니 다.이 럴 때 임 무 를 처음 수행 하 는 거 라면mActive 는 반드시 null 입 니 다.이 때 scheduleNext 방법 으로 대기 열 에서 Runnable 대상 을 꺼 내 고 THREAD 를 통 해POOL_EXECUTOR 스 레 드 탱크 에서 작업 을 수행 합 니 다.대기 열 에 있 는 Runnable 의 run 방법 에서 mFuture 의 run 방법 을 먼저 실행 하고 실행 이 끝 난 후에 scheduleNext 방법 을 호출 하여 모든 Runnable 이 실 행 될 때 까지 대기 열 에서 Runnable 을 꺼 내 실행 하 는 것 을 볼 수 있 습 니 다.온라인 성 터 에서 이 Runnable 을 어떻게 수행 하 는 지 살 펴 보 겠 습 니 다.위의 코드 를 통 해 알 수 있 듯 이 온라인 도시 에서 Runnable 을 실행 하 는 가장 핵심 적 인 부분 은 mFuture 를 실행 하 는 run 방법 입 니 다.그럼 이 mFuture 의 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);
  }
}

이 방법 에서 callable 대상 은 우리 AsyncTask 의 mWorker 대상 입 니 다.이 안에서 도 mWorker 의 call 방법 을 실행 하여 시간 이 걸 리 는 임 무 를 수행 합 니 다.그래서 우 리 는 내 가 다시 쓴 doInBackground 가 이 call 방법 에서 실 행 될 것 이 라 고 생각 할 수 있 습 니 다.이제 AsyncTask 의 구조 방법 으로 돌아 가서 이 mWorker 의 call 방법 을 살 펴 보 겠 습 니 다.

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);
}

여기 서 우 리 는 그것 이 우리 가 다시 쓴 doInBackground 방법 을 실행 하고 돌아 온 결 과 를 post Result 방법 에 전달 하 는 것 을 잘 보 았 다.다음은 이 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;
}

이 post Result 방법 에 대해 서 는 하위 스 레 드 의 작업 처리 가 끝 난 후에 Handler 대상 을 통 해 Message 를 메 인 스 레 드 에 보 내 고 메 인 스 레 드 에 맡 깁 니 다.AsyncTask Result 대상 에 저 장 된 것 은 하위 스 레 드 가 되 돌아 온 결과 와 현재 AsyncTask 대상 입 니 다.다음은 Handler 에서 어떤 일 을 처 리 했 는 지 살 펴 보 겠 습 니 다.

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:
        // There is only one result
        result.mTask.finish(result.mData[0]);
        break;
      case MESSAGE_POST_PROGRESS:
        result.mTask.onProgressUpdate(result.mData);
        break;
    }
  }
}

이 Handler 의 handle Message 에서 두 가지 Message 를 받 을 수 있 습 니 다.MESSAGE 에서POST_PROGRESS 이 메 시 지 는 주로 PublishProgress 방법 을 통 해 하위 스 레 드 가 실 행 된 진 도 를 메 인 스 레 드 에 보 내 고 onProgressUpdate 방법 을 통 해 진도 바 를 업데이트 하 는 것 입 니 다.MESSAGE 에서POST_RESULT 라 는 메시지 에서 현재 AsyncTask 대상 을 통 해 finish 방법 을 호출 했 습 니 다.이 finish 방법 을 살 펴 보 겠 습 니 다.

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

이 럴 때 우리 가 AsyncTask 를 취소 하면 onPost Execute 방법 을 실행 하지 않 고 onCanceled 방법 을 실행 하 는 것 을 알 수 있 습 니 다.따라서 onCanceled 방법 을 다시 써 서 취소 할 때 우리 가 처리 해 야 할 작업 을 수행 할 수 있 습 니 다.물론 AsyncTask 가 취소 되 지 않 았 다 면,이 때 는 onPost Execute 방법 을 다시 실행 합 니 다.여기까지.
총결산
위의 SerialExecutor 스 레 드 탱크 에서 알 수 있 듯 이 여러 개의 비동기 작업 이 동시에 실 행 될 때 이들 이 수행 하 는 순 서 는 직렬 이 고 작업 이 만 든 선후 순서에 따라 한 번 실 행 됩 니 다.만약 우리 가 여러 작업 을 동시에 수행 하 기 를 원한 다 면 AsyncTask 의 setDefaultExecutor 방법 을 통 해 스 레 드 풀 을 THREAD 로 설정 할 수 있 습 니 다.POOL_EXECUTOR 면 됩 니 다.
AsyncTask 의 버 전 간 차이 점 에 대해 서 는 언급 할 수 밖 에 없습니다.Android 1.6 에서 AsyncTask 는 직렬 로 작업 을 수행 하고 Android 1.6 에 서 는 스 레 드 풀 로 병행 작업 을 처리 하 며 3.0 이후 에 야 SerialExecutor 스 레 드 풀 을 통 해 직렬 로 작업 을 처리 합 니 다.Android 4.1 이전에 AsyncTask 류 는 주 스 레 드 에 있어 야 하지만 다음 버 전에 서 는 시스템 에 의 해 자동 으로 완 료 됩 니 다.안 드 로 이 드 5.0 버 전에 서 는 Activity Thread 의 main 방법 에서 AsyncTask 의 init 방법 을 실행 하고 안 드 로 이 드 6.0 에 서 는 init 방법 을 삭제 합 니 다.따라서 이 AsyncTask 를 사용 할 때 더 많은 시스템 버 전이 적합 하 다 면 사용 할 때 주의해 야 합 니 다.
원본 다운로드:https://github.com/lijiangdong/asynctask-example
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기