加強(qiáng)版異步任務(wù)方案

一、前言

為了提高流暢性,耗時(shí)任務(wù)放后臺(tái)線程運(yùn)行,已是APP開(kāi)發(fā)的常識(shí)了。
關(guān)于異步有很多方案,當(dāng)前最流行的,莫過(guò)于RxJava了;
更早一些時(shí)候,還有AsyncTask(骨灰級(jí)的API)。

總的來(lái)說(shuō),AsyncTask構(gòu)思精巧,代碼簡(jiǎn)潔,使用方便,有不少地方值得借鑒。
當(dāng)然問(wèn)題也有不少,比如不能隨Activity銷毀而銷毀導(dǎo)致的內(nèi)存泄漏,還有不適合做長(zhǎng)時(shí)間的任務(wù)等。

筆者以AsyncTask為范本,寫了一個(gè)“AsyncTaskPlus”:
保留了AsyncTask的所有用法,解決了其中的一些問(wèn)題,同時(shí)引入了一些新特性。
接下來(lái)給大家介紹一下這“加強(qiáng)版”的框架,希望對(duì)各位有所啟發(fā)。

二、任務(wù)調(diào)度

2.1 AsyncTask的Executor

AsyncTask的任務(wù)調(diào)度主要依賴兩個(gè)Executor:ThreadPoolExecutor 和 SerialExecutor。
代碼如下:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

public static final Executor THREAD_POOL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

static {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 30, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(128), sThreadFactory);
    threadPoolExecutor.allowCoreThreadTimeOut(true);
    THREAD_POOL_EXECUTOR = threadPoolExecutor;
}

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

關(guān)于線程池,估計(jì)大家都很熟悉了,參數(shù)就不多作解釋了。
如果不是很熟悉,推薦閱讀筆者的另一篇文章《速讀Java線程池》。

上面代碼中,通過(guò)巧用“裝飾者模式”,增加“串行調(diào)度”的功能。
裝飾者模式有以下特點(diǎn):

  1. 裝飾對(duì)象和真實(shí)對(duì)象有相同的接口,這樣客戶端對(duì)象就能以和真實(shí)對(duì)象相同的方式和裝飾對(duì)象交互。
  2. 裝飾對(duì)象包含一個(gè)真實(shí)對(duì)象的引用。
  3. 裝飾對(duì)象接受所有來(lái)自客戶端的請(qǐng)求,它把這些請(qǐng)求轉(zhuǎn)發(fā)給真實(shí)的對(duì)象。
  4. 裝飾對(duì)象可以在轉(zhuǎn)發(fā)這些請(qǐng)求以前或以后增加一些附加功能。

SerialExecutor只有二十來(lái)行代碼,卻用了兩次裝飾者模式:Runnable和Executor。

  • Runnable部分,往隊(duì)列添加的匿名Runnable對(duì)象(裝飾對(duì)象),當(dāng)被Executor調(diào)用run()方法時(shí),先執(zhí)行“真實(shí)對(duì)象”的run()方法,然后再調(diào)用scheduleNext();
  • Executor部分,通過(guò)增加一個(gè)任務(wù)隊(duì)列,實(shí)現(xiàn)串行調(diào)度的功能,而具體的任務(wù)執(zhí)行轉(zhuǎn)發(fā)給“真實(shí)對(duì)象”THREAD_POOL_EXECUTOR。

想要串行調(diào)度,為什么不多加一個(gè)coreSize=1的ThreadPoolExecutor呢?
兩個(gè)ThreadPoolExecutor,彼此線程不可復(fù)用。

雖然SerialExecutor的方案很不錯(cuò),但是THREAD_POOL_EXECUTOR的coreSize太小了(不超過(guò)4),
這導(dǎo)致AsyncTask不適合執(zhí)行長(zhǎng)時(shí)間運(yùn)行的任務(wù),否則多幾個(gè)任務(wù)就會(huì)堵塞。
因此,如果要改進(jìn)AsyncTask,首先要改進(jìn)Executor。

