AsyncTask 의 작업 원리
53169 단어 안 드 로 이 드 소스 코드안 드 로 이 드 기술
AsyncTask 류 는 일반적으로 안 드 로 이 드 개발 자 들 이 접촉 하 는 최초의 비동기 처리 방법 입 니 다. 현재 유행 하 는 새로운 비동기 임무 류, 예 를 들 어 RxJava 등 이 있 지만 AsyncTask 의 바 텀 실현 원 리 는 배 울 필요 가 있 습 니 다. 우리 가 다른 프레임 워 크 를 이해 하거나 자신의 디자인 프레임 워 크 를 이해 하 는 데 큰 도움 이 됩 니 다.이것 은 handler 와 스 레 드 탱크 의 방식 으로 비동기 작업 을 해서 결 과 를 메 인 스 레 드 로 되 돌려 줍 니 다.
데이터 구조
AsyncTask 의 원 리 를 설명 하기 전에 우 리 는 먼저 사용 하 는 데이터 구 조 를 복습 하거나 배 워 야 한다.이것들 은 우리 가 반드시 알 아야 할 데이터 구조 이다.
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.
public interface Callable<V>{
V call() throws Exception;
}
이러한 설명 을 통 해 알 수 있 듯 이 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.
위의 데이터 구조 해석 을 통 해 사실은 기본적으로 소스 코드 가 뚜렷 해 졌 다.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 를 사용 하면 몇 가지 주의 점 이 있 습 니 다:
* 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 기술 과 데이터 구조 기술 은 우리 가 배 울 만 한 가치 가 있 으 며 모든 기술 구 조 는 선행 기술 을 바탕 으로 대응 하 는 장단 점 에 따라 확장 된다.도움 이 된다 면 좋아요 좀 눌 러 주세요.문제 가 있 으 면 소통 에 답 할 수 있다.