博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android:异步处理之Handler、Looper、MessageQueue之间的恩怨(三)
阅读量:4507 次
发布时间:2019-06-08

本文共 6675 字,大约阅读时间需要 22 分钟。

前言

  如果你在阅读本文之前,你不知道Handler在Android中为何物,我建议你先看看本系列的第一篇博文《》;我们都知道在Android系统中不能在子线程中直接更新UI界面,所以我们一般借助Handler+Thread或者AsyncTask这两种方法来实现UI界面的更新。而Handler+Thread这方法其实就是子线程向UI主线程进行消息传递,通知UI主线程去更新界面的一套机制。因为有时候面试官比较喜欢和蔼可亲的考你Handler的这套机制,所以我们结合源代码深入的研究这套通讯机制是灰常有必要的,你想想如果能鄙视一下面试官,呵呵o(╯□╰)o。。

概述

  谷歌的这套消息机制是参考windows设计的,姑爷微爷之间有啥专利官司咱也不关心。一般来说,线程都会通过Looper来建立自己的消息循环,并且锁定一个FIFO的消息队列MessageQueue,Handler通过Looper来实现Message(消息)在MessageQueue中的存取。每一个Hanlder在实例化的时候都会自动或者手动绑定一个Looper,间接向一个MessageQueue发送Message,所以Handler也封装了消息发送和接收的接口。

入门例子

  看概述好闷的,琢磨文字不说,晦涩又难懂,记得住又成一个大问题。来不如来个例子瞧瞧比较实在,所以我在这里给大家写了一个向子线程发送消息并显示输出的例子,强调一下下哦,是向子线程哟。

 主要代码如下:

public class MainActivity extends ActionBarActivity {    private Handler handler;    private Button btn;        @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);                btn = (Button) findViewById(R.id.sendmsg);                new HandlerThread().start();//启动子线程                btn.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                handler.sendEmptyMessage(0);//向子线程发送消息            }        });    }    class HandlerThread extends Thread{        @Override        public void run() {            //开始建立消息循环            Looper.prepare();//初始化Looper            handler = new Handler(){
//默认绑定本线程的Looper @Override public void handleMessage(Message msg) { switch(msg.what){ case 0: Toast.makeText(MainActivity.this, "子线程收到消息", Toast.LENGTH_SHORT).show(); } } }; Looper.loop();//启动消息循环 } }}

布局文件:

我们只需要点击按钮,发送成功。。。。。

我在里简单说一下消息发送的过程:

1、启动一个子线程,并在子线程初始化一个Looper。

2、在HandlerThread中实例化Handler,Handler自动绑定上当前线程的Looper。

3、重写Handler里面的消息处理方法。

4、执行Looper.loop()启动消息循环,子线程进入等待消息状态。

做个小研究

  当然,由例子入手讲解才容易理解。我们就通过上面梳理好的消息发送流程,结合源代码来探究消息循环的建立、消息的分发和处理的原理。

1、Looper的初始化

我们进入Looper中查看源码:

public static void prepare() {  prepare(true);}private static void prepare(boolean quitAllowed) {  if (sThreadLocal.get() != null) {    throw new RuntimeException("Only one Looper may be created per thread");  }  sThreadLocal.set(new Looper(quitAllowed));}

在我们调用Looper的prepare这个静态方法的时候,我们发现这个线程创建了一个Looper实例,并将其赋值给sThreadLocal这个线程的局部变量中,当然我们可以肯定这个sThreadLocal是当前的线程私有的,不信自己度娘去。我们接下来就要看Looper的构造方法。

private Looper(boolean quitAllowed) {  mQueue = new MessageQueue(quitAllowed);  mThread = Thread.currentThread();}

相信大家的眼睛都是雪亮的吧?!在Looper()中,实例化了一个消息队列(MessageQueue)!并且如我们所愿的绑定到了mQueue这个局部变量上,在这里我们可以得出这么一个结论:调用Looper. prepare()的线程就建立起一个消息循环的对象,但是!并还没有开始展开消息循环这件大事件。

2、实例化Handler并绑定当前线程的Looper

我们可以看看Handler的源代码——Handler的构造方法

public Handler() {  this(null, false);}public Handler(Callback callback, boolean async) {  if (FIND_POTENTIAL_LEAKS) {    final Class
klass = getClass();  if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&      (klass.getModifiers() & Modifier.STATIC) == 0) {    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +      klass.getCanonicalName());    }  }  mLooper = Looper.myLooper();  if (mLooper == null) {    throw new RuntimeException(      "Can't create handler inside thread that has not called Looper.prepare()");  }  mQueue = mLooper.mQueue;  mCallback = callback;  mAsynchronous = async;}

在代码中我们通过handler = new Handler() 调用到了Handler(Callback callback, boolean async)这个方法;我们发现mLooper = Looper.myLooper()把线程中的Looper绑定到了Handler上,通过mQueue = mLooper.mQueue获取了线程的消息队列,我当然也可以换句话说:Handler已经绑定到了创建此Handler对象的线程的消息队列上了,所以咱们可以开始干坏事了。。。。