2.2 通用版Executor

實(shí)現(xiàn)思路和 SerialExecutor 差不多,加一個(gè)隊(duì)列, 實(shí)現(xiàn)另一層調(diào)度控制。
首先,把 RunnablescheduleNext 兩部分都抽象出來(lái):

interface Trigger {
    fun next()
}

class RunnableWrapper constructor(
        private val r: Runnable,
        private val trigger: Trigger) : Runnable {
    override fun run() {
        try {
            r.run()
        } finally {
            trigger.next()
        }
    }
}

接下來(lái)的實(shí)現(xiàn)和SerialExecutor類似:

class PipeExecutor @JvmOverloads constructor(
        windowSize: Int,
        private val capacity: Int = -1,
        private val rejectedHandler: RejectedExecutionHandler = defaultHandler) : TaskExecutor {

    private val tasks = PriorityQueue<RunnableWrapper>()
    private val windowSize: Int = if (windowSize > 0) windowSize else 1
    private var count = 0

    private val trigger : Trigger = object : Trigger {
        override fun next() {
            scheduleNext()
        }
    }

    fun execute(r: Runnable, priority: Int) {
        schedule(RunnableWrapper(r, trigger), priority)
    }

    @Synchronized
    internal fun scheduleNext() {
        count--
        if (count < windowSize) {
            startTask(tasks.poll())
        }
    }

    @Synchronized
    internal fun schedule(r: RunnableWrapper, priority: Int) {
        if (capacity > 0 && tasks.size() >= capacity) {
            rejectedHandler.rejectedExecution(r, TaskCenter.poolExecutor)
        }
        if (count < windowSize || priority == Priority.IMMEDIATE) {
            startTask(r)
        } else {
            tasks.offer(r, priority)
        }
    }

    private fun startTask(active: Runnable?) {
        if (active != null) {
            count++
            TaskCenter.poolExecutor.execute(active)
        }
    }
}

解析一下代碼中的參數(shù)和變量:

  • tasks:任務(wù)緩沖區(qū)
  • count:正在執(zhí)行的任務(wù)的數(shù)量
  • windowSize:并發(fā)窗口,控制Executor的并發(fā)
  • capacity:任務(wù)緩沖區(qū)容量,小于等于0時(shí)為不限容量,超過(guò)容量觸發(fā)rejectedHandler
  • rejectedHandler:默認(rèn)為AbortPolicy(拋出異常)
  • priority:調(diào)度優(yōu)先級(jí)

當(dāng)count>=windowSize時(shí),priority高者先被調(diào)度;
優(yōu)先級(jí)相同的任務(wù),遵循先進(jìn)先出(FIFO)的調(diào)度規(guī)則。

需要注意的是,調(diào)度優(yōu)先級(jí)不同于線程優(yōu)先級(jí),線程優(yōu)先級(jí)更底層一些。
比如AsyncTask的doInBackground()中就調(diào)用了:
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
這可以使得后臺(tái)線程的線程優(yōu)先級(jí)低于UI線程。

以下是PipeExecutor的流程圖:


定義了PipeExecutor了之后,我們可以實(shí)現(xiàn)多個(gè)實(shí)例。
例如,可以仿照 RxJava 的 Schedulers,定義適用于“IO密集型”任務(wù)和“計(jì)算密集型”任務(wù)的Executor。

val io = PipeExecutor(20, 512)
val computation = PipeExecutor(Math.min(Math.max(2, cpuCount), 4), 512)

也可以定義串行調(diào)度的Executor:

val single = PipeExecutor(1)

不過(guò)我們不建議定義全局的串行調(diào)度Executor,因?yàn)闀?huì)有相互阻塞的風(fēng)險(xiǎn)。
但是可以根據(jù)場(chǎng)景定義專屬的串行調(diào)度Executor,比如給日志收集創(chuàng)建一個(gè),給數(shù)據(jù)上報(bào)創(chuàng)建一個(gè)……

