文档章节

拨云见日---android异步消息机制源码分析(一)

JohnnyR
 JohnnyR
发布于 2015/02/13 16:15
字数 2309
阅读 354
收藏 17
点赞 0
评论 0

做过windows GUI的同学应该清楚,一般的GUI操作都是基于消息机制的,应用程序维护一个消息队列,开发人员编写对应事件的回调函数就能实现我们想要的操作

其实android系统也和windows GUI一样,也是基于消息机制,今天让我们通过源码来揭开android消息机制的神秘面纱

谈起异步消息,就不能不提及Handler,在安卓中,由于主线程中不能做耗时操作,所以耗时操作必须让子线程执行,而且只能在主线程(即UI线程)中执行UI更新操作,通过Handler发送异步消息,我们就能更新UI,一般的handler的用法如下:

public class TestActivity extends Activity {
	
	private Handler mHandler;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_test);
		final TextView tv = (TextView) findViewById(R.id.textView);
		
		//创建一个handler对象,重写handleMessage回调函数,在回调函数里面做UI更新操作
		mHandler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				switch (msg.what) {
				case 0x1:
					//在这里更新UI
					Bundle b = msg.getData();
					tv.setText(b.getString("textview"));
					break;

				default:
					break;
				}
			}
		};
		
		new Thread(new InnerRunnable()).start();
	}
	
	private class InnerRunnable implements Runnable {

		@Override
		public void run() {
			//做耗时操作放在这里
			
			//创建一个消息对象
			Message msg = Message.obtain();
			msg.what = 0x1;
			//可以用Bundle传递数据
			Bundle b = new Bundle();
			b.putString("textview", "异步加载");
			mHandler.sendMessage(msg);
		}
		
	}
}

我们简要归纳一下Handler的常规使用方法:

1、在UI线程创建Handler对象,并重写handler的handlerMessage回调函数,可以在回调函数里面做UI更新操作

2、子线程完成耗时操作后,创建Message对象,用Message对象保存结果

3、在子线程通过handler对象调用sendMessage函数发送消息,这样UI线程收到消息后,获取数据,就可以更新UI

是不是很简单,那现在让我们一步一步来拨开迷雾,看清android异步消息本质

先简要介绍会涉及的组件:

Handler:消息发送者

Looper:消息循环,负责从MessageQueue获取消息,并处理

Message: 消息,可以存放数据

MessageQueue : 消息队列,负责存/取消息

从上面的例子我们得知,要使用Handler必须要先在UI线程创建一个Handler对象,那么我们通过源码查看Handler构造函数,代码位于: com.google.android / android/os/Handler.java,(因为源码较多,所以以下代码截图只包含关键代码)

public class Handler {
    final MessageQueue mQueue;
    final Looper mLooper;
    final Callback mCallback;
    final boolean mAsynchronous;
    IMessenger mMessenger;
    
     public Handler() {
        this(null, false);
    }
    
     public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> 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使用了无参构造函数版本,但内部最终调用了Handler(Callback callback, boolean async)构造函数

Handler(Callback callback, boolean async)代码开始,先检查FIND_POTENTIAL_LEAKS开关(该开关默认关闭),若该开关打开,则检查Handler所在类是否为匿名类/成员类或局部类时,并且该类是否是static的,如果不是static的,则输出Log提示使用者Handler类不为static可能会导致内存泄露,至于为什么会造成内存泄露,这里先卖个关子

调用Looper.myLooper()获取Looper对象,然后通过mQueue = mLooper.mQueue和Looper对象中的消息队列关联

那么跟进Looper.myLooper看看,代码位于: com.google.android / android/os/Looper.java

public final class Looper {
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
    final Thread mThread;
    
    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));
    }
    
     public static Looper myLooper() {
        return sThreadLocal.get();
    }
    
       private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
}

 Looper.myLooper()仅仅是返回一个线程副本,从代码得知,实际创建Looper对象的函数为Looper.paepare(),该函数创建Looper对象后存放到线程本地存储

Looper的构造函数为private,我们只能通过Looper.myLooper获取Looper对象,而创建Looper对象时,会创建一个消息队列

这样我们现在明白了handler创建相关的工作,现在让我们来看看子线程发送消息的处理机制

当子线程发送传递消息时,调用了handler对象的sendMessage函数

public class Handler {
    final MessageQueue mQueue;
    final Looper mLooper;
    final Callback mCallback;
    final boolean mAsynchronous;
    IMessenger mMessenger;

     public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
       }
       
     public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
     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);
    }
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
}

 根据上面的调用关系链:sendMessage->sendMessageDelayed->sendMessageAtTime->enqueueMessage;可以看到最终sendMessage最终只做了两件事情:

1、通过msg.target = this把Message对象和当前Handler对象关联

2、把消息放到handler中Looper对象的MessageQueue中

消费消息位于Looper.loop函数中

public final class Looper {

 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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            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);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            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();
        }
    }
}

Looper.loop函数:

1、通过 final Looper me = myLooper()获取当前线程的Looper对象

