從源碼角度看AsyncTask與LoaderManager多線程設(shè)計

題外話

哈,擱置了一段時間沒有寫博客播玖。主要是去研究Android虛擬機和ffmpeg中ffplay的源碼了。計劃上是時候把AsyncTask和其中蘊含的多線程編程思想和大家所得分享一下,自己也需要記錄一下洛二。
之后可能將計劃把handler最后一部分:native層中l(wèi)ooper使用管道掛起handler線程,從而讓步cpu資源的原理分析一下攻锰。當然如果覺得自己寫的沒有其他人好晾嘶,我也就不放出來了。

開篇以及題外話

AsyncTask是Android官方開放出來的多線程控制器娶吞。源碼我在16年的時候已經(jīng)分析過了附上地址:
http://blog.csdn.net/yujunyu12/article/details/52279927

題外話:我這才發(fā)現(xiàn)我這篇博文被csdn推薦過首頁垒迂,感覺是時候可以讓簡書和csdn的文章同步一下了。

這一篇文章我當時比較年輕只是把大體的流程解析了一遍妒蛇。也沒有把其中的關(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ū)保護原則 要等我準備好

4.Balking 阻行原則 不需要的話陶耍,就算了吧

5.Produce-Consumer 生產(chǎn)者和消費者模型

6.Read-Write Lock 讀寫鎖

7.Thread - per - Message 工作交于第三者實現(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);
                }
            }
        };
    }

實際上在我看來這個構(gòu)造函數(shù)在整個AsyncTask來說也是及其重要的地位臭脓。我很久之前是著重對AysncTask的線程池執(zhí)行器進行了解析。如今在看一遍AysncTask源碼沉桌,卻又有了新的理解谢鹊。我再一次看到這個構(gòu)造函數(shù)的時候,發(fā)現(xiàn)AsyncTask為什么說做的精妙留凭,為什么說做是一個經(jīng)典的異步工具佃扼〔嵴校看到了這個構(gòu)造函數(shù)之后爽茴,就會明白跪解,AsyncTask實際上是對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í)行完成");
    }
}

這段代碼的思想其實和上述我所說的多線程編程模式中的Future模式一致宙项。這個模式的核心思想是當我們需要從多線程里面獲取某個結(jié)果的時候癌淮,我們并不需要一致等待線程完成辱挥,而是只需要拿到一個線程對象的句柄或者說“拿到這個結(jié)果的兌換票”,之后在想獲取的地方獲取罩旋。

這么說還是太過于抽象了匠题,讓我們稍微看看java下面是怎么實現(xiàn)的拯坟。由于Call我們先來看看關(guā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);
        }
    }

這一段代碼的大致思路是當線程池啟動時候郁季,調(diào)用run方法。在run方法里面執(zhí)行call的的方法獲取計算結(jié)果钱磅。

這里有一點值得注意的是UNSAFE.compareAndSwapObject這個方法梦裂。這個方法是一個UNSAFE類的靜態(tài)native方法,對應(yīng)的是java虛擬機每個平臺下面的一個指令是一個原子操作故不需要擔心多線程多次訪問問題盖淡。結(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())

這個方法顧名思義,就是掃描類中的某個對象并且進行比較褪迟。那么這個方法調(diào)用意思是從這個類映射的內(nèi)存找到找到runner這個屬性對象偏移量也就是runner這個對象在內(nèi)存的位置冗恨,不斷的嘗試的修改為Thread.currentThread(),如果成功則返回true否則返回false味赃。

其實這個操作和我們用wait定義臨界區(qū)一個意思派近,不過就是這個方法是原子操作保證同一資源在同一時間只有一個線程訪問(不準確,因為在linux內(nèi)核中是搶占式調(diào)度的洁桌,這么說好理解)渴丸,而wait是保證只有一個線程訪問資源,手段是通過掛起其他線程。