不同實(shí)例,猶如不同的水管,往同一個(gè)池子進(jìn)水,故而命名為PipeExecutor。

2.3 去重版Executor

我們項(xiàng)目中,頁(yè)面更新用的是“發(fā)布訂閱模式”:
數(shù)據(jù)層有變更,發(fā)布更新消息;
上層收到消息,異步加載數(shù)據(jù),刷新頁(yè)面。

然后就碰到一個(gè)問(wèn)題:若短時(shí)間內(nèi)有多次數(shù)據(jù)更新,就會(huì)有多個(gè)消息發(fā)往上層。
不做特殊處理,就會(huì)幾乎同時(shí)啟動(dòng)多個(gè)異步任務(wù),浪費(fèi)計(jì)算資源;
多個(gè)線程對(duì)并發(fā)讀取同一數(shù)據(jù),多線程問(wèn)題也隨之而來(lái),若處理不好,結(jié)果不可預(yù)知。

用串行執(zhí)行器?所有任務(wù)串行的話,無(wú)法利用任務(wù)并發(fā)的優(yōu)勢(shì)。

所以經(jīng)過(guò)比較多種方案,最終的結(jié)論是:

  • 1、任務(wù)分組,不同組并行,同組串行
  • 2、同組的任務(wù),如果有任務(wù)在執(zhí)行,最多只能有一個(gè)在等待,丟棄后面的任務(wù)

所謂分組,就是給任務(wù)打tag, 比如刷新A數(shù)據(jù)的任務(wù)叫ATask, 刷新B任務(wù)的叫BTask。

關(guān)于第2點(diǎn),其實(shí)有考慮過(guò)其他一些方案,比如下面兩個(gè):

  • 取消正在執(zhí)行的任務(wù)
    • 首先不是所有任務(wù)都可以中斷的,可以不接收其結(jié)果,但是不一定能中斷其執(zhí)行
    • 即使能取消(比如中斷網(wǎng)絡(luò)請(qǐng)求),也不是最佳方案。
      比方說(shuō)當(dāng)前線程或許已經(jīng)快要下載完了,在等一會(huì)后面的任務(wù)就可以讀緩存去結(jié)果了;
      任務(wù)2取消任務(wù)1,任務(wù)3取消任務(wù)2……等到最后一個(gè)任務(wù)執(zhí)行,用戶可能已經(jīng)不耐煩了。
  • 如果有任務(wù)在執(zhí)行,丟棄后面的任務(wù)
    比方說(shuō)任務(wù)1讀取了數(shù)據(jù),在計(jì)算的時(shí)候,數(shù)據(jù)源變更,然后發(fā)送事件,啟動(dòng)任務(wù)2……
    直接丟棄后面的任務(wù),最終頁(yè)面顯示的是舊的數(shù)據(jù)。

我們定義了一個(gè)LaneExecutor來(lái)實(shí)現(xiàn)這個(gè)方案,示意圖如下:

discard=true

各組任務(wù)就像一個(gè)個(gè)車道(Lane), 故而命名為L(zhǎng)aneExecutor。

洋蔥似地一層包一層,很明顯,也是裝飾者模式。
職責(zé)分配:
LaneExecutor負(fù)責(zé)任務(wù)去重;
PipeExecutor負(fù)責(zé)任務(wù)并發(fā)控制和調(diào)度優(yōu)先級(jí);
ThreadPoolExecutor負(fù)責(zé)分配線程來(lái)執(zhí)行任務(wù)。

