天天看點

從最簡單的圖檔加載,教你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進階架構技術體系大綱和視訊資料,以及五年積累整理的所有面試資源筆記。加群請備注csdn領取進階大綱。