[置顶] [Android] 任意时刻从子线程切换到主线程的实现
来源:程序员人生 发布时间:2014-12-15 08:58:07 阅读次数:3235次
========================================================
作者: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
生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