前言: 我们在开发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实现

  1. public class MyActivity extends Activity {
  2.  
  3. private final String TAG = DemoExecutorService;
  4.  
  5. @Override
  6. public void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.main);
  9. initFindView();
  10. setListener();
  11. }
  12.  
  13. private TextView mTitle;
  14. private Button mBtn;
  15. private void initFindView() {
  16. mTitle = (TextView) findViewById(R.id.title);
  17. mBtn = (Button) findViewById(R.id.btn);
  18. }
  19.  
  20. private void setListener() {
  21. mBtn.setOnClickListener(new View.OnClickListener() {
  22. @Override
  23. public void onClick(View view) {
  24. TestCallBack testCallBack = new TestCallBack();
  25. testCallBack.loadToHandler();
  26. }
  27. });
  28. }
  29.  
  30. private class TestCallBack {
  31. public TestCallBack() {
  32. Log.d(TAG, #####TestCallBack===Constructor);
  33. }
  34.  
  35. public void loadToHandler() {
  36. Handler myHandler = new Handler(getMainLooper()) {
  37. @Override
  38. public void handleMessage(Message msg) {
  39. Log.d(TAG, #######receive the msg?? what = + msg.what);
  40. int num = msg.what;
  41. switch(num){
  42. case :
  43. mTitle.setText(######Yeah, we receive the first msg );
  44. break;
  45. case :
  46. mTitle.setText(######Yeah, we receive the second msg );
  47. break;
  48. default:
  49. break;
  50. }
  51. }
  52. };
  53. testExecutorHandler(myHandler);
  54. }
  55. }
  56.  
  57. private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
  58. Future<!--?--> mTask;
  59. boolean mSendMsg;
  60.  
  61. public void testExecutorHandler(final Handler handler) {
  62. Log.d(TAG, ########testExecutorHandler, mTask = + mTask);
  63. if(mTask != null) {
  64. // 通过取消mTask,来实现之前排队但未运行的submit的task的目的,通过标志位不让其发msg给UI主线程更新.
  65. mTask.cancel(false);
  66. Log.d(TAG, ########mTask.isCannel? === + mTask.isCancelled());
  67. mSendMsg = false;
  68. }
  69. Runnable r = new Runnable() {
  70. @Override
  71. public void run() {
  72. mSendMsg = true;
  73. try {
  74. Log.d(TAG, ###step ####start to sleep 6s.);
  75. Thread.sleep();
  76. } catch (InterruptedException e) {
  77. e.printStackTrace();
  78. }
  79. Message msg;
  80. Log.d(TAG, ####### mSendMsg === + mSendMsg);
  81. if(mSendMsg) {
  82. msg = handler.obtainMessage();
  83. msg.what = ;
  84. handler.sendMessage(msg);
  85. } else {
  86. return ;
  87. }
  88. Log.d(TAG, ####step ####start to sleep 4s.);
  89. try {
  90. Thread.sleep();
  91. } catch (InterruptedException e) {
  92. e.printStackTrace();
  93. }
  94. // 若没有重新obtainMessage的话,就会出现以下错误,因为已经被回收, 所以报错. 需要重新 obtainMessage().
  95. // E/AndroidRuntime( 1606): java.lang.IllegalStateException: The specified message queue synchronization barrier token has not been posted or has already been removed.
  96. Log.d(TAG, ####### mSendMsg === + mSendMsg);
  97. if(mSendMsg) {
  98. msg = handler.obtainMessage();
  99. msg.what = ;
  100. handler.sendMessage(msg);
  101. } else {
  102. return ;
  103. }
  104. }
  105. };
  106. // mExecutor.submit(r); // 若只是这样子就不会进入Future任务里面,那样每一个submit提交的都会被依次执行.
  107. mTask = mExecutor.submit(r);
  108. }
  109.  
  110. }

结果和打印如下图2和3所示:

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

方法二:利用runnable实现更新:

由于部分方法跟上面一样,所以要看完整代码可以在下面下载,以下只是核心代码.

  1. public void loadToRunnable() {
  2. Runnable runable = new Runnable(){
  3. @Override
  4. public void run() {
  5. Log.d(TAG, #########Ok.. let update callback1...);
  6. mTitle.setText(####Yeah, Refresh in runnable, callback1);
  7. }
  8. };
  9.  
  10. Runnable runable2 = new Runnable() {
  11. @Override
  12. public void run() {
  13. Log.d(TAG, ######Ok, let update callback2, ...);
  14. mTitle.setText(####Callback2 update success!!!);
  15. }
  16. };
  17.  
  18. testExecutorRunnable(runable, runable2, getMainLooper());
  19. }
  20. }
  1. public void testExecutorRunnable(final Runnable callback,
  2. final Runnable callback2, final Looper looper) {
  3. Log.d(TAG, #####testExecutor##mTask..#### = + mTask);
  4. if(mTask != null) {www.2cto.com
  5. mTask.cancel(false); // true 表示强制取消,会发生异常; false表示等待任务结束后取消.
  6. mSendMsg = false;
  7. }
  8.  
  9. Runnable r = new Runnable(){
  10. @Override
  11. public void run() {
  12. mSendMsg = true;
  13. try {
  14. Log.d(TAG, #####Step ####Async run after submit...###sleep 6s);
  15. Thread.sleep();
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. if(callback != null && mSendMsg){
  20. // Handler handler = new Handler(); // Not use it, should use the Looper.
  21. Handler handler = new Handler(looper);
  22. handler.post(callback);
  23. }
  24.  
  25. try {
  26. Log.d(TAG, #####Step ####Async run after submit...###sleep 4s);
  27. Thread.sleep();
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. if(callback2 != null && mSendMsg) {
  32. Handler handler = new Handler(looper);
  33. handler.post(callback2);
  34. }
  35. }
  36. };
  37.  
  38. mTask = mExecutor.submit(r);
  39. }

Android 异步更新UI-线程池-Future-Handler实例分析的更多相关文章

  1. Android异步更新UI的四种方式

    Android异步更新UI的四种方式 2015-09-06 09:23 segmentfault 字号:T | T 大家都知道由于性能要求,android要求只能在UI线程中更新UI,要想在其他线程中 ...

  2. [Android学习笔记]子线程更新UI线程方法之Handler

    关于此笔记 不讨论: 1.不讨论Handler实现细节 2.不讨论android线程派发细节 讨论: 子线程如何简单的使用Handler更新UI 问题: android开发时,如何在子线程更新UI? ...

  3. Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面

    Android应用的开发过程中需要把繁重的任务(IO,网络连接等)放到其他线程中异步执行,达到不阻塞UI的效果. 下面将由浅入深介绍Android进行异步处理的实现方法和系统底层的实现原理. 本文介绍 ...

  4. Android异步处理系列文章四篇之二 使用AsyncTask异步更新UI界面

    Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+Loope ...

  5. Android异步处理二:使用AsyncTask异步更新UI界面

    在<Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面>中,我们使用Thread+Handler的方式实现了异步更新UI界面,这一篇中,我们介绍一种更为简 ...

  6. handler机制和异步更新UI页面

    Android 提供了Handler和Looper来满足线程之间的通行,Handler是先进先出原则,Looper类用来管理特定线程内对象之间的消息互换,也可以使用Runnable来完成页面异步更新 ...

  7. Android 通过广播来异步更新UI

    之前的项目里要做一个异步更新UI的功能,可是结果出现了ANR,所以想写个demo来測试究竟是哪个地方出现了问题,结果发现原来的思路是没有问题,郁闷~~ 如今这个demo 就是模拟项目里面 的步骤 1. ...

  8. 使用AsyncTask异步更新UI界面及原理分析

    概述: AsyncTask是在Android SDK 1.5之后推出的一个方便编写后台线程与UI线程交互的辅助类.AsyncTask的内部实现是一个线程池,所有提交的异步任务都会在这个线程池中的工作线 ...

  9. OkHttp3几个简单的例子和在子线程更新UI线程的方法

    okHttp用于android的http请求.据说很厉害,我们来一起尝尝鲜.但是使用okHttp也会有一些小坑,后面会讲到如何掉进坑里并爬出来. 首先需要了解一点,这里说的UI线程和主线程是一回事儿. ...

随机推荐

  1. regular-第一课(正则表达式基础)

    之前一直听说正则表达式,尤其是在学习java的时候,遇到了不少关于正则表达式的用法.例如一个输入框,你可以使用正则表达式限制输入的内容.当然,在android以后,正则表达式就几乎没有怎么用了.不过呢 ...

  2. HTTP协议建立连接、通讯与关闭连接全过程

    为解决服务器TimeWait多的问题,了解了一下TCP/IP协议的连接过程.以访问一静态页面为例,从建立连接到访问拿到数据,然后关闭的整个过程.使用EtherPeek截图如下:   图首为一次交互过程 ...

  3. 用djbdns为域名解析服务护航

      上期回顾:http://chenguang.blog.51cto.com/350944/292195       650) this.width=650;" alt="&quo ...

  4. windows新建或者重命名文件及文件夹必须手动刷新才能显示出来

    平台:win8.1 问题:windows新建或者重命名文件及文件夹必须手动刷新才能显示出来 解决方法: 注册表中HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\ ...

  5. Vue框架学习笔记

    <div id="app"> </div> var app = new Vue({ el:"#app", // 绑定的元素 data:{ ...

  6. Linux 网卡驱动学习(九)(层二转发)

    1.mac 地址表的自学习过程 port1上的A计算机要与port2上的B计算机通信时,A发到交换机上,交换机收到信息后,交换机先记录发port1所相应的a的mac地址并记录在自己的mac表中,然后再 ...

  7. thinkphp动态注册路由

    thinkphp动态注册路由 一.总结 1.thinkphp使用路由步骤:a.config配置文件中开启路由  b.Route类的rule方法创建路由(在Routephp中)Route::rule(' ...

  8. RPC调用框架比较分析--转载

    原文地址:http://itindex.net/detail/52530-rpc-%E6%A1%86%E6%9E%B6-%E5%88%86%E6%9E%90 什么是RPC: RPC(Remote Pr ...

  9. Invalid property 'annotatedClasses' of bean class

    Invalid property 'annotatedClasses' of bean class 在整合Hibernate和Spring时出现,Invalid property 'annotated ...

  10. 【习题 6-9 UVA - 127】"Accordian" Patience

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 链表模拟即可. 1pile不能加s... [代码] #include <bits/stdc++.h> using nam ...