這個不就是我們經(jīng)常所說的CAS樂觀鎖谱轨,或者說是自旋鎖嗎戒幔。很樂觀的認為這個屬性一定發(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í)行原則罢吃,這個原則是指在訪問同一個資源的時候楚午,最好是保證一次只有一個線程在對這個資源進行操作。

這個關(guān)鍵的函數(shù)理解了尿招。接下來理解FutureTask就好辦了矾柜。繼續(xù)回到run方法里面看看究竟做了什么事情:
1.我們在run里面也就是線程本體里面通過call方法獲取結(jié)果
2.接著把結(jié)果通過set方法設(shè)置進去,很明顯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è)置為下個線程運作run的方法缆瓣。當然里面還有該線程執(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模式是把線程實際的動作交給其他人來處理宜雀,Guarded Suspension模式切平,通過只允許單線程訪問資源,來保護資源辐董。這些組合起來才是完整future模式悴品。可以說futuretask看似簡單,實際里面的內(nèi)容多多苔严。在AsyncTask中涉及到了線程池定枷,而線程池實際上也是屬于Work-Thread的模式,意思就是線程池在待機届氢,有任務(wù)就開始工作欠窒。

當然最近比較流行的kotlin的線程協(xié)程模型實際上也是這么一個思路。等我有時間找找kotlin有沒有放出源碼退子,再給你驗證一下岖妄。我所寫的tnloader圖片加載器的okhttpsupport也是通過這種方式進行了okhttp和tnloader圖片加載兩個線程進行通信。

回到開頭寂祥,我們再回頭看看AsyncTask又是怎么回事荐虐。我們現(xiàn)在也能即使0基礎(chǔ)明白了AsyncTask的源碼。

構(gòu)造函數(shù)mWorker擴展了接口WorkerRunnable壤靶,也就是一個callable

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

那么也懂了mFuture缚俏,為什么要設(shè)置這個mWork,因為執(zhí)行器最后一定通過callable調(diào)用里面的run的方法贮乳。這里就不分析AsyncTask的異步執(zhí)行為什么是串行忧换,又怎么并行了。我前年已經(jīng)分析過了向拆。

這也是為什么我們說doInBackground(mParams)是在線程中工作,因為這個doInBackground是在call里面的亚茬,call實際上就是線程執(zhí)行的本體。

postResult(doInBackground(mParams));

而onPreExecute();又因為此時還沒有執(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浓恳,當執(zhí)行完成之后將會通過這個切換主線程刹缝。詳細的就看我16年寫的文章【苯總結(jié)一句話梢夯,AsyncTask實際上是對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");

    }
}
image.png

這個顯示的流程账劲,我順便把線程也打印下來戳护,順便我們可以清晰的看出也就是在loadInBackground的位置在另一個線程金抡。其他的都處于主線程中」贸撸看到打印了吧竟终,生成一個名字為AsyncTask的線程名,其實官方就是在告訴我們切蟋,AsyncLoader是封裝了AsyncTask一次统捶。我們稍稍看看getLoaderManager中的init初始化做了什么吧。

我們會發(fā)現(xiàn)這個getLoaderManager是Activity 中FragmentController的getLoaderManager方法柄粹。但是實際上是一個橋接模式喘鸟,把真正的工作交給mHost工作,

private final FragmentHostCallback<?> mHost;

public LoaderManager getLoaderManager() {
        return mHost.getLoaderManagerImpl();
    }

去FragmentHostCallback下面找實際對象


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

