講解順序:
1.AsyncTask簡單介紹
2.主要方法及作用
3.應用與使用
4.實現原理分析
1.AsyncTask簡單介紹
AsyncTask是開發中常用的異步實現工具,又因爲其無需再通過Handler 更新ui ,所以使用起來比較方便,在開發中使用頻率較高。內部主要由Handler ,線程池 實現類ThreadPoolExecutor 等構成,主要實現了異步執行任務並且可執行ui線程任務,下面我們先從使用在研究如何實現的。
2.主要方法及作用
AsyncTask是一個抽象類,所以要使用必須要繼承它
public abstract class AsyncTask<Params, Progress, Result>
泛型1:代表傳入參數的可變數組類型,既定義 doInBackground 的參數類型
泛型2:代表異步任務的執行進度的可變長度數據類型,既定義onProgressUpdate 參數類型
泛型3:代表異步任務執行結果的返回值類型及 異步執行結束onPostExecute 參數類型
private class MyAsyncTask extends AsyncTask<String, Integer, Boolean>{
/**
* 任務即將開始
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* 任務已經開始執行了,此處執行耗時任務
* @param params
* @return
*/
@Override
protected Boolean doInBackground(String... params) {
return null;
}
/**
* 任務執行結束,返回異步執行結果
* @param aBoolean
*/
@Override
protected void onPostExecute(Boolean aBoolean) {
super.onPostExecute(aBoolean);
}
/**
* 任務的執行進度
* @param values
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
/**
* 將異步任務設置爲:取消狀態
* @param aBoolean
*/
@Override
protected void onCancelled(Boolean aBoolean) {
super.onCancelled(aBoolean);
}
}
這裏單獨說下 publishProgress 方法
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
它的作用是用來更新當前異步任務執行的進度,在doInBackground 中調用,其參數類型爲一個可變數組,類型就是新建類時泛型3的類型。當調用 publishProgress 方法後會執行 onProgressUpdate 方法,此方法在ui線程中執行
3.應用與使用
上面講了方法的使用,當我們將耗時操作在 doInBackground 處理完畢並且給出返回值,在onPostExecute中處理ui邏輯後,開始調用
MyAsyncTask myAsyncTask=new MyAsyncTask();
myAsyncTask.execute();
這裏調用的是無參數的execute()方法,也可以
myAsyncTask.execute("1"); 這裏簡單傳遞了一個參數1,這裏的參數類型就是泛型1所定義的可變長度數組類型了。
使用我們簡單聊這麼多,如果你想要詳細瞭解使用方法可以參考: AsyncTask使用
下面我們將對AsyncTask進行詳細的原理分析
4.實現原理分析
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
開始執行任務調用了execute 方法並且參數是一個可變數組,調用 executeOnExecutor 方法。
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();
//將參數賦值給mParams
mWorker.mParams = params;
//調用Executor接口的execute方法
exec.execute(mFuture);
return this;
}
這裏看到調用了onPreExecute 方法用於即將開始任務的一些數據處理,將參數存儲在實現了Callable接口的
WorkerRunnable抽象類中,然後調用Executor的execute方法。這裏插入看下AsyncTask的構造函數。
public AsyncTask() {
this((Looper) null);
}
實際上調用了public AsyncTask(@Nullable Looper callbackLooper)
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return 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);
}
}
};
}
首先獲Looper 對象,如果你不瞭解handler的原理建議先看寫下Handler 的實現原理,否則可能有點蒙,意思就是獲取主線程looper對象,並且綁定handler.
WorkerRunnable 方法是實現了Callable 接口的靜態類內部只有一個數組。
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
這裏mWorker 既(WorkerRunnable)初始化,call()方法啥時執行我們後邊分析,接着mFuture既(FutureTask)被初始化了,FutureTask是對於具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果的。現在我們重新回到
executeOnExecutor方法。 看這行 exec.execute(mFuture); 那麼這個execute在哪裏執行的呢?
首先 executeOnExecutor 方法第一個參數是Executor 這段我們上邊看到過
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
sDefaultExecutor 是個啥呢?
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
他只不過是一個靜態變量SERIAL_EXECUTOR 賦值的。
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
SERIAL_EXECUTOR 是一個靜態類變量,且一開始就初始化了,看下SerialExecutor方法
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);
}
}
}
因爲AsyncTask 本身是一個抽象類, SerialExecutor 是抽象類中靜態類初始化,無論AsyncTask被多少類繼承 其屬性決定了SerialExecutor 及 ArrayDeque 只會被new 一次 。所以每次調用線程池方法mTasks都是同一個變量。ArrayDeque雙端隊列的實現類。
offer方法是將此Runnable 加入到隊列的末尾,然後逐個執行。run方法中執行了r.run();這個方法很重要,那麼這個run是在哪裏執行的呢。
我們回到 executeOnExecutor 方法,exec.execute(mFuture)其實就是 SerialExecutor 中的execute方法,這裏的r 變量就是 mFuture,mFuture是 FutureTask 的變量名,所以r.run()調用的是FutureTask中的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);
}
}
現在我們看下 FutureTask 類
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
這個方法是Futuretask 的一個構造函數,就是將傳進來的參數callable 賦值給自身的變量callable 並且將狀態設爲new。上邊我們在AsyncTask的構造函數中有兩個類的初始化其中Futretask 類的初始化調用的就是這個構造函數。再看下
public AsyncTask(@Nullable Looper callbackLooper)方法。
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
原來傳進來的是mWorker 既 實現了Callable 接口的抽象類WorkerRunnable 類。
現在回到FutureTask 類的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<V> c = callable; 將callable(上邊剛講過) 賦值給變量c ,c不是null 並且 state 狀態是NEW,執行WorkerRunnable 的call()方法
緊接着,如果調用成功ran==true 執行set方法。
call方法回調執行內容
public AsyncTask(@Nullable Looper callbackLooper) 方法
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
在這裏執行了doInBackground方法,並且將mParams 參數傳遞給它。
然後看FutureTask 中set方法
protected void set(V v) {
if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
outcome = v;
U.putOrderedInt(this, STATE, NORMAL); // final state
finishCompletion();
}
}
執行了finishCompletion
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (U.compareAndSwapObject(this, WAITERS, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
主要看下done()方法,mFuture 重寫了done 方法,所以會調用到
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);
}
}
};
看下postResultIfNotInvoked方法
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
postResult方法
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
通過handler發送消息 ,所以下邊的操作都在ui線程中執行了
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
調用了finish方法
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
是否取消,如果沒有停止就調用 onPostExecute 並且將狀態設爲 FINISHED
這時 重寫的onPostExecute執行了,並且參數爲我們定義AsyncTask 泛型的參數三的參數類型。既 返回 子線程中
doInBackground的返回結果。
這裏可能仔細看會有疑問,在call()方法中的 finally 中也執行了 postResult方法 ,在done 中最後也執行了postResult方法,不是說最後onPostExecute 會調用兩遍了,不對啊,如果你看的夠仔細你會找到答案,在call()方法中
mTaskInvoked.set(true); 方法 ,將value設爲1;
public final void set(boolean newValue) {
value = newValue ? 1 : 0;
}
當調用done 方法中的 postResultIfNotInvoked 時首先會去獲取該值,並且是0纔會向下執行
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
所以如果call成功執行,done 中的onPostExecute 就不執行了。
到現在已經講了主要三個方法 的執行過程
onPreExecute 主線程中
doInBackground 子線程中
onPostExecute 主線程中
這是我們使用AsyncTask最常用的三個方法,上邊爲了快速理清流程,有些地方一筆帶過了,現在我們重新認識下SerialExecutor類,
這個類我們上邊提過,現在我覺得有必要仔細看下,因爲它決定了AsyncTask中線程池的執行過程及時機。
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實現了Executor接口,可以看到其內部只有一個方法execute方法參數是Runnable 接口
public interface Executor {
void execute(Runnable command);
}
開始執行會調用到executeOnExecutor中的exec.execute(mFuture); exec就是SerialExecutor類的變量,會執行SerialExecutor內的
execute方法,然後執行offer方法,但是Runnable 接口的Run方法不會被執行,只是new 了一個Runnable 放入mTasks數組中,因爲沒人去調用run方法,所以不會被調用,接着判斷mActive是否爲空,第一次調用當然是空了,所以會執行scheduleNext()方法。
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
從隊列首部獲取一個元素給mActive賦值,然後調用
THREAD_POOL_EXECUTOR的execute 方法,這個獲取到的mActive就是剛纔new 的Runnable接口了,所以,run方法的執行要看THREAD_POOL_EXECUTOR的execute具體如何實現。這個暫且先放下,後邊再仔細分析,先把流程理通。直接看run方法被調用執行了,上邊我們已經分析過r.run(),實際上是調用了FutureTask中的run方法,這裏就不講了,它使用了try finally 語法,我們知道finally中的語法無論如何是都會執行的。所以當一個任務執行完成以後,都會去調用下scheduleNext方法,用於查找隊列中是否還有未執行的任務,如果有,就又開始執行。這是一個有序隊列按照先後順序執行。所以,如果你有多個類繼承自AsyncTask 並且調用了它的execute方法,那麼他會按照順序一個一個的去執行,並不是併發執行。看到這裏,這點我們要明白。
現在我們來看下THREAD_POOL_EXECUTOR的execute 方法,THREAD_POOL_EXECUTOR其實是間接實現了Executor接口的子類ThreadPoolExecutor所以看下其內部execute 實現,這裏要看的仔細些,要看的問題是:
mTasks.offer 所new的那個Runnable 接口的run方法何時,在哪裏執行?
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
/*1.獲取當前正在運行線程數是否小於核心線程池,是則新創建一個線程執行任務,否則將任務放到任務隊列中*/
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))//在addWorker中創建工作線程執行任務
return;
c = ctl.get();
}
/*2.當前核心線程池中全部線程都在運行workerCountOf(c) >= corePoolSize,所以此時將線程放到任務隊列中*/
if (isRunning(c) && workQueue.offer(command)) {//線程池是否處於運行狀態,且是否任務插入任務隊列成功
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))//線程池是否處於運行狀態,如果不是則使剛剛的任務出隊
reject(command);//拋出RejectedExceptionException異常
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/*3.插入隊列不成功,且當前線程數數量小於最大線程池數量,此時則創建新線程執行任務,創建失敗拋出異常*/
else if (!addWorker(command, false))
reject(command);//拋出RejectedExceptionException異常
}
我們上邊說了,Runnable 接口的run方法調用沒有看到,這裏我們就仔細分析下,主要方法addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//如果當前線程數已經大於最大線程數直接返回
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//將正在運行的線程數+1,數量自增成功則跳出循環,自增失敗則繼續從頭繼續循環
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//正在運行的線程數自增成功後則將線程封裝成工作線程Worker
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//將線程封裝爲Worker工作線程
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
//全局鎖
final ReentrantLock mainLock = this.mainLock;
//獲取全局鎖
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
/*當持有了全局鎖的時候,還需要再次檢查線程池的運行狀態等*/
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
////線程處於活躍狀態,即線程已經開始執行或者還未死亡,正確的應線程在這裏應該是還未開始執行的
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//包含線程池中所有的工作線程,只有在獲取了全局的時候才能訪問它。將新構造的工作線程加入到工作線程集合中
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
//新構造的工作線程加入成功
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//在被構造爲Worker工作線程,且被加入到工作線程集合中後,執行線程任務,注意這裏的start實際上執行Worker中run方法,所以接下來分析Worker的run方法
t.start();
workerStarted = true;
}
}
} finally {
//未能成功創建執行工作線程
if (! workerStarted)
//在啓動工作線程失敗後,將工作線程從集合中移除
addWorkerFailed(w);
}
return workerStarted;
}
主要的方法已經做過備註。其中主要Worker工作線程類;
w = new Worker(firstTask); 將傳進來的Runnable 接口傳給Worker
看下Worker 類
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker. */
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
首先它實現了Runnable 接口,構造函數內,將傳進來的Runnable 接口賦值給自身的firstTask變量,同時從線程工廠獲取一個線程,並將實現的Runnable 綁定
this.thread = getThreadFactory().newThread(this);
所以在addWorker方法中調用 t.start() 既調用了 Worker 中獲取到的thread 對象的start,所以此時Worker中的Run()方法被執行了。
public void run() {
runWorker(this);
}
這裏調用了 final void runWorker(Worker w) 方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//獲取Worker 中的firstTask構造參數
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
//調用Runnable 接口的run方法
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
這裏看到最終調用的run方法,這個run方法就是我們上邊提的問題的答案:
mTasks.offer 所new的那個Runnable 接口的run方法何時,在哪裏執行? ,是的,就是在這裏執行的。
所以,在這個run方法中運行的方法都是在子線程中運行的,所以我們可以在doInBackground 方法中執行異步任務。
還有更新進度相關的兩個方法沒有介紹,由於篇幅有點長,也搞了幾天了,就暫時結束,後邊會專門針對這兩個方法再寫一遍補充文章。
如果你看到文章中有錯誤,或者有更好的理解,歡迎留言交流,或者加qq 1301749314
參考:
https://www.cnblogs.com/yulinfeng/p/7021293.html
https://www.cnblogs.com/sg9527/p/8004502.html
https://zhidao.baidu.com/question/1639323053250406060.html
https://blog.csdn.net/zmx729618/article/details/52767736
https://blog.csdn.net/dove_knowledge/article/details/71077512