編輯:關於Android編程
在Android開發中常常會遇到網絡請求,數據庫數據准備等一些耗時的操作;而這些操作是不允許在主線程中進行的。因為這樣會堵塞主線程導致程序出現未響應情況。
所以只能另起一個子線程進行這些耗時的操作,完成後再顯示到界面。眾所周知,界面等控件操作只能在主線程中完成;所以不可避免的需要從子線程切換到主線程。
對於這樣的情況在Android 中比較常見的是使用AsynTask類或者 Handler來進行線程切換;而其中AsynTask是官方封裝的類,較為簡單,效率也比較可以,但是並不適合所有的情況,至少我使用了一兩次後就再也沒有使用了。使用 Handler可以說是最萬能的方式,其原理是消息循環,在主線程中建立Handler 變量時,就會啟動Handler消息循環,一個個的處理消息隊列中的任務。但是其也有棘手的時候;其棘手的地方就是麻煩。
每次都需要去建立一個 Handler 類,然後使用voidhandleMessage(Messagemsg) 方法把消息取出來進行界面操作,而其中還要遇到參數的傳遞等問題,說起來真的是挺麻煩的。
既然有著這麼多的問題,但是又有其的優勢,我們何不自行封裝一次呢?
這裡我梳理一下思路:
還是使用 Handler進行線程切換在子線程中能通過簡單的調用就切換到主線程進行工作在子線程切換到主線程時,子線程進入阻塞直到主線程執行完成(知道為什麼有這樣的需求麼?)一定要保證其效率主線程的執行要有時間限制,不能執行太長時間導致主線程阻塞我能想到的就是這些;觀眾老爺們咋樣?可否還有需求?
說干就干,梳理一下實現方法
使用Handler 實現,既然這樣那麼主方法當然就是采用繼承Handler 來實現而要簡單同時又要能隨時進入方法 那麼對外采用靜態方法是個不錯的選擇而要保證效率的話,那就不能讓Handler 的消息隊列過於太多,但是又要滿足能隨時調用,那麼采用外部 Queue更具情況有阻塞與不阻塞子線程兩種情況,那麼采用兩個 Queue吧,分開來好一點要保證不能長時間在主線程執行那麼對於隊列的執行一定要有時間限制加一個時間變量吧當然最後考慮了一下,既然要簡單那麼傳入參數采用Runnable 是很爽的
/**
* @author liuyazhuang
*
*/
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:
/**
* @author liuyazhuang
*
*/
final class HandlerPoster extends Handler {
private final int ASYNC = 0x1;
private final int SYNC = 0x2;
private final Queue asyncPool;
private final Queue 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);
}
}
下面來說說這個我花了很大時間弄出來的類。
兩個標識,兩個隊列,兩個執行狀態,一個時間限制;很好理解吧?標識為了區別分別是處理那個隊列使用;隊列當然是裝著任務了;執行狀態是為了避免重復發送消息導致消息隊列過多;時間限制這個最好理解了。
構造函數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);
}
}
}
}
可以看見進入方法後第一件事兒就是進入同步狀態,然後調用asyncPool.offer(runnable);把任務寫入到隊列。之後判斷當前是否處於異步任務執行中,如果不是:立刻改變狀態,然後發送一個消息給當前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進行了一次封裝後的類;後面介紹。同樣是進入同步,添加,判斷,發送消息。
任務執行者@Override_void_handleMessage(Message_msg):
這裡是復寫的Handler的消息處理方法,當當前Handler消息隊列中有消息的時候將會按照順序一個個的調用該方法。
分段來看:
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有一個變量long_started用於標識開始時間。當執行一個任務後就判斷一次如果超過了每次占用主線程的時間限制,那麼不管隊列中的任務是否執行完退出,同時發起一個新的消息到Handler循環隊列。在while部分,我們從隊列取出一個任務,采用Poll方法;判斷是否為空,如果為空進入隊列同步塊;然後再取一次,再次判斷。如果恰巧在進入同步隊列之前有新的任務來了,那麼第二次取到的當然就不是 NULL也就會繼續執行下去。反之,如果還是為空;那麼重置當前隊列的狀態為false同時跳出循環。
下面來看第二部分:
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);了。從上面的處理部分可以看出來其處理的過程與第一部分可以說是完全一樣的。只不過是從不同隊列取出不同的類SyncPost,然後判斷執行,以及發送不同標識的消息;可以說如果懂了第一部分,這部分是毫無營養的。
這裡就有問題了,既然方法操作流程一樣,那麼同步與異步是在哪裡進行區分的?
這裡就要看看SyncPost了:
/**
* @author liuyazhuang
*
*/
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的構造函數:
是不是傳入一個Runnable接口?所以說是對Runnable 的簡單封裝。
可以看見其public_void_run()方法:
在該方法中我們進入了同步塊,然後調用Runnable接口的run方法。同時在執行完成後將其中的一個狀態變量進行了改變boolean_end=true;然後調用this.notifyAll();通知等待的部分可以繼續了,當然有這樣的情況;假如在進入該同步塊的時候子線程還未執行到this.wait();部分呢?所以我們為此准備了end和try。
然後看看public_void_waitRun()方法:
在這個中,我們首先判斷狀態,如果狀態已經變了,那麼證明子線程執行到此處時,主線程以及執行了void_run()。所以也就不用進入同步塊進行等待了,不然那還不等死啊?反之就進入進行等待直到主線程調用this.notifyAll();
回到類ToolKit
/**
* @author liuyazhuang
*
*/
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;
}
}
}
其中就一個靜態變量HandlerPoster
然後一個初始化部分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方法。最後當然留下了一個銷毀方法;媽媽說要學會清理不留垃圾:void_dispose()
OK,完成了。
// Runnable 類實現其中 run() 方法 // run() 運行在主線程中,可在其中進行界面操作 // 同步進入主線程,等待主線程處理完成後繼續執行子線程 ToolKit.runOnMainThreadSync(Runnable runnable); // 異步進入主線程,無需等待 ToolKit.runOnMainThreadAsync(Runnable runnable);對外就是這麼兩個方法,簡單便捷啊;
Android生命周期中的onPause()和onStop()
我不知道大家有沒有這樣問題,項目做多了,就容易忽略最最基礎的知識,其實我也是在最近發現了自己也存在這樣的問題。因此打算做一些最基礎的知識的調研來重新學習和回顧這些容易被忽
Android Studio如何添加工程(project)為library(針對非gradle)
這篇文章還是針對非gradle build的工程,gradle build有一些差別。在Eclipse要引用別的工程為本工程的library很簡單,但是在Android
Android中HttpURLConnection使用詳解
認識Http協議 Android中發送http網絡請求是很常見的,要有GET請求和POST請求。一個完整的http請求需要經歷兩個過程:客戶端發送請求到服務器,然後服務
Android基礎入門教程——10.2 SmsManager(短信管理器)
本節引言: 本節帶來的是Android中的SmsManager(短息管理器),見名知意,就是用來管理手機短信的, 而該類的應用場景並不多,一般是我