題外話
哈,擱置了一段時間沒有寫博客。主要是去研究Android虛擬機(jī)和ffmpeg中ffplay的源碼了。計劃上是時候把AsyncTask和其中蘊(yùn)含的多線程編程思想和大家所得分享一下,自己也需要記錄一下。
之后可能將計劃把handler最后一部分:native層中l(wèi)ooper使用管道掛起handler線程,從而讓步cpu資源的原理分析一下。當(dāng)然如果覺得自己寫的沒有其他人好,我也就不放出來了。
開篇以及題外話
AsyncTask是Android官方開放出來的多線程控制器。源碼我在16年的時候已經(jīng)分析過了附上地址:
http://blog.csdn.net/yujunyu12/article/details/52279927
題外話:我這才發(fā)現(xiàn)我這篇博文被csdn推薦過首頁,感覺是時候可以讓簡書和csdn的文章同步一下了。
這一篇文章我當(dāng)時比較年輕只是把大體的流程解析了一遍。也沒有把其中的關(guān)節(jié)講透。而LoadManager 被稱為可以用來代替AsyncTask的更好方案。這一次我將結(jié)合LoadManager和asynctask一起分析一下,Android官方對多線程的編程思想以及為什么網(wǎng)上的人老是說AsyncTask內(nèi)存泄露。
多線程編程的一些設(shè)計模式
在這里我稍微借用java多線程設(shè)計模式一書中,所設(shè)定的多線程程設(shè)計的模式概念:
1.Single Threaded Excution 單線程執(zhí)行模式(能通過這座橋的只有一個人)
2.Immutable 不變原則(想破壞它也沒辦法)
3.Guarded Suspension 臨界區(qū)保護(hù)原則 要等我準(zhǔn)備好
4.Balking 阻行原則 不需要的話,就算了吧
5.Produce-Consumer 生產(chǎn)者和消費(fèi)者模型
6.Read-Write Lock 讀寫鎖
7.Thread - per - Message 工作交于第三者實(shí)現(xiàn)
8.Worker-Thread 工作線程,有工作就完成
9.Future 先獲取對象再獲取到線程結(jié)果
10.Two-Phase Termiation 線程結(jié)束模式
11.Thread-Special Storage Thread-local每個線程自身的存儲map
以上就是這本書對多線程設(shè)計模式的定義。
我們結(jié)合上面的思想分析一下AsyncTask,LoadManager其中的源碼。
關(guān)于AsyncTask的源碼我很早就分析過了,這邊稍微提一下。下面分析的源碼老規(guī)矩還是5.1.0版本的。
先讓我們看看關(guān)鍵的幾處:
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
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);
}
}
};
}
實(shí)際上在我看來這個構(gòu)造函數(shù)在整個AsyncTask來說也是及其重要的地位。我很久之前是著重對AysncTask的線程池執(zhí)行器進(jìn)行了解析。如今在看一遍AysncTask源碼,卻又有了新的理解。我再一次看到這個構(gòu)造函數(shù)的時候,發(fā)現(xiàn)AsyncTask為什么說做的精妙,為什么說做是一個經(jīng)典的異步工具??吹搅诉@個構(gòu)造函數(shù)之后,就會明白,AsyncTask實(shí)際上是對Java的異步調(diào)用FutureTask做了第二次封裝。
我們先來看看一個FutureTask的常見用法:
創(chuàng)建一個Call對象
public class Call implements Callable<Integer> {
private int sum;
@Override
public Integer call() throws Exception {
for(int i=0 ;i<5000;i++){
sum=sum+i;
}
return sum;
}
}
在主函數(shù):
public class FutureTest {
public static void main(String[] args){
ExecutorService service = Executors.newFixedThreadPool(1);
Call call = new Call();
Future<Integer> future = service.submit(call);
service.shutdown();
try {
Thread.sleep(2000);
System.out.println("主線程在執(zhí)行其他任務(wù)");
if(future.get()!=null){
//輸出獲取到的結(jié)果
System.out.println("future.get()-->"+future.get());
}else{
//輸出獲取到的結(jié)果
System.out.println("future.get()未獲取到結(jié)果");
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("主線程在執(zhí)行完成");
}
}
這段代碼的思想其實(shí)和上述我所說的多線程編程模式中的Future模式一致。這個模式的核心思想是當(dāng)我們需要從多線程里面獲取某個結(jié)果的時候,我們并不需要一致等待線程完成,而是只需要拿到一個線程對象的句柄或者說“拿到這個結(jié)果的兌換票”,之后在想獲取的地方獲取。
這么說還是太過于抽象了,讓我們稍微看看java下面是怎么實(shí)現(xiàn)的。由于Call我們先來看看關(guān)鍵的運(yùn)行方法run。
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 {
//此處獲取結(jié)果,調(diào)用了call方法
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);
}
}
這一段代碼的大致思路是當(dāng)線程池啟動時候,調(diào)用run方法。在run方法里面執(zhí)行call的的方法獲取計算結(jié)果。
這里有一點(diǎn)值得注意的是UNSAFE.compareAndSwapObject這個方法。這個方法是一個UNSAFE類的靜態(tài)native方法,對應(yīng)的是java虛擬機(jī)每個平臺下面的一個指令是一個原子操作故不需要擔(dān)心多線程多次訪問問題。結(jié)合一下下面貼的代碼稍微說一下
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long stateOffset;
private static final long runnerOffset;
private static final long waitersOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = FutureTask.class;
stateOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("state"));
runnerOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("runner"));
waitersOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("waiters"));
} catch (Exception e) {
throw new Error(e);
}
}
結(jié)合兩個代碼,就可以知道
UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread())
這個方法顧名思義,就是掃描類中的某個對象并且進(jìn)行比較。那么這個方法調(diào)用意思是從這個類映射的內(nèi)存找到找到runner這個屬性對象偏移量也就是runner這個對象在內(nèi)存的位置,不斷的嘗試的修改為Thread.currentThread(),如果成功則返回true否則返回false。
其實(shí)這個操作和我們用wait定義臨界區(qū)一個意思,不過就是這個方法是原子操作保證同一資源在同一時間只有一個線程訪問(不準(zhǔn)確,因?yàn)樵趌inux內(nèi)核中是搶占式調(diào)度的,這么說好理解),而wait是保證只有一個線程訪問資源,手段是通過掛起其他線程。
這個不就是我們經(jīng)常所說的CAS樂觀鎖,或者說是自旋鎖嗎。很樂觀的認(rèn)為這個屬性一定發(fā)生變化,不斷的在循環(huán)檢測該地址的值是否改變。我們可以如此寫可以等價上面的操作。
private Thread t;
public synchronized void waitforObject(){
while (t==null){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我也曾經(jīng)想去操作這個UNSAFE類,既然java隱藏了,那就算了。發(fā)現(xiàn)沒,這里面也是符合了多線程編程的單一線程執(zhí)行原則,這個原則是指在訪問同一個資源的時候,最好是保證一次只有一個線程在對這個資源進(jìn)行操作。
這個關(guān)鍵的函數(shù)理解了。接下來理解FutureTask就好辦了。繼續(xù)回到run方法里面看看究竟做了什么事情:
1.我們在run里面也就是線程本體里面通過call方法獲取結(jié)果
2.接著把結(jié)果通過set方法設(shè)置進(jìn)去,很明顯outcome就是我們想要的結(jié)果。
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
這樣我們再看看get和其中調(diào)用的report方法:
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
每一次獲取度判斷一次其等待的時間是否超時,超時拋出異常,沒有超時則獲取在outcome的數(shù)據(jù)返回。接著通過waitNode設(shè)置為下個線程運(yùn)作run的方法。當(dāng)然里面還有該線程執(zhí)行的狀態(tài)
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
這里暫時不做任何討論。
FutureTask暫時分析到這里。說到這里我們大概知道所謂的future模式就是拿到包裹結(jié)果的對象,再通過對象從里面獲取真正的結(jié)果。從這里我們可以模仿FutureTask,寫一個簡單版本的:
Data.java
public interface Data {
public abstract String getContent();
}
FutureData.java
public class FutureData implements Data {
private RealData data = null;
private boolean ready = false;
public synchronized void setRealData(RealData data){
if(ready){
return;//balk模式,臨界值不正確則不執(zhí)行
}
this.data = data;
this.ready = true;
notifyAll();
}
@Override
public synchronized String getContent() {
// TODO Auto-generated method stub
//臨界區(qū)中等待數(shù)據(jù)加載成功
while (!ready) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
}
}
return data.getContent();
}
}
RealData.java
public class RealData implements Data {
//為了顯示出效果把加載過程用sleep加長了寫入時間
private final String content;
public RealData(int count ,char c) {
// TODO Auto-generated constructor stub
System.out.println(" making RealData("+count+","+c+") BEGIN");
char[] buffer = new char[count];
for(int i=0;i<count;i++){
buffer[i] = c;
try{
Thread.sleep(100);
}catch(InterruptedException e){
}
}
System.out.println(" making RealData("+count+","+c+") END");
this.content = new String(buffer);
}
@Override
public String getContent() {
// TODO Auto-generated method stub
return content;
}
}
Host.java
public class Host {
public Data request(final int count,final char c){
System.out.println(" request("+count+","+c+") BEGIN");
final FutureData futureData = new FutureData();
//這個是Thread-pre 好處在于可以迅速拿到FutureData的對象,但是要獲取里面的RealData的內(nèi)容,需要等待線程解鎖
new Thread(){
public void run() {
RealData data = new RealData(count, c);
futureData.setRealData(data);
};
}.start();
System.out.println(" request("+count+","+c+") END");
return futureData;
}
}
public class Main {
public static void main(String[] args) {
System.out.println("main BEGIN");
Host host = new Host();
Data data1 = host.request(10, 'A');
Data data2 = host.request(20, 'B');
Data data3 = host.request(30, 'C');
System.out.println("DO other");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("DO other END");
System.out.println("data1: "+data1.getContent());
System.out.println("data2: "+data2.getContent());
System.out.println("data3: "+data3.getContent());
System.out.println("main END");
}
}
似乎之前說的很簡單,自己著手寫一個類似futuretask還是涉及到了不少的多線程模式,如balk模式,一旦檢測到返回值不正確則立即返回。Thread-pre模式是把線程實(shí)際的動作交給其他人來處理,Guarded Suspension模式,通過只允許單線程訪問資源,來保護(hù)資源。這些組合起來才是完整future模式??梢哉ffuturetask看似簡單,實(shí)際里面的內(nèi)容多多。在AsyncTask中涉及到了線程池,而線程池實(shí)際上也是屬于Work-Thread的模式,意思就是線程池在待機(jī),有任務(wù)就開始工作。
當(dāng)然最近比較流行的kotlin的線程協(xié)程模型實(shí)際上也是這么一個思路。等我有時間找找kotlin有沒有放出源碼,再給你驗(yàn)證一下。我所寫的tnloader圖片加載器的okhttpsupport也是通過這種方式進(jìn)行了okhttp和tnloader圖片加載兩個線程進(jìn)行通信。
回到開頭,我們再回頭看看AsyncTask又是怎么回事。我們現(xiàn)在也能即使0基礎(chǔ)明白了AsyncTask的源碼。
構(gòu)造函數(shù)mWorker擴(kuò)展了接口WorkerRunnable,也就是一個callable
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
那么也懂了mFuture,為什么要設(shè)置這個mWork,因?yàn)閳?zhí)行器最后一定通過callable調(diào)用里面的run的方法。這里就不分析AsyncTask的異步執(zhí)行為什么是串行,又怎么并行了。我前年已經(jīng)分析過了。
這也是為什么我們說doInBackground(mParams)是在線程中工作,因?yàn)檫@個doInBackground是在call里面的,call實(shí)際上就是線程執(zhí)行的本體。
postResult(doInBackground(mParams));
而onPreExecute();又因?yàn)榇藭r還沒有執(zhí)行線程所以也是線程之外
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;
}
而我之前也說過了里面有一個handler,當(dāng)執(zhí)行完成之后將會通過這個切換主線程。詳細(xì)的就看我16年寫的文章??偨Y(jié)一句話,AsyncTask實(shí)際上是對FutureTask的封裝。在通過模板模式,將對應(yīng)的操作暴露出來。
我們切換一下,看一下LoadManager為什么很多人說這個用來替代AsyncTask。我們挑最相似AsyncTaskLoader。這個類是繼承Loader的抽象類。老規(guī)矩,讓我們看看AsyncTaskLoader怎么用,再來分析源碼吧。
public class AsyncLoader extends AsyncTaskLoader<Integer> {
public AsyncLoader(Context context) {
super(context);
}
@Override
protected void onStartLoading() {
super.onStartLoading();
Log.e("TAG","start "+Thread.currentThread());
forceLoad();
}
@Override
public Integer loadInBackground() {
Log.e("TAG","load "+Thread.currentThread());
return 100;
}
@Override
public void forceLoad() {
super.forceLoad();
Log.e("TAG","forceLoad "+Thread.currentThread());
}
@Override
public void deliverResult(Integer data) {
super.deliverResult(data);
Log.e("TAG","deliverResult"+Thread.currentThread());
}
@Override
protected void onStopLoading() {
super.onStopLoading();
Log.e("TAG","onStopLoading "+Thread.currentThread());
cancelLoad();
}
@Override
protected void onReset() {
super.onReset();
Log.e("TAG","onReset "+Thread.currentThread());
}
@Override
protected boolean onCancelLoad() {
Log.e("TAG","onCancelLoad "+Thread.currentThread());
return super.onCancelLoad();
}
}
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Integer> {
private static final int ID = 0;
private String TAG = "TAG";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(getLoaderManager().getLoader(ID) == null){
Log.e(TAG,"no loader");
}else {
Log.e(TAG,"has loader");
}
getLoaderManager().initLoader(ID,null,this);
}
@Override
public Loader<Integer> onCreateLoader(int id, Bundle args) {
Log.e(TAG,"onCreateLoader");
return new AsyncLoader(this);
}
@Override
public void onLoadFinished(Loader<Integer> loader, Integer data) {
Log.e(TAG,"onLoadFinished "+data);
}
@Override
public void onLoaderReset(Loader<Integer> loader) {
Log.e(TAG,"onCreateLoader");
}
}