實際上會在每個FragmentController里面創(chuàng)建一個新的LoaderManagerImpl并且加入到一個ArrayMap進行管理驻右。當然如果已經(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森爽。實際上我們初始化也是這個LoaderInfo恨豁。在這里稍微提一下ArrayMap和SparseArray。ArrayMap是android特有的一種Map爬迟,拓展了Map的接口橘蜜,沒有entry這種對象反之用兩個數(shù)組維護,是能夠插入任何的數(shù)據(jù)付呕,而且在內(nèi)存上做了一些優(yōu)化计福。SparseArray有人經(jīng)常把他和map相比較,但是實際上并沒有實現(xiàn)map的接口徽职。只是用法和思想相似象颖,說不是map的成員也對,是也對姆钉。它有一點好處的是key值默認是int说订,減少了Integer的封裝類型,大大的減少了內(nèi)存的消耗育韩。有時間再分析吧克蚂。

現(xiàn)在開始真正的走起Loader的生命周期:

我們這邊按照第一次進來闺鲸,創(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,由于傳進來默認是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();
            }
        }

到這里Loader以及LoaderManager初始化就完成了嗜逻。

接著如果我們只是簡單的復寫了里面的方法涩僻,你會發(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();
    }

從這里我們一的得知我們自己也能復寫onForceLoad坪圾,參與進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實際上是一個LoadTask惑朦,擴展了runnable接口兽泄,繼承了AsyncTask。這個就是關(guān)鍵行嗤。而Handler的唯一的目的就是延時執(zhí)行這個runnnable對象以及在刪除Loader的時候已日,刪除掉在handler中的runnable回調(diào)。

也就是說栅屏,每一次都會生成一個AsyncTask對象飘千,Handler延時調(diào)用run的方法。

看到了executeOnExecutor這個串行執(zhí)行AsyncTask的方法栈雳,大致也就明了接下來的步驟了护奈。這邊把復寫的方法都列出來:

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)用了上面的方法霉旗,達成一個鏈式的調(diào)用。這個思路和AysncTask里面重寫了excutor的excute的方法思路很像蛀骇,區(qū)別在于一個AsyncTask中不斷向下一個任務(wù)調(diào)用厌秒,到了Loader里面是通過Loader不斷的的向下一個AsyncTask調(diào)用。

繼續(xù)關(guān)注Loader的生命周期擅憔。由于它復寫了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è)計出來继控,將真正的做事的對象交給線程。

當然也有一些其他沒有說到胖眷,比如調(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ù)運行朝刊。

2.AsyncTask容易內(nèi)存泄漏。

就以上面的例子來說蜈缤,加載圖片圖片如果是一個超長的時間拾氓,activity就算走了onDestroy也沒有辦法通過gc去回收。只要我們了解gc在android虛擬機原理就明白了底哥,android虛擬機是每一次申請內(nèi)存的時候才會試著做一次gc咙鞍。這個時候會通過遍歷Active堆(4.4以下,2.2以上)/Allcation或者LargeObjectMap(4.4以上)里面所認為的根對象集合趾徽。通過調(diào)用鏈不斷的遍歷续滋,最后如果發(fā)現(xiàn)AsyncTask還引用Activity的對象,那么Activity對象也會打上標記不允許清除孵奶。
這個詳細就在這里不做討論了疲酌,有時間再結(jié)合源碼分析,不過估計寫的不可能有羅升陽好拒课。

3.并發(fā)數(shù)目有限徐勃。

因為AsyncTask是一個線程池并發(fā)的。我們可以清晰的看到有128并發(fā)數(shù)量的上限早像。

主要是以上幾點僻肖,網(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方法揉稚。我們直接看看實現(xiàn)類怎么回收數(shù)據(jù)的。我們看看LoaderInfo是怎么運作的熬粗。

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)中把所有的引用鏈斷開佣盒。

做好了上述這幾點挎袜,我們就能在AsyncTaskLoader完美的避免內(nèi)存泄漏。

2.并發(fā)數(shù)量的問題肥惭。

如果仔細的讀者盯仪,應(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é)做處理。這導致一個什么問題呢脆霎?比如說我們在AsyncTask中執(zhí)行一個時間十分長的行為总处,我們就算調(diào)用了reset的方法,就算是Handler把AsyncTask看作runnable Remove掉睛蛛,也沒有辦法回收掉這個線程鹦马。