但后來(lái)又遇到另一個(gè)問(wèn)題:
有多個(gè)控件要加載同一個(gè)URL的數(shù)據(jù),然后很自然地我們就以 URL作為tag了,以避免重復(fù)下載(做有緩存,第一任務(wù)下載完成之后,后面的任務(wù)可以讀取緩存)。
但是用LaneExecutor來(lái)執(zhí)行時(shí),只保留一個(gè)任務(wù)在等待,然后最終只有兩個(gè)控件能顯示數(shù)據(jù)。
查到問(wèn)題后,筆者給LaneExecutor加了一種模式,該模式下,不丟棄任務(wù)。

discard=false

如此,所有任務(wù)都會(huì)被執(zhí)行,但是只有第一個(gè)需要下載數(shù)據(jù),后面任務(wù)讀緩存就好了。

2.4 統(tǒng)一管理Executor

當(dāng)項(xiàng)目復(fù)雜度到了一定程度,如果沒(méi)有統(tǒng)一的公共定義,可能會(huì)出現(xiàn)各種冗余實(shí)例。
分散的Executor無(wú)法較好地控制并發(fā);
如果各自創(chuàng)建的是ThreadPoolExecutor,則還要加上一條:降低線程復(fù)用。
故此,可以集中定義Executor,各模塊統(tǒng)一調(diào)用。
代碼如下:

object TaskCenter {
    internal val poolExecutor: ThreadPoolExecutor = ThreadPoolExecutor(
            0, 256,
            60L, TimeUnit.SECONDS,
            SynchronousQueue(),
            threadFactory)
    
    // 常規(guī)的任務(wù)調(diào)度器,可控制任務(wù)并發(fā),支持任務(wù)優(yōu)先級(jí)
    val io = PipeExecutor(20, 512)
    val computation = PipeExecutor(Math.min(Math.max(2, cpuCount), 4), 512)

    // 帶去重策略的 Executor,可用于數(shù)據(jù)刷新等任務(wù)
    val laneIO = LaneExecutor(io, true)
    val laneCP = LaneExecutor(computation, true)

    // 相同的tag的任務(wù)會(huì)被串行執(zhí)行,相當(dāng)于串行的Executor
    // 可用于寫日志,上報(bào)統(tǒng)計(jì)信息等任務(wù)
    val serial = LaneExecutor(PipeExecutor(Math.min(Math.max(2, cpuCount), 4), 1024))
}

2.5 Executor的使用

    TaskCenter.io.execute{
        // do something
    }

    TaskCenter.laneIO.execute("laneIO", {
        // do something
    }, Priority.HIGH)

    val serialExecutor = PipeExecutor(1)
    serialExecutor.execute{
        // do something
    }

    TaskCenter.serial.execute ("your tag", {
        // do something
    })
  • PipeExecutor的使用和常規(guī)的Executor是一樣的,execute中傳入Runnable即可,
    然后由于Runnable只有一個(gè)方法,也沒(méi)有參數(shù),lambda的形式就顯得更加簡(jiǎn)潔了。
  • LaneExecutor由于要給任務(wù)打tag, 所以要傳入tag參數(shù);
    如果不傳,則沒(méi)有分組的效果,也就是回退到PipeExecutor的特性;
  • 兩種Executor都可以傳入優(yōu)先級(jí)。

很多開(kāi)源項(xiàng)目都設(shè)計(jì)了API來(lái)使用外部的Executor,比如RxJava可以這樣用:

object TaskSchedulers {
    val io: Scheduler by lazy { Schedulers.from(TaskCenter.io) }
    val computation: Scheduler by lazy { Schedulers.from(TaskCenter.computation) }
    val single by lazy { Schedulers.from(PipeExecutor(1)) }
}
Observable.range(1, 8)
       .subscribeOn(TaskSchedulers.computation)
       .subscribe { Log.d(tag, "number:$it") }

這樣有一個(gè)好處,各種任務(wù)都在一個(gè)線程池上執(zhí)行任務(wù),可復(fù)用彼此創(chuàng)建的線程。

三、流程控制

3.1 AsyncTask的執(zhí)行流