2、消息循环中, 通过Message msg = queue.next()获取队列中的消息,若没有消息则阻塞

3、调用msg.target.dispatchMessage(msg)处理消息

4、处理完毕后调用msg.recycle()回收消息

根据上面代码可得知实际处理消息的是msg.target.dispatchMessage(msg),那我们来看看msg.target到底是什么东西

public final class Message implements Parcelable { 
    /*package*/ int flags;

    /*package*/ long when;
    
    /*package*/ Bundle data;
    
    /*package*/ Handler target;     
    
    /*package*/ Runnable callback;   
    
    // sometimes we store linked lists of these things
    /*package*/ Message next;

    private static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;

    private static final int MAX_POOL_SIZE = 50;
    
     public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
    
        public void recycle() {
        clearForRecycle();

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
}

原来msg.target是一个handler对象,而最终调用的是handler的dispatchMessage函数来处理消息

public class Handler { 

    final MessageQueue mQueue;
    final Looper mLooper;
    final Callback mCallback;
    final boolean mAsynchronous;
    IMessenger mMessenger;
    
    public interface Callback {
        public boolean handleMessage(Message msg);
    }
    
     public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
}

根据以下代码可以得知消息处理的步骤如下: 

1、先判断Message的callback对象是否为空,不为空则回调Message对象的callback对象的函数(可以再创建Message时指定Callback)

2、如果创建Handler的Callback对象不为空,那么则调用Callback对象的handlerMessage函数

3、若没指定Handler的Callback或者Handler中Callback对象的handlerMessage函数调用失败,则回调handlerMessage,而这个handlerMessage函数正是我们创建handler时重写的函数

至此我们明白安卓异步消息的内部机制:

1、子线程通过handler向UI线程Looper中MessageQueue发送message

2、Looper处理消息时再回调handler中定义的回调

至此用一张图总结android异步消息机制(网上找的,凑合看):

所以要让一个线程要想成为消息处理线程,那么必须得有Looper才行,可能此时你会疑惑,为什么UI线程没有创建Looper却也能接收Handler发送来的消息?

其实,APP初始化过程中,android会自动帮UI线程创建Looper对象,代码位于:com.google.android / android/app/ActivityThread.java

public final class ActivityThread {
public static void main(String[] args) {
        SamplingProfilerIntegration.start();
        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());
        Security.addProvider(new AndroidKeyStoreProvider());
        Process.setArgV0("<pre-initialized>");
        
        Looper.prepareMainLooper();
        
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        AsyncTask.init();
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