這個顯示的流程,我順便把線程也打印下來,順便我們可以清晰的看出也就是在loadInBackground的位置在另一個線程。其他的都處于主線程中??吹酱蛴×税?,生成一個名字為AsyncTask的線程名,其實(shí)官方就是在告訴我們,AsyncLoader是封裝了AsyncTask一次。我們稍稍看看getLoaderManager中的init初始化做了什么吧。
我們會發(fā)現(xiàn)這個getLoaderManager是Activity 中FragmentController的getLoaderManager方法。但是實(shí)際上是一個橋接模式,把真正的工作交給mHost工作,
private final FragmentHostCallback<?> mHost;
public LoaderManager getLoaderManager() {
return mHost.getLoaderManagerImpl();
}
去FragmentHostCallback下面找實(shí)際對象
LoaderManagerImpl getLoaderManagerImpl() {
if (mLoaderManager != null) {
return mLoaderManager;
}
mCheckedForLoaderManager = true;
mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true /*create*/);
return mLoaderManager;
}
LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
if (mAllLoaderManagers == null) {
mAllLoaderManagers = new ArrayMap<String, LoaderManager>();
}
LoaderManagerImpl lm = (LoaderManagerImpl) mAllLoaderManagers.get(who);
if (lm == null && create) {
lm = new LoaderManagerImpl(who, this, started);
mAllLoaderManagers.put(who, lm);
} else if (started && lm != null && !lm.mStarted){
lm.doStart();
}
return lm;
}
實(shí)際上會在每個FragmentController里面創(chuàng)建一個新的LoaderManagerImpl并且加入到一個ArrayMap進(jìn)行管理。當(dāng)然如果已經(jīng)創(chuàng)建了那么則會從這個map中找到對應(yīng)的loadermanager。一般的,我們那只有名字為(root)的loadermanager,也就說至始至終只有一個。
那么就要從LoaderManagerImpl里面去找到我們想要的初始化方法。
final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>(0);
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
if (mCreatingLoader) {
throw new IllegalStateException("Called while creating a loader");
}
LoaderInfo info = mLoaders.get(id);
if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
if (info == null) {
// Loader doesn't already exist; create.
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
if (DEBUG) Log.v(TAG, " Created new loader " + info);
} else {
if (DEBUG) Log.v(TAG, " Re-using existing loader " + info);
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
if (info.mHaveData && mStarted) {
// If the loader has already generated its data, report it now.
info.callOnLoadFinished(info.mLoader, info.mData);
}
return (Loader<D>)info.mLoader;
}
我們先從mLoaders這個SparseArray獲取到對應(yīng)id的LoaderInfo。實(shí)際上我們初始化也是這個LoaderInfo。在這里稍微提一下ArrayMap和SparseArray。ArrayMap是android特有的一種Map,拓展了Map的接口,沒有entry這種對象反之用兩個數(shù)組維護(hù),是能夠插入任何的數(shù)據(jù),而且在內(nèi)存上做了一些優(yōu)化。SparseArray有人經(jīng)常把他和map相比較,但是實(shí)際上并沒有實(shí)現(xiàn)map的接口。只是用法和思想相似,說不是map的成員也對,是也對。它有一點(diǎn)好處的是key值默認(rèn)是int,減少了Integer的封裝類型,大大的減少了內(nèi)存的消耗。有時間再分析吧。
現(xiàn)在開始真正的走起Loader的生命周期:
我們這邊按照第一次進(jìn)來,創(chuàng)建loaderinfo,也就是說走了createAndInstallLoader的方法。
private LoaderInfo createAndInstallLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<Object> callback) {
try {
mCreatingLoader = true;
LoaderInfo info = createLoader(id, args, callback);
installLoader(info);
return info;
} finally {
mCreatingLoader = false;
}
}
private LoaderInfo createLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<Object> callback) {
LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
Loader<Object> loader = callback.onCreateLoader(id, args);
info.mLoader = (Loader<Object>)loader;
return info;
}
從這里就得知會通過回調(diào)走onCreatLoader拿到loader對象。也就是上面打印的第一個步驟。
接著再走installLoader方法。
void installLoader(LoaderInfo info) {
mLoaders.put(info.mId, info);
if (mStarted) {
// The activity will start all existing loaders in it's onStart(),
// so only start them here if we're past that point of the activitiy's
// life cycle
info.start();
}
}
再來看看installLoader,由于傳進(jìn)來默認(rèn)是true,那么一定會走info.start,接著就走到Loader的onStartLoading的回調(diào)。
void start() {
if (mRetaining && mRetainingStarted) {
// Our owner is started, but we were being retained from a
// previous instance in the started state... so there is really
// nothing to do here, since the loaders are still started.
mStarted = true;
return;
}
if (mStarted) {
// If loader already started, don't restart.
return;
}
mStarted = true;
if (DEBUG) Log.v(TAG, " Starting: " + this);
if (mLoader == null && mCallbacks != null) {
mLoader = mCallbacks.onCreateLoader(mId, mArgs);
}
if (mLoader != null) {
if (mLoader.getClass().isMemberClass()
&& !Modifier.isStatic(mLoader.getClass().getModifiers())) {
throw new IllegalArgumentException(
"Object returned from onCreateLoader must not be a non-static inner member class: "
+ mLoader);
}
if (!mListenerRegistered) {
mLoader.registerListener(mId, this);
mLoader.registerOnLoadCanceledListener(this);
mListenerRegistered = true;
}
mLoader.startLoading();
}
}
到這里L(fēng)oader以及LoaderManager初始化就完成了。
接著如果我們只是簡單的復(fù)寫了里面的方法,你會發(fā)現(xiàn)這個異步根本沒有執(zhí)行。我們還需要在onStartLoading里面調(diào)用forceLoad();去刷新數(shù)據(jù)。才會真正的啟動線程去執(zhí)行。
由于我們初始化的是AsyncTaskLoader,我們這個時候應(yīng)該去AsyncTaskLoader里面查看forceLoad();方法。這個方法又調(diào)用了onForceLoad,而執(zhí)行之前會調(diào)用cancelLoad回調(diào)取消上次正在加載的方法,我們適合把一些需要回收的數(shù)據(jù)在這里回收一次。
@Override
protected void onForceLoad() {
super.onForceLoad();
cancelLoad();
mTask = new LoadTask();
if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
executePendingTask();
}
從這里我們一的得知我們自己也能復(fù)寫onForceLoad,參與進(jìn)forceLoad方法的調(diào)用。
關(guān)鍵的方法是executePendingTask
void executePendingTask() {
if (mCancellingTask == null && mTask != null) {
if (mTask.waiting) {
mTask.waiting = false;
mHandler.removeCallbacks(mTask);
}
if (mUpdateThrottle > 0) {
long now = SystemClock.uptimeMillis();
if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
// Not yet time to do another load.
if (DEBUG) Log.v(TAG, "Waiting until "
+ (mLastLoadCompleteTime+mUpdateThrottle)
+ " to execute: " + mTask);
mTask.waiting = true;
mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
return;
}
}
if (DEBUG) Log.v(TAG, "Executing: " + mTask);
mTask.executeOnExecutor(mExecutor, (Void[]) null);
}
}
這個mTask實(shí)際上是一個LoadTask,擴(kuò)展了runnable接口,繼承了AsyncTask。這個就是關(guān)鍵。而Handler的唯一的目的就是延時執(zhí)行這個runnnable對象以及在刪除Loader的時候,刪除掉在handler中的runnable回調(diào)。
也就是說,每一次都會生成一個AsyncTask對象,Handler延時調(diào)用run的方法。
看到了executeOnExecutor這個串行執(zhí)行AsyncTask的方法,大致也就明了接下來的步驟了。這邊把復(fù)寫的方法都列出來:
final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
private final CountDownLatch mDone = new CountDownLatch(1);
// Set to true to indicate that the task has been posted to a handler for
// execution at a later time. Used to throttle updates.
boolean waiting;
/* Runs on a worker thread */
@Override
protected D doInBackground(Void... params) {
if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
try {
D data = AsyncTaskLoader.this.onLoadInBackground();
if (DEBUG) Log.v(TAG, this + " <<< doInBackground");
return data;
} catch (OperationCanceledException ex) {
if (!isCancelled()) {
// onLoadInBackground threw a canceled exception spuriously.
// This is problematic because it means that the LoaderManager did not
// cancel the Loader itself and still expects to receive a result.
// Additionally, the Loader's own state will not have been updated to
// reflect the fact that the task was being canceled.
// So we treat this case as an unhandled exception.
throw ex;
}
if (DEBUG) Log.v(TAG, this + " <<< doInBackground (was canceled)", ex);
return null;
}
}
/* Runs on the UI thread */
@Override
protected void onPostExecute(D data) {
if (DEBUG) Log.v(TAG, this + " onPostExecute");
try {
AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
} finally {
mDone.countDown();
}
}
/* Runs on the UI thread */
@Override
protected void onCancelled(D data) {
if (DEBUG) Log.v(TAG, this + " onCancelled");
try {
AsyncTaskLoader.this.dispatchOnCancelled(this, data);
} finally {
mDone.countDown();
}
}
/* Runs on the UI thread, when the waiting task is posted to a handler.
* This method is only executed when task execution was deferred (waiting was true). */
@Override
public void run() {
waiting = false;
AsyncTaskLoader.this.executePendingTask();
}
/* Used for testing purposes to wait for the task to complete. */
public void waitForLoader() {
try {
mDone.await();
} catch (InterruptedException e) {
// Ignore
}
}
}
我們可以指知道,每一次Handler執(zhí)行都會調(diào)用一次run里面的方法,run又調(diào)用了上面的方法,達(dá)成一個鏈?zhǔn)降恼{(diào)用。這個思路和AysncTask里面重寫了excutor的excute的方法思路很像,區(qū)別在于一個AsyncTask中不斷向下一個任務(wù)調(diào)用,到了Loader里面是通過Loader不斷的的向下一個AsyncTask調(diào)用。
繼續(xù)關(guān)注Loader的生命周期。由于它復(fù)寫了doInBackground,onPostExecute,onCancelled三個方法。
由于我們分析了doInBackGround方法,就會懂了異步線程就會調(diào)用一次抽象方法loadInBackground里面。
一旦結(jié)束了通過onPostExecute調(diào)用dispatchOnLoadComplete方法,該方法最終也會通過deliverResult(data);回調(diào)到onLoadComplete中。
deliverResult走完父類的的方法才走到我們重寫的方法中。
大致上我們AsyncTaskLoader的生命周期的完成了。
這個在java編程中就是一個很明顯的模板設(shè)計模式,而在多線程設(shè)計模式又是結(jié)合了Thread - per - Message和Work-Thread設(shè)計出來,將真正的做事的對象交給線程。
當(dāng)然也有一些其他沒有說到,比如調(diào)用onContentChanged告訴容器變化了,刷新一次數(shù)據(jù),不過最后還是調(diào)用forceload這里不做討論。我們甚至可以在OnStartThread直接調(diào)用deliverResult直接獲取緩存的數(shù)據(jù),不需要每次都重新調(diào)用線程來獲取數(shù)據(jù)等。
由于這個源碼十分的簡單,這一次就不上源碼類的流程圖了。
源碼都分析清楚,我們是時候說一下網(wǎng)上一些關(guān)于asynctask缺陷。
1.很明顯,AsyncTask的生命周期比activity要長。
就用網(wǎng)絡(luò)加載圖片為例子,由于是開了線程在工作,如果圖片沒有加載好,就算是activity走了onDestroy,它也會繼續(xù)運(yùn)行。
2.AsyncTask容易內(nèi)存泄漏。
就以上面的例子來說,加載圖片圖片如果是一個超長的時間,activity就算走了onDestroy也沒有辦法通過gc去回收。只要我們了解gc在android虛擬機(jī)原理就明白了,android虛擬機(jī)是每一次申請內(nèi)存的時候才會試著做一次gc。這個時候會通過遍歷Active堆(4.4以下,2.2以上)/Allcation或者LargeObjectMap(4.4以上)里面所認(rèn)為的根對象集合。通過調(diào)用鏈不斷的遍歷,最后如果發(fā)現(xiàn)AsyncTask還引用Activity的對象,那么Activity對象也會打上標(biāo)記不允許清除。
這個詳細(xì)就在這里不做討論了,有時間再結(jié)合源碼分析,不過估計寫的不可能有羅升陽好。
3.并發(fā)數(shù)目有限。
因?yàn)锳syncTask是一個線程池并發(fā)的。我們可以清晰的看到有128并發(fā)數(shù)量的上限。
主要是以上幾點(diǎn),網(wǎng)上還說了結(jié)果丟失和串并行問題,在我的眼里根本還算不上主要矛盾。
那么AsyncTaskLoader又處理的如何呢?
1.針對生命周期和內(nèi)存泄漏的問題:
final void performDestroy() {
mDestroyed = true;
mWindow.destroy();
mFragments.dispatchDestroy();
onDestroy();
mFragments.doLoaderDestroy();
if (mVoiceInteractor != null) {
mVoiceInteractor.detachActivity();
}
}
我們可以清楚的發(fā)現(xiàn)整個LoaderManager的生命周期將會依賴這個Activity。一旦銷毀的activity,則立即調(diào)用LoaderManager的doLoaderDestroy方法。我們直接看看實(shí)現(xiàn)類怎么回收數(shù)據(jù)的。我們看看LoaderInfo是怎么運(yùn)作的。
void destroy() {
if (DEBUG) Log.v(TAG, " Destroying: " + this);
mDestroyed = true;
boolean needReset = mDeliveredData;
mDeliveredData = false;
if (mCallbacks != null && mLoader != null && mHaveData && needReset) {
if (DEBUG) Log.v(TAG, " Reseting: " + this);
String lastBecause = null;
if (mHost != null) {
lastBecause = mHost.mFragmentManager.mNoTransactionsBecause;
mHost.mFragmentManager.mNoTransactionsBecause = "onLoaderReset";
}
try {
mCallbacks.onLoaderReset(mLoader);
} finally {
if (mHost != null) {
mHost.mFragmentManager.mNoTransactionsBecause = lastBecause;
}
}
}
mCallbacks = null;
mData = null;
mHaveData = false;
if (mLoader != null) {
if (mListenerRegistered) {
mListenerRegistered = false;
mLoader.unregisterListener(this);
mLoader.unregisterOnLoadCanceledListener(this);
}
mLoader.reset();
}
if (mPendingLoader != null) {
mPendingLoader.destroy();
}
}
主要做的工作注銷了注冊的監(jiān)聽接口,這個是主要用來監(jiān)聽Loader里面的加載是否成功,加載是否取消,第二個先后調(diào)用了一個在重寫LoaderManager抽象方法的onLoaderReset,接著再回調(diào)Loader的reset方法。
這樣就完成了生命周期是怎么綁定的。那么相對的我們也要在AsyncLoader的onReset回調(diào)中回收一些由Loader引用到其他位置的資源,在onLoaderonReset回收一些Activity引用其他位置的一些資源,保證自己在這個回調(diào)中把所有的引用鏈斷開。
做好了上述這幾點(diǎn),我們就能在AsyncTaskLoader完美的避免內(nèi)存泄漏。
2.并發(fā)數(shù)量的問題。
如果仔細(xì)的讀者,應(yīng)該知道是怎么解決的。就和okhttp一樣,不依賴下面的線程池最大并發(fā)數(shù),而是保證每個線程任務(wù)保存在隊列里面,每執(zhí)行完一個,從隊列中獲取下一個任務(wù)。
這個也是一樣,只允許AsyncTask執(zhí)行一個線程,但是每一次都是一個AsyncTask作為任務(wù)通過Handler不斷的輪詢下一個AsyncTask的任務(wù)。這樣就避免了并發(fā)數(shù)目的問題。
關(guān)于AsyncTask和AsyncTaskLoader都有的一些小遺憾
不知道讀者注意到?jīng)]有,除了用于讀寫這種特殊操作的讀寫鎖多線程模式之外,還有一種模式Two-Phase Termiation并沒有涉及到,其他8種多線程主要的設(shè)計模式都涉及到了。
這個模式是用來終結(jié)線程工作的。換言之,兩者都沒有對線程的終結(jié)做處理。這導(dǎo)致一個什么問題呢?比如說我們在AsyncTask中執(zhí)行一個時間十分長的行為,我們就算調(diào)用了reset的方法,就算是Handler把AsyncTask看作runnable Remove掉,也沒有辦法回收掉這個線程。
看來java和Android官方本來就是希望把如何結(jié)束線程這個行為交給我們開發(fā)者來判斷。
對于結(jié)束線程一個概念,要結(jié)束線程的手段有兩種,一個是觸發(fā)Interceptor的異常,一個是讓線程自發(fā)的運(yùn)行到run的最后一行,自主結(jié)束這個線程行為。第一種線程觸發(fā)了中斷雖然行為中斷了,實(shí)際上線程并不會立即回收。那么最好處理方式是第二種,第二種才會使得線程自己死亡,被回收。當(dāng)然還有一種stop的方法,這個不能使用,萬一你寫數(shù)據(jù)的時候突然斷了,這就不得了了。
案例代碼:
try {
while (!shutdownRequest) {
doWork();
}
} catch (InterruptedException e) {
// TODO: handle exception
}finally {
doShutDown();
}
我們可以通過一個標(biāo)志位作為判斷,靈活使用try...finally的妙用回收線程內(nèi)的一些數(shù)據(jù)。
轉(zhuǎn)換到AsyncTaskLoader的思路,我們完全可以設(shè)置一個標(biāo)志位在loadInBackground里面,在onReset回調(diào)中把標(biāo)志位設(shè)置掉,告訴線程是時候結(jié)束,讓線程自己走完回收處理。
這就是推薦的線程回收方式。
結(jié)語:我本來只是想要稍微復(fù)習(xí)一下AsyncTask里面的代碼的,分析一下我畢業(yè)的時候沒有注意到的問題,畢竟雖然簡單,但是還是十分經(jīng)典的。這個時候,發(fā)現(xiàn)AsyncTaskLoader是AsyncTask的升級版本,而且里面還是有不少門道,就發(fā)出來一起分析。原本以為官方對線程結(jié)束有什么很好的見解,沖著這個問題去學(xué)習(xí),發(fā)現(xiàn)就是上面我說的結(jié)論。通過兩者之間的比較,現(xiàn)在比起AsyncTask我們當(dāng)然優(yōu)先使用AsyncTaskLoader。