上一章我們分析了任務(wù)調(diào)度,構(gòu)造了一系列Executor,增強(qiáng)任務(wù)處理方面的通用性。
不過(guò)任務(wù)調(diào)度只是AsyncTask的一部分,AsyncTask的精髓其實(shí)在于流程控制:在任務(wù)執(zhí)行的不同階段,回調(diào)相應(yīng)的方法。
下面是AsyncTask的流程圖:

通過(guò)使用FutureTask和Callable,使得AsyncTask具備對(duì)任務(wù)執(zhí)行更強(qiáng)的控制力,比如cancel任務(wù)。
有的文章說(shuō)cancel()不一定的立即中斷任務(wù),但其實(shí)Futuret.cancel()確實(shí)已經(jīng)是最好的方案了,
如果強(qiáng)行調(diào)用Thread.stop(),則猶如關(guān)掉空中飛機(jī)的引擎,后果不堪設(shè)想。

通過(guò)與Handler的配合,AsyncTask可以在任務(wù)執(zhí)行過(guò)程中和執(zhí)行結(jié)束后發(fā)布數(shù)據(jù)到UI線程,
這使得AsyncTask尤其適用于“數(shù)據(jù)加載+界面刷新”的場(chǎng)景。
而這類場(chǎng)景在APP開(kāi)發(fā)中較為常見(jiàn),這也是AsyncTask一度被廣泛使用的原因之一。

3.2 生命周期

AsyncTask其中一個(gè)廣為詬病的問(wèn)題就是內(nèi)存泄漏:
若AsyncTask持有Activity引用,且生命周期比Activity的長(zhǎng),則Activity無(wú)法被及時(shí)回收。
這個(gè)問(wèn)題其實(shí)不是AsyncTask獨(dú)有,Handler,RxJava等都存在類似問(wèn)題。
解決方案有多種,靜態(tài)類、弱引用、Activity銷毀時(shí)取消等。
RxJava提供了dispose方法來(lái)取消任務(wù),同時(shí)也有很多集成生命周期的開(kāi)源方案,比如RxLifecycle、AutoDispose等。

AsyncTask也提供了cancel方法,但是比較命苦,吐槽者眾,助力者寡。
其實(shí)要實(shí)現(xiàn)自動(dòng)cancel不難,建立和Activity/Fragment的關(guān)系即可,可通過(guò)觀察者模式來(lái)實(shí)現(xiàn)。


UITask是參考AsyncTask寫的一個(gè)類, 使用了上一章介紹的Executor。
結(jié)構(gòu)上,UITask為觀察者,Activity/Fragment為被觀察者,LifecycleManager為 UITask 和 Activity/Fragment 構(gòu)建關(guān)系的橋梁。
實(shí)現(xiàn)上需要兩個(gè)數(shù)據(jù)結(jié)構(gòu):一個(gè)SparseArray,一個(gè)List。
SparseArray的key為被觀察者的identityHashCode, value為觀察者列表。

UITask提供了host()方法,方法中獲取宿主(也就是Activity/Fragment)的identityHashCode,
通過(guò)register()方法,添加 “Activity->UITask” 到SparseArray中。

abstract class UITask<Params, Progress, Result> : LifeListener {
    fun host(host: Any): UITask<Params, Progress, Result> {
        LifecycleManager.register(System.identityHashCode(host), this)
        return this
    }

    override fun onEvent(event: Int) {
        if (event == LifeEvent.DESTROY) {
            cancel(true)
        } else if (event == LifeEvent.SHOW) {
            changePriority(+1)
        } else if (event == LifeEvent.HIDE) {
            changePriority(-1)
        }
    }
}
override fun onCreate(savedInstanceState: Bundle?) {
    TestTask().host(this).execute("hello")
}

需要在BaseActivity中通知事件:

abstract class BaseActivity : Activity() {
    override fun onDestroy() {
        super.onDestroy()
        LifecycleManager.notify(this, LifeEvent.DESTROY)
    }