3、重写Handler的handleMessage()方法

public void handleMessage(Message msg) {}

没啥好说的,一个空方法而已,提供我们override的入口函数。

4、通过Looper.loop()启动消息循环

还是上面的思路,我们进入loop()里面看看,总会有收获的。。

public static void loop() {  final Looper me = myLooper();  if (me == null) {    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");  }  final MessageQueue queue = me.mQueue;   Binder.clearCallingIdentity();  final long ident = Binder.clearCallingIdentity();  for (;;) {    Message msg = queue.next(); // might block    if (msg == null) {
      return;    }    Printer logging = me.mLogging;     if (logging != null) {      logging.println(">>>>> Dispatching to " + msg.target + " " +        msg.callback + ": " + msg.what);    }    msg.target.dispatchMessage(msg);    if (logging != null) {      logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);    }    final long newIdent = Binder.clearCallingIdentity();      if (ident != newIdent) {        Log.wtf(TAG, "Thread identity changed from 0x"              + Long.toHexString(ident) + " to 0x"              + Long.toHexString(newIdent) + " while dispatching to "              + msg.target.getClass().getName() + " "              + msg.callback + " what=" + msg.what);      }    msg.recycle();   }}

  在loop()的这个静态方法中,我们可以注意到for (;;)这个方法,这是死胡同死循环,所以我们将其称作为“消息循环”,说起来挺形象滴。在消息循环中会调用queue.next()来获取消息队列中排队等待处理的消息,并将其赋值到msg这个变量上;接下来就判断如果msg != null 就开始分发消息,也就是执行msg.target.dispatchMessage(msg)。在分发消息结束后,将会回收掉这个消息,体现在msg.recycle()这个函数上。

msg.target是一个handler对象,表示需要处理这个消息的handler对象,所以我们回到Handler看看dispatchMessage()这个方法了:

public void dispatchMessage(Message msg) {  if (msg.callback != null) {    handleCallback(msg);  } else {    if (mCallback != null) {      if (mCallback.handleMessage(msg)) {        return;      }    }    handleMessage(msg);  }}

  不知道大家有没有一眼发现handleMessage()这个方法,这可不是我们在第三步重写Handler中的方法么。真相大白,当 msg.callback != null 并且 mCallback != null 时将会调用 handleMessage(msg) 来处理其他线程发送来的消息,我们通过覆盖这个方法来实现我们具体的消息处理过程;这也就是Handler消息处理机制的全部内容。

做个小结吧

  通读全文,我们可以知道消息循环机制的核心就是Looper,因为Looper持有了MessageQueue的对象,并且可以被一个线程设为该线程的一个局部变量,我们可以这么认为这个线程通过Looper拥有了一个消息队列。而Handler的用处就是封装了消息发送和消息处理的方法,在线程通信中,线程可以通过Handler发送消息给创建Handler的线程,通过Looper将消息放入进入消息接收线程的消息队列,等待Looper取出消息并在最后交给Handler处理具体消息。

再说一句

  我们会发现在Activity中实例化一个Handler并不需要Looper.prepare()来初始化一个Looper和Looper.loop()来启动消息循环,因为Activity在构造过程中已经对Looper进行了初始化并且建立了消息循环,参见ActivityThread.java中的代码:

public final class ActivityThread {    public static final void main(String[] args) {        ......        Looper.prepareMainLooper();        ......        ActivityThread thread = new ActivityThread();        thread.attach(false);        ......        Looper.loop();        ......        thread.detach();        ......    }}

  Android应用程序进程在启动的时候,会在进程中加载ActivityThread类,并且执行这个类的main函数,应用程序的消息循环过程就是在这个main函数里面实现的;如果大家想要更深入了解的话,建议大家去研究下Activity的启动机制哈。

 

作者:enjoy风铃

出处:
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则下次不给你转载了。

转载于:https://www.cnblogs.com/net168/p/4082217.html

你可能感兴趣的文章
实例管理
查看>>
《那些年啊,那些事——一个程序员的奋斗史》——34
查看>>
SQL Server如何清除连接过的服务器名称历史?
查看>>
怎么才能成为优秀的前端开发工程师?
查看>>
go语言基础之冒泡排序原理
查看>>
随笔分类 - C++托管(clr)(转)
查看>>
python2.7入门--- 日期和时间
查看>>
虚拟IP技术
查看>>
MFC用CWindowDC dc(GetParent())不能在标题栏画线的问题
查看>>
Django:环境搭建
查看>>
ACM山东工商 Contest - 软件171-2 第1次测验
查看>>
centos7.5yum安装mysql(官方yum源比较慢)
查看>>
华为往年笔试题【去重和排序】【vertor二维数组,迭代器】
查看>>
【转】EXCEL不显示科学计数法
查看>>
系统安装 - 我们找不到任何驱动器
查看>>
MySQL截取字符串的函数
查看>>
好久不来.
查看>>
deepin系统下安装git
查看>>
[转载]搜索引擎技术介绍
查看>>
HDOJ1811解题报告【拓扑排序 正向反向】
查看>>