[置頂] [Android] 任意時刻從子線程切換到主線程的實現
來源:程序員人生 發布時間:2014-12-15 08:58:07 閱讀次數:3392次
========================================================
作者:qiujuer
博客:blog.csdn.net/qiujuer
網站:www.qiujuer.net
開源庫:Genius-Android
轉載請注明出處:http://blog.csdn.net/qiujuer/article/details/41599383
========================================================
引入
在Android開發中常常會遇到網絡要求,數據庫數據準備等1些耗時的操作;而這些操作是不允許在主線程中進行的。由于這樣會梗塞主線程致使程序出現未響應情況。
所以只能另起1個子線程進行這些耗時的操作,完成后再顯示到界面。盡人皆知,界面等控件操作只能在主線程中完成;所以不可避免的需要從子線程切換到主線程。
方法
對這樣的情況在Android 中比較常見的是使用AsynTask類或 Handler來進行線程切換;而其中AsynTask是官方封裝的類,較為簡單,效力也比較可以,但是其實不合適所有的情況,最少我使用了1兩次后就再也沒有使用了。使用 Handler可以說是最萬能的方式,其原理是消息循環,在主線程中建立Handler
變量時,就會啟動Handler消息循環,1個個的處理消息隊列中的任務。但是其也有辣手的時候;其辣手的地方就是麻煩。
每次都需要去建立1個 Handler 類,然后使用voidhandleMessage(Messagemsg) 方法把消息取出來進行界面操作,而其中還要遇到參數的傳遞等問題,說起來真的是挺麻煩的。
想法
既然有著這么多的問題,但是又有其的優勢,我們何不自行封裝1次呢?
這里我梳理1下思路:
- 還是使用 Handler進行線程切換
- 在子線程中能通過簡單的調用就切換到主線程進行工作
- 在子線程切換到主線程時,子線程進入阻塞直到主線程履行完成(知道為何有這樣的需求么?)
- 1定要保證其效力
- 主線程的履行要有時間限制,不能履行太長時間致使主線程阻塞
我能想到的就是這些;觀眾老爺們咋樣?可否還有需求?
說干就干,梳理1下實現方法
- 使用Handler 實現,既然這樣那末主方法固然就是采取繼承Handler 來實現
- 而要簡單同時又要能隨時進入方法 那末對外采取靜態方法是個不錯的選擇
- 而要保證效力的話,那就不能讓Handler 的消息隊列過于太多,但是又要滿足能隨時調用,那末采取外部 Queue
- 更具情況有阻塞與不阻塞子線程兩種情況,那末采取兩個 Queue吧,分開來好1點
- 要保證不能長時間在主線程履行那末對隊列的履行1定要有時間限制加1個時間變量吧
- 固然最后斟酌了1下,既然要簡單那末傳入參數采取Runnable 是很爽的
萬事俱備,只欠東風了;好了進入下1環節。
CodeTime
首先我們建立1個ToolKit類:
public class ToolKit {
/**
* Asynchronously
*
* @param runnable Runnable Interface
*/
public static void runOnMainThreadAsync(Runnable runnable) {
}
/**
* Synchronously
*
* @param runnable Runnable Interface
*/
public static void runOnMainThreadSync(Runnable runnable) {
}
}
兩個對外的方法簡單來講就是這樣了;但是其功能實現就需要使用繼承Handler了。
建立類HandlerPoster,繼承自Handler:
final class HandlerPoster extends Handler {
private final int ASYNC = 0x1;
private final int SYNC = 0x2;
private final Queue<Runnable> asyncPool;
private final Queue<SyncPost> syncPool;
private final int maxMillisInsideHandleMessage;
private boolean asyncActive;
private boolean syncActive;
HandlerPoster(Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
asyncPool = new LinkedList<>();
syncPool = new LinkedList<>();
}
void dispose() {
this.removeCallbacksAndMessages(null);
this.asyncPool.clear();
this.syncPool.clear();
}
void async(Runnable runnable) {
synchronized (asyncPool) {
asyncPool.offer(runnable);
if (!asyncActive) {
asyncActive = true;
if (!sendMessage(obtainMessage(ASYNC))) {
throw new GeniusException("Could not send handler message");
}
}
}
}
void sync(SyncPost post) {
synchronized (syncPool) {
syncPool.offer(post);
if (!syncActive) {
syncActive = true;
if (!sendMessage(obtainMessage(SYNC))) {
throw new GeniusException("Could not send handler message");
}
}
}
}
@Override
public void handleMessage(Message msg) {
if (msg.what == ASYNC) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
Runnable runnable = asyncPool.poll();
if (runnable == null) {
synchronized (asyncPool) {
// Check again, this time in synchronized
runnable = asyncPool.poll();
if (runnable == null) {
asyncActive = false;
return;
}
}
}
runnable.run();
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage(ASYNC))) {
throw new GeniusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
asyncActive = rescheduled;
}
} else if (msg.what == SYNC) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
SyncPost post = syncPool.poll();
if (post == null) {
synchronized (syncPool) {
// Check again, this time in synchronized
post = syncPool.poll();
if (post == null) {
syncActive = false;
return;
}
}
}
post.run();
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage(SYNC))) {
throw new GeniusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
syncActive = rescheduled;
}
} else super.handleMessage(msg);
}
}
下面來講說這個我花了很大時間弄出來的類。
類的變量部份:
兩個標識,兩個隊列,兩個履行狀態,1個時間限制;很好理解吧?標識為了區分分別是處理那個隊列使用;隊列固然是裝著任務了;履行狀態是為了不重復發送消息致使消息隊列過量;時間限制這個最好理解了。
下面來講說方法部份:
構造函數HandlerPoster(Looper_looper,int_maxMillisInsideHandleMessage):
傳入兩個參數,分別是 Looper,用于初始化到主線程,后面的是時間限制;然后初始化了兩個隊列。
燒毀函數void_dispose():首先去除掉沒有處理的消息,然后清空隊列。
添加異步履行方法void_async(Runnable_runnable):
void async(Runnable runnable) {
synchronized (asyncPool) {
asyncPool.offer(runnable);
if (!asyncActive) {
asyncActive = true;
if (!sendMessage(obtainMessage(ASYNC))) {
throw new GeniusException("Could not send handler message");
}
}
}
}
可以看見進入方法后第1件事兒就是進入同步狀態,然后調用asyncPool.offer(runnable);把任務寫入到隊列。
以后判斷當前是不是處于異步任務履行中,如果不是:立刻改變狀態,然后發送1個消息給當前Handler,固然不要忘記了傳入標識。
固然為了效力其消息的構造也是通過obtainMessage(ASYNC)方法來完成,為的就是不過量建立新的Message,盡可能使用當前隊列中空閑的消息。
添加同步履行方法void_sync(SyncPost_post):
void sync(SyncPost post) {
synchronized (syncPool) {
syncPool.offer(post);
if (!syncActive) {
syncActive = true;
if (!sendMessage(obtainMessage(SYNC))) {
throw new GeniusException("Could not send handler message");
}
}
}
}
可以看到,這里傳入的其實不是Runnable
而是SyncPost這是為了同步而對Runnable進行了1次封裝后的類;后面介紹。
一樣是進入同步,添加,判斷,發送消息。
任務履行者@Override_void_handleMessage(Message_msg):
這里是復寫的Handler的消息處理方法,鐺鐺前Handler消息隊列中有消息的時候將會依照順序1個個的調用該方法。
分段來看:
if (msg.what == ASYNC) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
Runnable runnable = asyncPool.poll();
if (runnable == null) {
synchronized (asyncPool) {
// Check again, this time in synchronized
runnable = asyncPool.poll();
if (runnable == null) {
asyncActive = false;
return;
}
}
}
runnable.run();
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage(ASYNC))) {
throw new GeniusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
asyncActive = rescheduled;
}
}
進入后首先判斷是不是是進行異步處理的消息,如果是那末進入該位置。
進入后我們進行了try_finally有1個變量long_started用于標識開始時間。
當履行1個任務后就判斷1次如果超過了每次占用主線程的時間限制,那末不管隊列中的任務是不是履行完成都退出,同時發起1個新的消息到Handler循環隊列。
在while部份,我們從隊列取出1個任務,采取Poll方法;判斷是不是為空,如果為空進入隊列同步塊;然后再取1次,再次判斷。
如果恰巧在進入同步隊列之前有新的任務來了,那末第2次取到確當然就不是 NULL也就會繼續履行下去。反之,如果還是為空;那末重置當前隊列的狀態為false同時跳出循環。
下面來看第2部份:
else if (msg.what == SYNC) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
SyncPost post = syncPool.poll();
if (post == null) {
synchronized (syncPool) {
// Check again, this time in synchronized
post = syncPool.poll();
if (post == null) {
syncActive = false;
return;
}
}
}
post.run();
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage(SYNC))) {
throw new GeniusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
syncActive = rescheduled;
}
} else super.handleMessage(msg);
首先還是判斷,如果是同步任務消息就進入,如果還是否是 那末只有調用super.handleMessage(msg);了。
從上面的處理部份可以看出來其處理的進程與第1部份可以說是完全1樣的。
只不過是從不同隊列取出不同的類SyncPost,然后判斷履行,和發送不同標識的消息;可以說如果懂了第1部份,這部份是毫無營養的。
這里就有問題了,既然方法操作流程1樣,那末同步與異步是在哪里進行辨別的?
這里就要看看SyncPost了:
final class SyncPost {
boolean end = false;
Runnable runnable;
SyncPost(Runnable runnable) {
this.runnable = runnable;
}
public void run() {
synchronized (this) {
runnable.run();
end = true;
try {
this.notifyAll();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void waitRun() {
if (!end) {
synchronized (this) {
if (!end) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
首先看看SyncPost的構造函數:
是否是傳入1個Runnable接口?所以說是對Runnable
的簡單封裝。
可以看見其public_void_run()方法:
在該方法中我們進入了同步塊,然后調用Runnable接口的run方法。同時在履行完成后將其中的1個狀態變量進行了改變boolean_end=true;
然后調用this.notifyAll();通知等待的部份可以繼續了,固然有這樣的情況;假設在進入該同步塊的時候子線程還未履行到this.wait();部份呢?所以我們為此準備了end和try。
然后看看public_void_waitRun()方法:
在這個中,我們首先判斷狀態,如果狀態已變了,那末證明子線程履行到此處時,主線程和履行了void_run()。
所以也就不用進入同步塊進行等待了,不然那還不等死???反之就進入進行等待直到主線程調用this.notifyAll();
豪情部份
馬上進入到完成部份了,組建都完善了那末該進行最后的組裝了。
回到類classToolKit
public class ToolKit {
private static HandlerPoster mainPoster = null;
private static HandlerPoster getMainPoster() {
if (mainPoster == null) {
synchronized (ToolKit.class) {
if (mainPoster == null) {
mainPoster = new HandlerPoster(Looper.getMainLooper(), 20);
}
}
}
return mainPoster;
}
/**
* Asynchronously
* The child thread asynchronous run relative to the main thread,
* not blocking the child thread
*
* @param runnable Runnable Interface
*/
public static void runOnMainThreadAsync(Runnable runnable) {
if (Looper.myLooper() == Looper.getMainLooper()) {
runnable.run();
return;
}
getMainPoster().async(runnable);
}
/**
* Synchronously
* The child thread relative thread synchronization operation,
* blocking the child thread,
* thread for the main thread to complete
*
* @param runnable Runnable Interface
*/
public static void runOnMainThreadSync(Runnable runnable) {
if (Looper.myLooper() == Looper.getMainLooper()) {
runnable.run();
return;
}
SyncPost poster = new SyncPost(runnable);
getMainPoster().sync(poster);
poster.waitRun();
}
public static void dispose() {
if (mainPoster != null) {
mainPoster.dispose();
mainPoster = null;
}
}
}
其中就1個靜態變量HandlerPoster
然后1個初始化部份HandlerPoster_getMainPoster()這里采取同步的方式進行初始化,用于適應多線程同時調用情況;固然在初始化的時候我們傳入了
mainPoster=newHandlerPoster(Looper.getMainLooper(),20); 這里就決定了是在主線程履行的HandlerPoster,同時指定主線程單次運行時間為20毫秒。
在方法void_runOnMainThreadAsync(Runnable_runnable)中:
首先判斷調用該方法的是不是是主線程,如果是那還弄到隊列中履行干嗎?直接履行啊;如果是子線程就調用getMainPoster().async(runnable);追加到隊列中履行。
而在方法void_runOnMainThreadSync(Runnable_runnable)中:
public static void runOnMainThreadSync(Runnable runnable) {
if (Looper.myLooper() == Looper.getMainLooper()) {
runnable.run();
return;
}
SyncPost poster = new SyncPost(runnable);
getMainPoster().sync(poster);
poster.waitRun();
}
一樣是線程判斷,然落后行封裝,然后丟進隊列中等待履行,而在該方法中調用poster.waitRun();進行等待;直到主線程履行了SyncPost類的run方法。
最后固然留下了1個燒毀方法;媽媽說要學會清算不留垃圾:void_dispose()
OK,完成了
// "Runnable" 類實現其中 "run()" 方法
// "run()" 運行在主線程中,可在其中進行界面操作
// 同步進入主線程,等待主線程處理完成后繼續履行子線程
ToolKit.runOnMainThreadSync(Runnable runnable);
// 異步進入主線程,無需等待
ToolKit.runOnMainThreadAsync(Runnable runnable);
對外就是這么兩個方法,簡單便捷??;大伙試試吧;1個字爽!
代碼:
ToolKit.java
HandlerPoster.java
SyncPost.java
開源項目:
Genius-Android
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