    override fun onPause() {
        super.onPause()
        LifecycleManager.notify(this, LifeEvent.HIDE)
    }

    override fun onResume() {
        super.onResume()
        LifecycleManager.notify(this, LifeEvent.SHOW)
    }
}

調(diào)用notify()方法時(shí),會(huì)根據(jù)Activity索引到對(duì)應(yīng)觀察者列表,然后遍歷列表,回調(diào)觀察者onEvent()方法。
其中,當(dāng)通知的事件為DESTROY時(shí),UITask執(zhí)行cancel()方法,從而取消任務(wù)。

3.3 動(dòng)態(tài)調(diào)整優(yōu)先級(jí)

上一節(jié),我們看到UITask除了關(guān)注DESTROY事件,還關(guān)注 Activity/Fragment 的HIDESHOW,
并根據(jù)可見(jiàn)狀態(tài)調(diào)整優(yōu)先級(jí)。

調(diào)整優(yōu)先級(jí)有什么用呢? 下面先看兩張圖感受一下。
為了凸顯效果,我們把加載任務(wù)的并發(fā)量控制為1(串行)。

第一張是不會(huì)自動(dòng)調(diào)整優(yōu)先級(jí)的,完全的先進(jìn)先出:

不改變優(yōu)先級(jí)

可以看到,切換到第二個(gè)頁(yè)面,由于上一頁(yè)的任務(wù)還沒(méi)執(zhí)行完,
所以要一直等到上一頁(yè)的任務(wù)都完成了才輪到第二個(gè)頁(yè)面加載。
很顯然這樣體驗(yàn)不太好。

接下來(lái)我們看下動(dòng)態(tài)調(diào)整優(yōu)先級(jí)是什么效果:

動(dòng)態(tài)調(diào)整優(yōu)先級(jí)

切換到第二個(gè)頁(yè)面之后,第一個(gè)頁(yè)面的任務(wù)的“調(diào)度優(yōu)先級(jí)”被降低了,所以會(huì)優(yōu)先加載第二個(gè)頁(yè)面的圖片;
再次切換回第一個(gè)頁(yè)面,第二個(gè)頁(yè)面的優(yōu)先級(jí)被降低,第一個(gè)頁(yè)面的優(yōu)先級(jí)恢復(fù),所以優(yōu)先加載第一個(gè)頁(yè)面的圖片。

那可否進(jìn)入第二個(gè)頁(yè)面的時(shí)暫停第一個(gè)頁(yè)面的任務(wù)?
暫停的方案不太友好,比方說(shuō)用戶在第二個(gè)頁(yè)面停留很久,第二個(gè)頁(yè)面的任務(wù)都完成了,然后切換回第一個(gè)頁(yè)面,發(fā)現(xiàn)只有部分圖片(其他被暫停了)。
而如果只是調(diào)整優(yōu)先級(jí),則第二個(gè)頁(yè)面的任務(wù)都執(zhí)行完之后,會(huì)接著執(zhí)行第一個(gè)頁(yè)面的任務(wù),返回第一個(gè)頁(yè)面時(shí)就能夠看到所有圖片了。
這就好比趕車,讓其他人給插個(gè)隊(duì),沒(méi)有問(wèn)題,但是不能不給別人排隊(duì)了吧。

