android非RxJava環(huán)境下使用Handler實(shí)現(xiàn)預(yù)加載
在進(jìn)行Android客戶端界面開(kāi)發(fā)時(shí),我們常常會(huì)需要將從服務(wù)端獲取的數(shù)據(jù)展示到頁(yè)面布局上,由于數(shù)據(jù)顯示到布局的前置條件是頁(yè)面布局已初始化完成,否則會(huì)出現(xiàn)空指針異常,所以一般我們需要將網(wǎng)絡(luò)請(qǐng)求放在布局初始化完成之后。
傳統(tǒng)的頁(yè)面加載流程是:
問(wèn)題:
如果加載的UI布局比較復(fù)雜,或者初始化邏輯執(zhí)行的時(shí)間比較多,那么網(wǎng)絡(luò)請(qǐng)求開(kāi)始執(zhí)行的時(shí)間就比較晚,最終完成頁(yè)面加載的時(shí)間就比較長(zhǎng)。
如果頁(yè)面初始化和網(wǎng)絡(luò)加載能同時(shí)進(jìn)行,等兩者都執(zhí)行結(jié)束后,再在布局上展示網(wǎng)絡(luò)數(shù)據(jù),這樣我們就可以縮短整個(gè)頁(yè)面的加載時(shí)間了。
所以,我們期望的頁(yè)面加載流程是:
這個(gè)流程我們稱之為:預(yù)加載
預(yù)加載的目標(biāo)任務(wù)可以是一個(gè)網(wǎng)絡(luò)請(qǐng)求,也可以是其它一些耗時(shí)操作,例如:加載一張圖片到控件上展示
在實(shí)現(xiàn)預(yù)加載方案之前,我們需要了解一下Handler工作機(jī)制中的SyncBarrier概念,對(duì)Barrier概念了解可以看這篇文章中對(duì)“同步分割欄”的介紹, 此處我們簡(jiǎn)單理解為:
在MessageQueue中添加一個(gè)特殊的msg,將這個(gè)msg作為一個(gè)標(biāo)記,在這個(gè)標(biāo)記被移除之前,當(dāng)前MessageQueue隊(duì)列中排在它后面的其它(非async) 的message不會(huì)被handler處理。
我們可以先不理會(huì)什么是 非async 的message,若需要了解更多,這篇文章中對(duì)“同步分割欄”的介紹中也有相關(guān)介紹。
利用這個(gè)特性,我們可以:
啟動(dòng)一個(gè)HandlerThread來(lái)異步執(zhí)行網(wǎng)絡(luò)請(qǐng)求
設(shè)置一個(gè)標(biāo)記SyncBarrier,此后在message將一直在messageQueue中不被執(zhí)行
網(wǎng)絡(luò)請(qǐng)求成功后,post一個(gè)任務(wù)來(lái)執(zhí)行展示數(shù)據(jù)
布局初始化成功后,移除SyncBarrier
將展示數(shù)據(jù)的任務(wù)post到ui線程來(lái)執(zhí)行
步驟3和步驟4的先后順序可以交換
其中,在android api 22及之前,設(shè)置標(biāo)記SyncBarrier可以由
HandlerThread.getLooper().postSyncBarrier();
在android api 23以后,需要調(diào)用的方法為:
HandlerThread.getLooper().getQueue().postSyncBarrier();
同樣的,移除標(biāo)記的方法分別為:
HandlerThread.getLooper().removeSyncBarrier(token); HandlerThread.getLooper().getQueue().removeSyncBarrier(token);
不幸的是:這些方法都是@hide的,無(wú)法直接調(diào)用。
幸運(yùn)的是:我們還有反射
封裝工具類如下: PreLoader.java
import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.MessageQueue; import java.lang.reflect.Method; /** * 使用Handler方式實(shí)現(xiàn)的預(yù)加載 * @author billy.qi */ public class PreLoader { private int token; private Handler mainThreadHandler;//用于將結(jié)果處理的task放在主線程中執(zhí)行 private HandlerThread handlerThread;//提供一個(gè)異步線程來(lái)運(yùn)行預(yù)加載任務(wù) private Handler handler;//預(yù)加載使用的handler private PreLoader(final Runnable task) { mainThreadHandler = new Handler(Looper.getMainLooper()); handlerThread = new HandlerThread("pre-loader") { @Override protected void onLooperPrepared() { super.onLooperPrepared(); handler = new Handler(); handler.post(task); //設(shè)置同步分割,后面post進(jìn)來(lái)的sync為true的message將暫停執(zhí)行 Looper looper = handlerThread.getLooper(); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { postSyncBarrier(looper.getQueue()); } else { postSyncBarrier(looper); } } }; handlerThread.start(); } /** * 開(kāi)啟預(yù)加載 * 比如:開(kāi)始網(wǎng)絡(luò)請(qǐng)求(在HandlerThread中執(zhí)行) * @param task 預(yù)加載任務(wù) */ public static PreLoader preLoad(Runnable task) { return new PreLoader(task); } /** * 處理加載結(jié)果, 一般在預(yù)加載任務(wù)處理完成后調(diào)用 * 由于有handler所在的messageQueue設(shè)置了同步分割(SyncBarrier),該task * @param task 在主線程中執(zhí)行的任務(wù) */ public void performResultTask(final Runnable task) { if (handler != null) { handler.post(new Runnable() { @Override public void run() { mainThreadHandler.post(task); } }); } } /** * 可以開(kāi)始執(zhí)行預(yù)加載結(jié)果處理 */ public void readyToGetData() { Looper looper = handlerThread.getLooper(); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { removeSyncBarrier(looper.getQueue()); } else { removeSyncBarrier(looper); } } private void postSyncBarrier(Object obj) { try{ Method method = MessageQueue.class.getMethod("postSyncBarrier"); token = (int) method.invoke(obj); } catch(Exception e) { e.printStackTrace(); } } private void removeSyncBarrier(Object obj) { try{ Method method = MessageQueue.class.getMethod("removeSyncBarrier", int.class); method.invoke(obj, token); } catch(Exception e) { e.printStackTrace(); } } public void destroy() { handlerThread.quit(); handlerThread = null; handler = null; mainThreadHandler = null; } }
在activity中使用實(shí)例:
import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.widget.TextView; public class PreLoaderActivity extends AppCompatActivity { private PreLoader preLoader; private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { preLoad();//啟動(dòng)預(yù)加載 //進(jìn)行頁(yè)面布局加載及其它初始化工作 super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView)findViewById(R.id.textView); //布局初始化完成 preLoader.readyToGetData(); } private void preLoad() { //預(yù)加載的任務(wù) preLoader = PreLoader.preLoad(new Runnable() { //將得到的請(qǐng)求結(jié)果展示到布局控件上 @Override public void run() { //模擬網(wǎng)絡(luò)請(qǐng)求耗時(shí) try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } final String result = "result"; //在UI線程執(zhí)行的顯示任務(wù) preLoader.performResultTask(new Runnable() { //將得到的請(qǐng)求結(jié)果展示到布局控件上 @Override public void run() { textView.setText(result); } }); } }); } @Override protected void onDestroy() { super.onDestroy(); //在onDestroy()中進(jìn)行銷毀 preLoader.destroy(); } }
通過(guò)預(yù)加載,讓一部分異步任務(wù)提前執(zhí)行,可以用來(lái)提高整體速度。
以上都是以網(wǎng)絡(luò)請(qǐng)求作為預(yù)加載的目的,它同時(shí)還可以用來(lái)預(yù)加載圖片、預(yù)加載文件、讀取數(shù)據(jù)庫(kù)等;
除了預(yù)加載外,還可以用來(lái)作為多個(gè)任務(wù)并行,并全部執(zhí)行完之后,再執(zhí)行另一個(gè)任務(wù)對(duì)之前所有任務(wù)執(zhí)行的結(jié)果進(jìn)行處理。
總結(jié):
在多線程開(kāi)發(fā)時(shí),經(jīng)常會(huì)遇到以下情況:
任務(wù)A、任務(wù)B(甚至更多任務(wù))是任務(wù)C的充要條件,為了提高效率,我們希望AB同時(shí)執(zhí)行,當(dāng)AB都完成之后,開(kāi)始執(zhí)行任務(wù)C。
這種情況下我們可以用這種方式來(lái)解決。
最后,源碼下載地址
相關(guān)文章
Android ScrollView取消慣性滾動(dòng)的方法
下面小編就為大家?guī)?lái)一篇Android ScrollView取消慣性滾動(dòng)的方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04Android自定義View app更新動(dòng)畫(huà)詳解
這篇文章給大家分享了Android自定義View app更新動(dòng)畫(huà)的相關(guān)代碼以及知識(shí)點(diǎn)內(nèi)容,有興趣的朋友參考學(xué)習(xí)下。2018-07-07Android實(shí)現(xiàn)通訊錄效果——獲取手機(jī)號(hào)碼和姓名
這篇文章主要介紹了Android實(shí)現(xiàn)通訊錄效果——獲取手機(jī)號(hào)碼和姓名的相關(guān)資料,需要的朋友可以參考下2016-03-03Android基于API的Tabs3實(shí)現(xiàn)仿優(yōu)酷t(yī)abhost效果實(shí)例
這篇文章主要介紹了Android基于API的Tabs3實(shí)現(xiàn)仿優(yōu)酷t(yī)abhost效果,結(jié)合完整實(shí)例形式分析了Android實(shí)現(xiàn)優(yōu)酷界面效果的相關(guān)技巧,需要的朋友可以參考下2015-12-12Android ListView長(zhǎng)按彈出菜單二種實(shí)現(xiàn)方式示例
這篇文章主要介紹了Android ListView長(zhǎng)按彈出菜單的方法,大家參考實(shí)現(xiàn)2013-11-11