文章类型: ANDROID
关键词: Android,多线程,异步任务,第二篇,,android,thread,Async,异步
内容摘要: 带你飞之Android多线程与异步任务--第二篇

带你飞之Android多线程与异步任务(第二篇)

2017/9/12 17:32:27    来源:apple    阅读:

休息了几天,今天晚上才有点空闲的时间来继续学习Android中的多线程与异步任务,当然十分感谢sundy老师的分享,讲的非常好,这么好的教程看完后不把老师的教导传达给大家,简直对不起bug~

没有看过第一篇博客的朋友可以查看这个链接:http://blog.csdn.net/ly985557461/article/details/43494837

看过第一篇的小伙伴们都知道我们讲解Android多线程与异步任务是分为5部分来讲解的,第一篇博客中我们主要讲解的是第二部分--在Android中使用的线程。今天我们就来讲解第三部分--使用handler异步时不可或缺的组件。

在讲解这部分之前,我们先来了解一下我们在Android中使用多线程时遇到的问题:

1:在Android中我们需要频繁的和UI进行交互,如果每次都创建Runnable对象,在里面实现更新UI的逻辑,这样看起来的话会非常的别扭,而且代码很难管理,这样的代码移交时会骂娘的~

2:我的程序中需要不断的加载更新的数据我该怎么确定数据的正确性?举个例子:平时我们可能会用到Cursor,就是数据库,假如数据库里面的数据发生了改变,我们开启了线程1去加载数据,在线程1去加载数据库数据的时候,数据库又发生了变化,此时我们又去开启线程2去加载数据。如果线程1加载数据需要消耗5秒钟,线程2加载数据需要消耗3秒钟,我们希望界面上显示的是线程2加载回来的数据,但是由于线程1加载的慢,最后界面呈现的就可能是线程1加载回来的数据,UI就出问题了,显示的不是最新的数据,那我们怎么确保数据的正确性?

3:用户快速的点击按钮,我的程序无法有足够快的相应,怎么办?比如有一个按钮,点击后需要进行大量的请求操作,假如用户快速点击的话,其实用户关心的是最后一次点击的效果,我们只需要执行最后一次点击的请求就行了。

上面这些问题我们用handler就可以很好的解决。


既然Handler可以很好的解决上面的问题,那我们就需要了解什么是Handler以及它的作用~

通过查看官方的文档我们可以知道Handler有下面几个构造方法~

1:handler()默认的构造函数--把我们的handler放到当前的线程并且绑定到当前线程的消息队列~

2:handler(Handler Callback)这个构造函数我们需要传递一个Callback,我们把calllback传递进去后,handler首先通过callback去处理这条消息,callback没法处理才会进入到hangler里面去继续运行~

3:handler(Looper)这个构造函数很重要,我们可以通过传入的Looper来告诉hangler到底绑定到那一个线程中去运行~,说到这里就需要澄清一个误区了:handler一定是在主线程中吗?现在你一定知道了,不一定!因为我们可以通过传入looper来决定handler在那一个线程中~相信平时大家看的最多的就是在Activity中创建了一个默认的handler--Handler handler = new Handler();这种创建方式是绑定到了UI线程(主线程),因为我们的Activity就是在主线程中的

所以产生这么一个误区~

了解完了handler的构造函数,我们看看handler的作用~

1:执行计划任务。你可以在预定的时间执行某些任务,可以实现定时器的效果~例如handler中对应的sendMessageAtTime、postDelay等方法~

2:线程间通信。在Android的应用启动时会创建一个主线程,主线程会创建一个消息队列来处理各种消息。当你在程序中创建了一个子线程时,如果你想在子线程中更新UI,你可以在你的子线程中拿到主线程中创建的handler对象,就可以通过该handler向主线程的消息队列发送消息了。由于Android要求在UI线程中更新界面,因此你可以通过这个方法在其他线程中更新界面~

3:确保操作始终在某个特定的线程中运行。例如当我们从数据库加载数据时,除了程序启动时需要加载外,每当我们收到数据改变的通知时也需要重新加载。为了确保数据的有效性(始终使用最后一次查询时得到的数据),并减少不必要的查询操作,我们应该确保它们在同一个线程中运行。比如前面举得例子,将两次数据库变更的操作通过handler发送到消息队列,就保证了顺序问题~

说了这么多就是说handler很有用~那么handler、looper、Message Queue之间的关系是什么呢?我们画一张简单的图片帮助大家理解它们之间的关系~

123.jpg

从这张图片可以看出:looper就相当于一个循环,不断的从Message Queue中取出Message,然后分发给handler来进行处理~你可以感受到我们的looper才是整个handler实现过程中最重要的部分~一个线程可以产生一个looper对象,由它来管理此线程里的Message Queue;你可以构造handler对象来与Looper沟通,以便push新消息到Message Queue里,或者接收Looper(从Message Queue取出)所送出来的消息;Message Queue就是用来存放线程放入的消息;UI Thread通常就是UI Thread,而Android启动程序时会为它建立一个Message Queue~

Looper提供了myLooper()方法,这个方法返回的是当前线程中的looper,如果我们使用handler默认的构造函数就会得到当前线程中的looper。还提供了getMainLooper()方法,这个方法得到的是主线程中的looper。