3.4 鏈?zhǔn)秸{(diào)用

UITask的用法和AsyncTask大同小異,回調(diào)方法和參數(shù)泛型都是一樣的,所以就不多作介紹了。
如今很多開(kāi)源庫(kù)都提供了鏈?zhǔn)紸PI,使用起來(lái)確實(shí)靈活方便,視覺(jué)上也比較連貫。
喜歡冰糖葫蘆一樣的鏈?zhǔn)秸{(diào)用?
項(xiàng)目中提供了一個(gè)ChainTask類,拓展了UITask,提供鏈?zhǔn)秸{(diào)用的API。

override fun onCreate(savedInstanceState: Bundle?) {
    val task = ChainTask<Double, Int, String>()
    task.tag("ChainTest")
        .preExecute { result_tv.text = "running" }
        .background { params ->
            for (i in 0..100 step 2) {
                // do something
                task.publishProgress(i)
            }
            "result is:" + (params[0] * 100)
        }
        .progressUpdate { values ->
            val progress = values[0]
            progress_bar.progress = progress
            progress_tv.text = "$progress%"
        }
        .postExecute { result_tv.text = it }
        .cancel { showTips("ChainTask cancel") }
        .priority(Priority.IMMEDIATE)
        .host(this)
        .execute(3.14)
}

四、總結(jié)

最后,可能會(huì)這樣的疑問(wèn):
既然已經(jīng)有 RxJava 這樣好用的開(kāi)源庫(kù)來(lái)實(shí)現(xiàn)異步了, 為什么還要寫這個(gè)項(xiàng)目呢?
首先,RxJava 不僅僅是異步而已:“ReactiveX是一個(gè)通過(guò)使用可觀察序列來(lái)編寫異步和基于事件的程序的庫(kù)?!?br> “可觀察序列 - 事件 - 異步”加起來(lái)才使得 RxJava 如此富有魅力。
有所得,必有所付出,為了實(shí)現(xiàn)這些豐富的特性,代碼量也是比較可觀的(當(dāng)前版本jar包約2.2M)。

AsyncTask則比較簡(jiǎn)單,除去注釋只有三百多行代碼;
功能也比較純粹:執(zhí)行異步任務(wù),在任務(wù)執(zhí)行的不同階段,回調(diào)相應(yīng)的方法。
Task參考了AsyncTask,功能類似,只是做了一些完善;
jar包大小45K,也算是比較輕量的。

這個(gè)年頭,apk動(dòng)輒幾十M甚至上百M(fèi),2.2M的庫(kù)并非不可接受。
但是也有一些場(chǎng)景,比方說(shuō)給第三方寫SDK的時(shí)候,對(duì)包大小和依賴比較敏感,而且也不需要這么大而全的特性,這時(shí)一些輕量級(jí)的方案就比較合適了。
而且,除了包大小之外,Task所實(shí)現(xiàn)的功能和RxJava也不盡相同。

如果說(shuō)AsyncTask是自行車,RxJava是汽車,則Task是摩托車。
各有各的用途,各有各的靈魂。

項(xiàng)目地址:
https://github.com/No89757/Task

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 簡(jiǎn)介 1. 線程分類 主線程(UI線程) : 處理和界面相關(guān)的事情. 子線程 : 處理耗時(shí)操作. Android中...
    王世軍Steven閱讀 974評(píng)論 0 2
  • 第5章 多線程編程 5.1 線程基礎(chǔ) 5.1.1 如何創(chuàng)建線程 在java要?jiǎng)?chuàng)建線程,一般有==兩種方式==:1)...
    AndroidMaster閱讀 1,895評(píng)論 0 11
  • 不同形式的線程雖然都是線程,但是它們?nèi)匀痪哂胁煌奶匦院蛯?shí)用場(chǎng)景: AsyncTask封裝了線程池和Handler...
    胡二囧閱讀 1,204評(píng)論 0 4
  • 前段時(shí)間遇到這樣一個(gè)問(wèn)題,有人問(wèn)微信朋友圈的上傳圖片的功能怎么做才能讓用戶的等待時(shí)間較短,比如說(shuō)一下上傳9張圖片,...
    加油碼農(nóng)閱讀 1,278評(píng)論 0 2
  • 其實(shí)所謂理財(cái)?shù)脑掝},就是賺錢、花錢、攢錢的問(wèn)題。 人這一輩子都跟錢脫不了干系,網(wǎng)上有一本書,叫《錢定江山——世界是...
    曉霞出東方閱讀 1,454評(píng)論 0 0

友情鏈接更多精彩內(nèi)容