看來java和Android官方本來就是希望把如何結(jié)束線程這個行為交給我們開發(fā)者來判斷。

對于結(jié)束線程一個概念忆肾,要結(jié)束線程的手段有兩種荸频,一個是觸發(fā)Interceptor的異常,一個是讓線程自發(fā)的運行到run的最后一行客冈,自主結(jié)束這個線程行為旭从。第一種線程觸發(fā)了中斷雖然行為中斷了,實際上線程并不會立即回收场仲。那么最好處理方式是第二種遇绞,第二種才會使得線程自己死亡,被回收燎窘。當然還有一種stop的方法摹闽,這個不能使用,萬一你寫數(shù)據(jù)的時候突然斷了褐健,這就不得了了付鹿。

案例代碼:

        try {
            while (!shutdownRequest) {
                doWork();
            }
        } catch (InterruptedException e) {
            // TODO: handle exception
        }finally {
            doShutDown();
        }

我們可以通過一個標志位作為判斷,靈活使用try...finally的妙用回收線程內(nèi)的一些數(shù)據(jù)蚜迅。

轉(zhuǎn)換到AsyncTaskLoader的思路舵匾,我們完全可以設(shè)置一個標志位在loadInBackground里面,在onReset回調(diào)中把標志位設(shè)置掉谁不,告訴線程是時候結(jié)束坐梯,讓線程自己走完回收處理。

這就是推薦的線程回收方式刹帕。

結(jié)語:我本來只是想要稍微復習一下AsyncTask里面的代碼的吵血,分析一下我畢業(yè)的時候沒有注意到的問題,畢竟雖然簡單偷溺,但是還是十分經(jīng)典的蹋辅。這個時候,發(fā)現(xiàn)AsyncTaskLoader是AsyncTask的升級版本挫掏,而且里面還是有不少門道侦另,就發(fā)出來一起分析。原本以為官方對線程結(jié)束有什么很好的見解,沖著這個問題去學習褒傅,發(fā)現(xiàn)就是上面我說的結(jié)論弃锐。通過兩者之間的比較,現(xiàn)在比起AsyncTask我們當然優(yōu)先使用AsyncTaskLoader殿托。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末霹菊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子碌尔,更是在濱河造成了極大的恐慌,老刑警劉巖券敌,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唾戚,死亡現(xiàn)場離奇詭異,居然都是意外死亡待诅,警方通過查閱死者的電腦和手機叹坦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卑雁,“玉大人募书,你說我怎么就攤上這事〔舛祝” “怎么了莹捡?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長扣甲。 經(jīng)常有香客問我篮赢,道長,這世上最難降的妖魔是什么琉挖? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任启泣,我火速辦了婚禮,結(jié)果婚禮上示辈,老公的妹妹穿的比我還像新娘寥茫。我一直安慰自己,他們只是感情好矾麻,可當我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布纱耻。 她就那樣靜靜地躺著,像睡著了一般险耀。 火紅的嫁衣襯著肌膚如雪膝迎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天胰耗,我揣著相機與錄音限次,去河邊找鬼。 笑死,一個胖子當著我的面吹牛卖漫,可吹牛的內(nèi)容都是我干的费尽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼羊始,長吁一口氣:“原來是場噩夢啊……” “哼旱幼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起突委,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤柏卤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后匀油,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缘缚,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年敌蚜,在試婚紗的時候發(fā)現(xiàn)自己被綠了桥滨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡弛车,死狀恐怖齐媒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情纷跛,我是刑警寧澤喻括,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站贫奠,受9級特大地震影響双妨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜叮阅,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一刁品、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浩姥,春花似錦挑随、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至眯分,卻和暖如春拌汇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背弊决。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工噪舀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留魁淳,地道東北人。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓与倡,卻偏偏與公主長得像界逛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子纺座,可洞房花燭夜當晚...
    茶點故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容