從最簡單的圖片載入,教你Android實現非同步!

非同步,在安卓開發中簡直是再熟悉不過了。
說到非同步,腦海中立馬浮現的就是多執行緒開發,Thread、Handler啥的一一湧上心頭…
我們知道在Android開發中不能在非UI執行緒中更新UI,但是,有的時候我們需要在程式碼中執行一些諸如訪問網路、查詢資料庫等耗時操作,為了不阻塞UI執行緒,我們時常會開啟一個新的執行緒(工作執行緒)來執行這些耗時操作,然後我們可能需要將查詢到的資料渲染到UI元件上,那麼這個時候我們就需要考慮非同步更新UI的問題了。
今天我們從一個簡單的業務需求,給大家介紹幾種實現非同步的方式,最後兩個簡直爽到不行。
業務是這樣的:需要根據檔案地址,載入本地圖片,最後在ImageView上顯示。當然了,從檔案中載入圖片,是一個耗時操作,必須在子執行緒中執行,ImageView顯示圖片呢,又屬於UI操作,需要回到主執行緒。接下來列舉幾種實現方式:
Thread+Handler
使用Thread+Handler是最傳統的實現非同步方式了,看下程式碼:
new Thread(new Runnable() { @Override public void run() { Bitmap bitmap = getBitmapFromFile(PATH); handler.post(new Runnable() { @Override public void run() { imageView.setImageBitmap(bitmap); } }); } }).start();
如果熟悉Lambda表示式的話,也可以這樣寫:
new Thread(() -> { Bitmap bitmap = getBitmapFromFile(PATH); handler.post(() -> imageView.setImageBitmap(bitmap)); }).start();
這樣看來程式碼乾淨了許多。
除了實現Runnable,還可以繼承Thread,實現run方法來做到開啟子執行緒。但由於Java的單繼承多實現,所以還是使用實現Runnable方式更實用一些。handler的post方法可以將訊息傳送回主執行緒,以實現執行緒間切換。
這種方式在需要的地方new一個物件,使得程式碼繁亂,不易管理,對系統資源也不便管理。
AsyncTask
AsyncTask提供了方便的介面實現工作執行緒和主執行緒的通訊。先貼程式碼:
class BitmapAsyncTask extends AsyncTask<String, Integer, Bitmap> { @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); } @Override protected Bitmap doInBackground(String... strings) { // 在doInBackground方法中執行耗時操作 Bitmap bitmap = getBitmapFromFile(strings[0]); return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); // 在onPostExecute方法中進行ui操作 imageView.setImageBitmap(bitmap); } } new BitmapAsyncTask().execute(PATH);
AsyncTask就是一個封裝過的後臺任務類,顧名思義就是非同步任務。AsyncTask定義了三種泛型型別 Params,Progress和Result。
doInBackground(Params…) 後臺執行,比較耗時的操作都可以放在這裡。注意這裡不能直接操作UI。此方法在後臺執行緒執行,完成任務的主要工作,通常需要較長的時間。在執行過程中可以呼叫publicProgress(Progress…)來更新任務的進度。
onPostExecute(Result) 相當於Handler 處理UI的方式,在這裡面可以使用在doInBackground 得到的結果處理操作UI。 此方法在主執行緒執行,任務執行的結果作為此方法的引數返回。
這種方式使用了執行緒池+Handler實現,較好得管理分配資源,還可以拿到進度回撥,有較高的拓展性。但需要建立新類,程式碼也會隨之增加,對於簡單的非同步操作,這種方式有些繁瑣。
RxJava
主要還是用到了RxJava的Scheduler(排程器)來實現執行緒切換,看下程式碼:
Observable observable = Observable.create(new Observable.OnSubscribe<Bitmap>() { @Override public void call(Subscriber<? super Bitmap> subscriber) { Bitmap bitmap = getBitmapFromFile(PATH); subscriber.onNext(bitmap); } }); observable.subscribeOn(Schedulers.io()) // 指定 subscribe() 發生在 IO 執行緒 .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回調發生在主執行緒 .subscribe(new Subscriber<Bitmap>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Bitmap bitmap) { imageView.setImageBitmap(bitmap); } });
使用Observable.create建立Observable,在call方法中進行耗時操作,執行完成後傳送訊息,在觀察者中的onNext中處理。
使用subscribeOn和observeOn進行執行緒切換。
使用RxJava的好處是很輕鬆得實現執行緒切換,還可以指定執行緒,有異常捕獲機制。但對於不熟悉RxJava的朋友來說會有些…
Kotlin協程
最後要安利一個非常酷炫的方式,那就是Kotlin協程。
越來越多的公司和專案開始使用Kotlin編碼,畢竟Kotlin得到了谷歌爸爸的支援,而且Kotlin的優秀語言特性,使得它受到開發者的廣泛歡迎。
今天介紹Kotlin的一個概念,叫做協程。協程是由程式直接實現的,是一種 輕量級執行緒 ,kotlin也為此提供了標準庫和額外的實驗庫。標準庫為kotlin.coroutines.experimental(寫作時使用kotlin-1.20版本),可見仍然還是一個實驗性功能。
看下程式碼
先定義一個後臺CoroutineContext,協程上下文,很容易理解,就是執行環境。
val Background = newFixedThreadPoolContext(2, "bg") mWriteJob = launch(Background) { var bitmap = getBitmapFromFile(PATH); launch(UI){ imageView.setImageBitmap(bitmap) } }
最後會返回一個Job物件,可以呼叫方法將其任務停止:
if (mWriteJob != null && mWriteJob!!.isActive) { mWriteJob!!.cancel() }
不由得想感嘆一下,使用協程做輕量的非同步操作,簡直爽到不行。
但畢竟協程可能還是瞭解不多,不免會有一些坑的出現,但多去了解和使用,想必也是很酷的。
小結
從個人感覺來說,我比較推薦使用RxJava和協程來實現,處理周密的話,輕鬆避免資源浪費和記憶體洩漏。
Android中的非同步操作,實現方式有好多種,各有利弊,就需要我們針對具體業務需求來選擇合適的方式,使得功能完成的前提下,優化效能,優化程式碼。
給看到最後的朋友們發一波福利;
現在加Android開發群;701740775,可免費領取一份最新Android高階架構技術體系大綱和視訊資料,以及五年積累整理的所有面試資源筆記。加群請備註簡書領取高階大綱。