下面让我们通过代码做几个实验来了解handler的特性~,代码会在最后给出地址~需要的小伙伴可以下载下来自行去测试~

1:如果我们有很多个handler来使用同一个HandlerThread,那么它会发生什么样的一种情况?

结果:同一个HandlerThread中可以有多个Handler;同一个HandlerThread中使用不同的handler发送的Message并不会混淆,也就是说handler1发送的消息并不会分发给handler2进行处理~

2:handler可以创建到任意线程中吗?实验分两部分来做:(1)在普通的Thread中调用new Handler()创建并发送消息~(2)在HandlerThread onLooperPrepared()中调用new Handler()创建并发送消息~

结果:我们的handler是不可以直接创建到任意线程中去的~可以创建在有looper的线程中去,并且等待我们当前线程的looper准备好了之后再去创建我们的handler~

3:如何指定handler运行的线程?1:调用Handler(Looper)以确定其使用的Looper。2:创建Handler时调用HandlerThread.getLooper()获取指定线程的looper。3:调用Looper.getMainLooper()获取UI线程的Looper。

4:同一个handler中不同的消息会同时执行吗?不会同时执行,虽然handler发送了两条消息,但是消息加入进了该handler所在线程的消息队列中,只有等待上一条消息处理完毕后,looper才会从消息队列中取出下一条消息进行处理,所以同一个handler中不同的消息并不会同时执行。

5:对于正在执行的message,调用removeMessage有什么后果?这个问题又会分为两个小问题:

5.1:调用removeMessage移除一个还没有执行的Message,看看会有什么后果?答案是该message会从消息队列中移除,该消息并不会被执行。

5.2:移除一个正在执行的Message,观察有什么效果?答案是该Message并不会被移除掉,正常执行~

下面问题又来了,在同一个handlerThread中我们使用多个handler,那么message是不会混淆的,这是为什么呢?我们来看看Handler的源代码~

平时大家习惯调用sendMessage,sendMessageDelay等方法~此处用sendMessage来讲解,先看一下它的源代码~

public final boolean sendMessage(Message msg)  
{  
  return sendMessageDelayed(msg, 0);  
}


我看看到,方法里面调用了sendMessageDelayed方法,好接着看这个方法的代码~

public final boolean sendMessageDelayed(Message msg, long delayMillis){  
  if (delayMillis < 0) {  
   delayMillis = 0;  
 }  
  return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  
}

 

好吧,里面又调用了一个方法sendMessageAtTime,继续看代码~

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {  
   MessageQueue queue = mQueue;  
   if (queue == null) {  
      RuntimeException e = new RuntimeException(  
      this + " sendMessageAtTime() called with no mQueue");  
      Log.w("Looper", e.getMessage(), e);  
      return false;  
   }  
   return enqueueMessage(queue, msg, uptimeMillis);  
}

最后一个方法中首先判断MessageQueue是否为空,也就是该handler所在的线程应该是一个具有MessageQueue的线程!我们发送消息才不会报错,怎么样才可以让我们的线程具备MessageQueue呢?两种方法1:继承HandlerThread~ 2:继承Thread,但是在Thread中调用Loop.prepare()~这两个方法都可以让我们的线程具备MessageQueue~。额。。。。最后又调用了enqueueMessage方法,继续跟进吧~

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {  
   msg.target = this;  
   if (mAsynchronous) {  
       msg.setAsynchronous(true);  
   }  
   return queue.enqueueMessage(msg, uptimeMillis);  
}

我们看614行,这行很重要~msg.target=this;就相当于说了一句话:我这条消息归这个handler管,你们其他的handler就不要对我进行处理了~也确实是这样子的~Looper在分发的时候根据Message的归属地进行了准确的分发,所以同一个线程中,handlerA发送的Message并不会分发到handlerB中~


下面给出一个小例子,例子中表明了要测试的问题~点击不同的按钮测试不同的问题~ 有的按钮由于点击后handler所在的线程没有MessageQueue,所以会崩溃奥~注意点击的姿势~

好了,最后给出详细的测试的小例子,可以结合里面的代码,自己改动一下,看看是什么效果~有问题欢迎大家留言讨论。

Android中的Handler很重要,但是很简单,学好Handler很重要~

例子下载地址:

AndroidThreadHandler.zip

↑ 上一篇文章:带你飞之Android多线程与异步任务(第一篇) 关键词:Android,多线程,异步任务,异常操作 发布日期:2017/9/12 17:22:58
↓ 下一篇文章:java中接口的定义和接口的实现 关键词:java中接口的定义和接口的实现 发布日期:2017/9/12 17:34:58
相关文章:
带你飞之Android多线程与异步任务(第一篇) 关键词:Android,多线程,异步任务,异常操作 发布日期:2017-09-12 17:22
Android,UI主线程与子线程 关键词:Android,UI,主线程,子线程,多线程,Android,thread,工作 发布日期:2017-06-26 15:41
Eclipse中Android项目目录结构介绍 关键词:Eclipse中Android项目目录结构介绍 发布日期:2017-03-09 16:29
相关目录:.NETANDROIDJAVA软件开发
我要评论
正在加载评论信息......