Android 异步更新UI-线程池-Future-Handler实例分析
前言: 我们在开发Android过程中,在处理耗时任务和UI交互的过程中,都会将耗时任务放到子线程处理并刷新. 下面我提出的两个问题,相信大多数开发者都会碰到:
1. 数据经常需要读取更新,并且比较耗时,需要分步刷新UI.
2. UI界面切换后,如何停止掉子线程里面正在读取的数据而不会将旧数据刷新到新UI界面上.
目前网上大部分教程主要只是简单的Handler.postDelayed(), Thread + Handler, Async等方式, 只适用于简单的一次性刷新. 或许有人会说我可以采用不断地new Thread的方式来创建子线程刷新,然后传message回去更新UI,但是这样的不断地new会有性能消耗大和数据同步的问题.
关于以上这两个问题的解决, 我在这里想要介绍的是使用线程池+Future+handler的配合使用.
为了更好理解异步更新UI的原理,这里先介绍下Thread + Handler + Looper + Message模型, 如下图1所示:

图1 Thread + Handler + Looper + Message模型
图1清楚给我们展示出了消息队列在Looper里面的处理方式,这里有两个重要的要点: (1)子线程也可以通过Looper管理Message, 但是需要加Looper.prepare() 和Looper.loop()才能实现消息循环; (2)UI主线程无需实现prepare()和loop()因为主线程中已经默认实现了.
现在开始介绍线程池+Future+handler的一些基本概念和使用demo实例.
线程池
是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源.如果在程序中反复创建和销毁线程,将会对程序的反应速度造成严重影响,有时甚至会Crash掉程序.这里我们使用简单的ExecutorService类.
Future
Future模式可以这样来描述:我有一个任务,提交给了Future,Future替我完成这个任务。期间我自己可以去做任何想做的事情。一段时间之后,我就便可以从Future那儿取出结果。就相当于下了一张订货单,一段时间后可以拿着提订单来提货,这期间可以干别的任何事情。其中Future 接口就是订货单,真正处理订单的是Executor类,它根据Future接口的要求来生产产品。
Handler
连接子线程和主线程的桥梁,可以通过sendmessage或者post的方式跟主线程通信.
说了这么多,如果还有对基本概念不太熟悉的童鞋可以先移步到最后的参考文章里看下再回来看本文章,此处直接上代码干货.
方法一:利用sendMessage实现
public class MyActivity extends Activity {
private final String TAG = DemoExecutorService;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initFindView();
setListener();
}
private TextView mTitle;
private Button mBtn;
private void initFindView() {
mTitle = (TextView) findViewById(R.id.title);
mBtn = (Button) findViewById(R.id.btn);
}
private void setListener() {
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
TestCallBack testCallBack = new TestCallBack();
testCallBack.loadToHandler();
}
});
}
private class TestCallBack {
public TestCallBack() {
Log.d(TAG, #####TestCallBack===Constructor);
}
public void loadToHandler() {
Handler myHandler = new Handler(getMainLooper()) {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, #######receive the msg?? what = + msg.what);
int num = msg.what;
switch(num){
case :
mTitle.setText(######Yeah, we receive the first msg );
break;
case :
mTitle.setText(######Yeah, we receive the second msg );
break;
default:
break;
}
}
};
testExecutorHandler(myHandler);
}
}
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
Future<!--?--> mTask;
boolean mSendMsg;
public void testExecutorHandler(final Handler handler) {
Log.d(TAG, ########testExecutorHandler, mTask = + mTask);
if(mTask != null) {
// 通过取消mTask,来实现之前排队但未运行的submit的task的目的,通过标志位不让其发msg给UI主线程更新.
mTask.cancel(false);
Log.d(TAG, ########mTask.isCannel? === + mTask.isCancelled());
mSendMsg = false;
}
Runnable r = new Runnable() {
@Override
public void run() {
mSendMsg = true;
try {
Log.d(TAG, ###step ####start to sleep 6s.);
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg;
Log.d(TAG, ####### mSendMsg === + mSendMsg);
if(mSendMsg) {
msg = handler.obtainMessage();
msg.what = ;
handler.sendMessage(msg);
} else {
return ;
}
Log.d(TAG, ####step ####start to sleep 4s.);
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 若没有重新obtainMessage的话,就会出现以下错误,因为已经被回收, 所以报错. 需要重新 obtainMessage().
// E/AndroidRuntime( 1606): java.lang.IllegalStateException: The specified message queue synchronization barrier token has not been posted or has already been removed.
Log.d(TAG, ####### mSendMsg === + mSendMsg);
if(mSendMsg) {
msg = handler.obtainMessage();
msg.what = ;
handler.sendMessage(msg);
} else {
return ;
}
}
};
// mExecutor.submit(r); // 若只是这样子就不会进入Future任务里面,那样每一个submit提交的都会被依次执行.
mTask = mExecutor.submit(r);
}
}
结果和打印如下图2和3所示:


图4用多次点击来模拟UI不停调用刷新的情况,后台的执行任务会只是保留当前task和最后一次提交的task,中间的task都被Futurecancel掉了.而且当前旧的task也会受到标志位的控制,不会将更新内容sendMessage出来,从而不会影响最后一次UI的刷新.

方法二:利用runnable实现更新:
由于部分方法跟上面一样,所以要看完整代码可以在下面下载,以下只是核心代码.
public void loadToRunnable() {
Runnable runable = new Runnable(){
@Override
public void run() {
Log.d(TAG, #########Ok.. let update callback1...);
mTitle.setText(####Yeah, Refresh in runnable, callback1);
}
};
Runnable runable2 = new Runnable() {
@Override
public void run() {
Log.d(TAG, ######Ok, let update callback2, ...);
mTitle.setText(####Callback2 update success!!!);
}
};
testExecutorRunnable(runable, runable2, getMainLooper());
}
}
public void testExecutorRunnable(final Runnable callback,
final Runnable callback2, final Looper looper) {
Log.d(TAG, #####testExecutor##mTask..#### = + mTask);
if(mTask != null) {www.2cto.com
mTask.cancel(false); // true 表示强制取消,会发生异常; false表示等待任务结束后取消.
mSendMsg = false;
} Runnable r = new Runnable(){
@Override
public void run() {
mSendMsg = true;
try {
Log.d(TAG, #####Step ####Async run after submit...###sleep 6s);
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(callback != null && mSendMsg){
// Handler handler = new Handler(); // Not use it, should use the Looper.
Handler handler = new Handler(looper);
handler.post(callback);
} try {
Log.d(TAG, #####Step ####Async run after submit...###sleep 4s);
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(callback2 != null && mSendMsg) {
Handler handler = new Handler(looper);
handler.post(callback2);
}
}
}; mTask = mExecutor.submit(r);
}
Android 异步更新UI-线程池-Future-Handler实例分析的更多相关文章
- Android异步更新UI的四种方式
Android异步更新UI的四种方式 2015-09-06 09:23 segmentfault 字号:T | T 大家都知道由于性能要求,android要求只能在UI线程中更新UI,要想在其他线程中 ...
- [Android学习笔记]子线程更新UI线程方法之Handler
关于此笔记 不讨论: 1.不讨论Handler实现细节 2.不讨论android线程派发细节 讨论: 子线程如何简单的使用Handler更新UI 问题: android开发时,如何在子线程更新UI? ...
- Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面
Android应用的开发过程中需要把繁重的任务(IO,网络连接等)放到其他线程中异步执行,达到不阻塞UI的效果. 下面将由浅入深介绍Android进行异步处理的实现方法和系统底层的实现原理. 本文介绍 ...
- Android异步处理系列文章四篇之二 使用AsyncTask异步更新UI界面
Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+Loope ...
- Android异步处理二:使用AsyncTask异步更新UI界面
在<Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面>中,我们使用Thread+Handler的方式实现了异步更新UI界面,这一篇中,我们介绍一种更为简 ...
- handler机制和异步更新UI页面
Android 提供了Handler和Looper来满足线程之间的通行,Handler是先进先出原则,Looper类用来管理特定线程内对象之间的消息互换,也可以使用Runnable来完成页面异步更新 ...
- Android 通过广播来异步更新UI
之前的项目里要做一个异步更新UI的功能,可是结果出现了ANR,所以想写个demo来測试究竟是哪个地方出现了问题,结果发现原来的思路是没有问题,郁闷~~ 如今这个demo 就是模拟项目里面 的步骤 1. ...
- 使用AsyncTask异步更新UI界面及原理分析
概述: AsyncTask是在Android SDK 1.5之后推出的一个方便编写后台线程与UI线程交互的辅助类.AsyncTask的内部实现是一个线程池,所有提交的异步任务都会在这个线程池中的工作线 ...
- OkHttp3几个简单的例子和在子线程更新UI线程的方法
okHttp用于android的http请求.据说很厉害,我们来一起尝尝鲜.但是使用okHttp也会有一些小坑,后面会讲到如何掉进坑里并爬出来. 首先需要了解一点,这里说的UI线程和主线程是一回事儿. ...
随机推荐
- 华为PUSH SDK 接入方法
本文参考了华为推送平台官网及其Demo:http://developer.huawei.com/cn/consumer/wiki/index.php?title=%E6%8E%A5%E5%85%A5% ...
- BZOJ2716: [Violet 3]天使玩偶(KD-Tree)
Description Input Output Sample Input 100 100 81 23 27 16 52 58 44 24 25 95 34 2 96 25 8 14 97 50 97 ...
- Spider_reg
# 解析 数据的分类 结构化数据 有固定的格式,如 :HTML.XML.JSON 非结构化数据 图片.音频.视频,这类数据一般都存储为二进制 # 正则表达式 re 使用流程 创建编译对象:p = re ...
- 【hdu 1533】Going Home
[链接]http://acm.hdu.edu.cn/showproblem.php?pid=1533 [题意] 一个N*M地图上有相同数量的字符H和字符m,m代表一个 人,H代表一个房子.人到房子的花 ...
- 洛谷 P1679 神奇的四次方数
P1679 神奇的四次方数 题目描述 在你的帮助下,v神终于帮同学找到了最合适的大学,接下来就要通知同学了.在班级里负责联络网的是dm同学,于是v神便找到了dm同学,可dm同学正在忙于研究一道有趣的数 ...
- PHP盛宴——经常使用函数集锦
近期写了蛮多PHP,也接触到挺多经常使用的函数,大多都记了笔记,发个博客出来.共同学习.事实上感觉学习一门语言,语法逻辑是软素养.而对语言的熟悉程度仅仅能随着使用时间的增长而慢慢增长,当对一门语言的函 ...
- 7.3 GROUP BY的“新”功能
7.3 GROUP BY的"新"功能正在更新内容,请稍后
- Event Serializers官网剖析(博主推荐)
不多说,直接上干货! Flume Sources官网剖析(博主推荐) Flume Channels官网剖析(博主推荐) Flume Channel Selectors官网剖析(博主推荐) Flume ...
- React评论展示案例(包含知识点:state、props、ref、React声明周期、localStorage本地存储等)
本案例在上一篇的案例(React组件之间通过Props传值的技巧(小案例,帮助体会理解props.state.受控组件和非受控组件等))的基础上加强功能和用户体验,但是当然还有很多需要改进的地方,后期 ...
- 在CentOS7 开发与部署 asp.net core app笔记
原文:在CentOS7 开发与部署 asp.net core app笔记 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/lihongzhai/art ...