 可以看到调用了Looper.prepareMainLooper()为主线程创建Looper,最后调用Looper.loop进行消息循环

通过分析源码,可以得知:

1、如果主线程和子线程,或者子线程同子线程通信,可以调用Looper.prepare()为Looper.loop()为子线程创建消息队列,再通过handler就可以非常简单的实现线程间通信

2、创建Message对象,一定要用Message.obtain()函数代替new Message()来创建消息,因为Looper处理完消息后,会调用  msg.recycle()函数把Message归还到消息池中,使用Message.obtain会先从消息池中获取,若没有才会创建新的Message,这样可以避免重复创建Message对象

3、Handler所在类必须是static类,因为非static内部类会隐式的持有外部类的引用,假如延时操作还未执行完成时就关闭Activity,因为handler持有Activity的引用,那么Activity就不会被及时回收而造成内存泄露


© 著作权归作者所有

共有 人打赏支持
JohnnyR
粉丝 8
博文 4
码字总数 5216
作品 0
广州
程序员
Android Handler异步通信:深入详解Handler机制源码

前言 在开发的多线程应用场景中,机制十分常用 今天,我将手把手带你深入分析 机制的源码,希望你们会喜欢 目录 1. Handler 机制简介 在多线程的应用场景中,将工作线程中需更新的操作信息 ...

carson_ho ⋅ 05/21 ⋅ 0

聊聊为什么在activity启动的时候获取不到View的宽高

周五一大早看到一篇鸿洋推了一篇博客讲onResume中Handler.post(Runnable)为什么获取不到宽高?,细细读了两遍,有些收获,但依然有些不明白的地方,同时对有些细节不太认同。于是自己梳理了一...

Marco黑八 ⋅ 06/03 ⋅ 0

Android Handler消息传递机制:图文解析工作原理

前言 在开发的多线程应用场景中,机制十分常用 今天,我将图文详解 机制 的工作原理,希望你们会喜欢 目录 1. 定义 一套 消息传递机制 2. 作用 在多线程的应用场景中,将工作线程中需更新的操...

⋅ 02/12 ⋅ 0

Handler消息处理机制分析

Handler经常用,然后自己总结一下下 一. What、Handler 是什么 Handler 与 Message、MessageQueue、Looper 一起构成了 Android 的消息机制,Android 系统通过大量的消息来与用户进行交互,V...

大二架构师 ⋅ 05/07 ⋅ 0

拨云见日---android异步消息机制源码分析(二)

在拨云见日---android异步消息机制源码分析(一)(http://my.oschina.net/u/1155515/blog/378460)中,我们了解了Java层异步消息机制的基本流程,可能细心的同学会发现java层中有很多native调...

JohnnyR ⋅ 2016/02/18 ⋅ 1

android蓝牙手柄、仿QQ看房、仿慕课网、数据库二维码框架等源码

Android精选源码 可自定义图片指示器并支持自定义Tab宽度的TabLayout源码(http://www.apkbus.com/thread-599243-1-1.html) android蓝牙控制手柄操作源码(http://www.apkbus.com/thread-59928...

逆鳞龙 ⋅ 05/15 ⋅ 0

安卓自定义View进阶-事件分发机制原理

安卓自定义View进阶-事件分发机制原理 之前讲解了很多与View绘图相关的知识,你可以在 安卓自定义View教程目录 中查看到这些文章,如果你理解了这些文章,那么至少2D绘图部分不是难题了,大部...

猴亮屏 ⋅ 05/22 ⋅ 0

内存泄露和LeakCanary的故事

新鲜文章请关注微信公众号: JueCode 今天我们来聊一聊Android中的内存泄露和内存泄露的检测工具LeakCanary。Java有垃圾回收线程为什么还会发生内存泄露?原因就是外部人为持有对象引用,持有...

juexingzhe ⋅ 05/22 ⋅ 0

Android:手把手教你学会使用Google出品的序列化神器Protocol Buffer

前言 习惯用 数据存储格式的你们,相信大多都没听过 其实 是 出品的一种轻量 & 高效的结构化数据存储格式,性能比 真的强!太!多! 由于 出品,我相信已经具备足够的吸引力 今天,我将详细介...

Carson_Ho ⋅ 04/16 ⋅ 0

Android AIDL浅析及异步调用

AIDL:Android Interface Definition Language,即 Android 接口定义语言。 AIDL 是什么 Android 系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。 为了使其...

cspecialy ⋅ 05/20 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

volatile和synchronized的区别

volatile和synchronized的区别 在讲这个之前需要先了解下JMM(Java memory Model :java内存模型):并发过程中如何处理可见性、原子性、有序性的问题--建立JMM模型 详情请看:https://baike.b...

MarinJ_Shao ⋅ 39分钟前 ⋅ 0

深入分析Kubernetes Critical Pod(一)

Author: xidianwangtao@gmail.com 摘要:大家在部署Kubernetes集群AddOn组件的时候,经常会看到Annotation scheduler.alpha.kubernetes.io/critical-pod"="",以表示这是一个关键服务,那你知...

WaltonWang ⋅ 46分钟前 ⋅ 0

原子性 - synchronized关键词

原子性概念 原子性提供了程序的互斥操作,同一时刻只能有一个线程能对某块代码进行操作。 原子性的实现方式 在jdk中,原子性的实现方式主要分为: synchronized:关键词,它依赖于JVM,保证了同...

dotleo ⋅ 53分钟前 ⋅ 0

【2018.06.22学习笔记】【linux高级知识 14.4-15.3】

14.4 exportfs命令 14.5 NFS客户端问题 15.1 FTP介绍 15.2/15.3 使用vsftpd搭建ftp

lgsxp ⋅ 今天 ⋅ 0

JeeSite 4.0 功能权限管理基础(Shiro)

Shiro是Apache的一个开源框架,是一个权限管理的框架,实现用户认证、用户授权等。 只要有用户参与一般都要有权限管理,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户...

ThinkGem ⋅ 昨天 ⋅ 0

python f-string 字符串格式化

主要内容 从Python 3.6开始,f-string是格式化字符串的一种很好的新方法。与其他格式化方式相比,它们不仅更易读,更简洁,不易出错,而且速度更快! 在本文的最后,您将了解如何以及为什么今...

阿豪boy ⋅ 昨天 ⋅ 0

Python实现自动登录站点

如果我们想要实现自动登录,那么我们就需要能够驱动浏览器(比如谷歌浏览器)来实现操作,ChromeDriver 刚好能够帮助我们这一点(非谷歌浏览器的驱动有所不同)。 一、确认软件版本 首先我们...

blackfoxya ⋅ 昨天 ⋅ 0

线性回归原理和实现基本认识

一:介绍 定义:线性回归在假设特证满足线性关系,根据给定的训练数据训练一个模型,并用此模型进行预测。为了了解这个定义,我们先举个简单的例子;我们假设一个线性方程 Y=2x+1, x变量为商...

wangxuwei ⋅ 昨天 ⋅ 0

容器之查看minikue的environment——minikube的环境信息

执行如下命令 mjduan@mjduandeMacBook-Pro:~/Docker % minikube docker-envexport DOCKER_TLS_VERIFY="1"export DOCKER_HOST="tcp://192.168.99.100:2376"export DOCKER_CERT_PATH="/U......

汉斯-冯-拉特 ⋅ 昨天 ⋅ 0

mysql远程连接不上

设置了root所有hosts远程登录,可是远程登录还是失败,原因可能如下: 登录本地数据库 mysql -uroot -p123456 查询用户表 mysql> select user,host,password from mysql.user; 删除密码为空的...

冰公子 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部