android通信利器之Handler

概述

之所以说通信利器,是因为在整个android源码里面进程和线程间的通信主要是通过binder和handler来完成的,android由大量的消息驱动的方式来进行交互,比如上一节讲到的activty的启动流程,所以从某种意义上来说android是一个以消息驱动的系统,handler的消息机制设计 Handler\MessageQueue\Looper\Message.

  • Message :消息分为硬件消息(比如触摸和滑动等)和软件生成的消息(我们主动new Message 发送出的)。
  • MessageQueue: 消息队列,主要的功能是向消息池投递消息和取走消息池里面的消息。
  • Handler: 消息辅助类,主要功能向消息池发送各种消息(Handler.sendMessage)和处理相应各种消息(Handler.handleMessage);
  • Looper :不断循环,按照分发机制,将消息分给目标处理者。

下面我们写一个自定义消息handler消息的线程,我们Activty就有一个这样的loop线程用来handle消息

class LooperThread extends Thread {
    public Handler mHandler;
  
    public void run() {
        Looper.prepare();  
  
        mHandler = new Handler() {  
            public void handleMessage(Message msg) {
                //TODO    定义消息处理逻辑. 
            }
        };
  
        Looper.loop();  
    }
}

Looper

  • prepare()
    对于无参的情况下表示,默认调用 prepare(true),表示的是这个looper运行完退出,而对于false表示当前looper不运行退出,并且每个线程只被允许执行一次** prepare()**方法.
private static void prepare(boolean quitAllowed) {
    //每个线程只允许执行一次该方法,第二次执行时线程的TLS已有数据,则会抛出异常。
    if (sThreadLocal.get() != null) { 
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //创建Looper对象,并保存到当前线程的TLS区域
    sThreadLocal.set(new Looper(quitAllowed));   
}

上面的ThreadLocal 是线程本地存储区(Thread Local Storage),每个线程都有自己的私有本地线程存储区,这个存储区(TLS)不允许被其他的线程访问,TLS的常见操作通过set/get 转的参数为泛型,

public void set(T value) {
      Thread currentThread = Thread.currentThread(); //获取当前线程
      Values values = values(currentThread); //查找当前线程的本地储存区
      if (values == null) {
          //当线程本地存储区,尚未存储该线程相关信息时,则创建Values对象
          values = initializeValues(currentThread); 
      }
      //保存数据value到当前线程this
      values.put(this, value);
  }
public T get() {
      Thread currentThread = Thread.currentThread(); //获取当前线程
      Values values = values(currentThread); //查找当前线程的本地储存区
      if (values != null) {
          Object[] table = values.table;
          int index = hash & values.mask;
          if (this.reference == table[index]) {
              return (T) table[index + 1]; //返回当前线程储存区中的数据
          }
      } else {
          //创建Values对象
          values = initializeValues(currentThread);
      }
      return (T) values.getAfterMiss(this); //从目标线程存储区没有查询是则返回null
  }

我们看到 sThreadLocal.set(new Looper(quitAllowed)); 是传了 Looper类型参数,而且我们看到了 Looper.prepare(),会创建一个looper对象.
而在Looper类的构造方法中会是实例化一个MessageQueue对象。

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);  /
    mThread = Thread.currentThread();  //记录当前线程.
}

另外与prepare() 相似的方法,prepareMainLooper()方法,该方法主要在ActivityThread类中使用。

  • loop() Looper 在prepare 之后 就需要调用 loop方法,它里面实现是
public static void loop() {
    final Looper me = myLooper();  //获取TLS存储的Looper对象 
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;  //获取Looper对象中的消息队列

    Binder.clearCallingIdentity();
    //确保在权限检查时基于本地进程,而不是基于最初调用进程。
    final long ident = Binder.clearCallingIdentity(); 

    for (;;) { //进入loop的主循环方法
        Message msg = queue.next(); //可能会阻塞 
        if (msg == null) { //没有消息,则退出循环
            return;
        }
        
        Printer logging = me.mLogging;  //默认为null,可通过setMessageLogging()方法来指定输出,
用于debug功能,这个是调试代码不用管
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        msg.target.dispatchMessage(msg); //用于分发Message 
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        final long newIdent = Binder.clearCallingIdentity(); //确保分发过程中identity不会损坏
        if (ident != newIdent) {
             //打印identity改变的log,在分发消息过程中是不希望身份被改变的。
        }
        msg.recycleUnchecked();  //将Message放入消息池 
    }
}

loop()进入循环模式,不断重复下面的操作,直到没有消息时退出循环,可以看到上面有i 个for循环,它做的事情是

  • 读取MessageQueue的下一条Message;
  • 把Message分发给相应的target;
  • 再把分发后的Message回收到消息池,以便重复利用。

上面的myLooper()方法,是用于获取TLS存储的Looper对象,

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

如何退出? quit ()方法

public void quit() {
    mQueue.quit(false); //消息移除 
}

public void quitSafely() {
    mQueue.quit(true); //安全地消息移除
}

MessageQueue.quit()

void quit(boolean safe) {
        // 当mQuitAllowed为false,表示不运行退出,强行调用quit()会抛出异常
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
        synchronized (this) {
            if (mQuitting) { //防止多次执行退出操作
                return;
            }
            mQuitting = true;
            if (safe) {
                removeAllFutureMessagesLocked(); //移除尚未触发的所有消息
            } else {
                removeAllMessagesLocked(); //移除所有的消息
            }
            //mQuitting=false,那么认定为 mPtr != 0
            nativeWake(mPtr);
        }
    }
  • 当safe =true时,只移除尚未触发的所有消息,对于正在触发的消息并不移除;
  • 当safe =flase时,移除所有的消息

Handler

构造方法

  • 无参 方式

public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
//匿名类、内部类或本地类都必须申明为static,
否则会警告可能出现内存泄露,关于这个问题可以google一下网上有更加详细的解释
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());
}
}
//必须先执行Looper.prepare(),才能获取Looper对象,否则为null.
mLooper = Looper.myLooper(); //从当前线程的TLS中获取Looper对象
if (mLooper == null) {
throw new RuntimeException("");
}
mQueue = mLooper.mQueue; //消息队列,来自Looper对象
mCallback = callback; //回调方法
mAsynchronous = async; //设置消息是否为异步处理方式
}

对于Handler的无参构造方法,默认采用当前线程TLS中的Looper对象,并且callback回调方法为null,且消息为同步处理方式。只要执行的Looper.prepare()方法,那么便可以获取有效的Looper对象。

- 带参数的构造方法 

/**
* Use the provided {@link Looper} instead of the default one.
*
* @param looper The looper, must not be null.
*/
public Handler(Looper looper) {
this(looper, null, false);
}

public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

可以看出通过带參的方式,可以指定looper ,也可以设置是同步还是异步。
当然还有,(可以看源码中,提供了其他几种构造方法)
/**
 * Constructor associates this handler with the {@link Looper} for the
 * current thread and takes a callback interface in which you can handle
 * messages.
 *
 * If this thread does not have a looper, this handler won't be able to receive messages
 * so an exception is thrown.
 *
 * @param callback The callback interface in which to handle messages, or null.
 */
public Handler(Callback callback) {
    this(callback, false);
}
### dispatchMessage
你的handleMessage是如何收到消息的?上面我们提到了 在Looper.loop(),中当发现有消息时,调用消息的目标handler,执行dispatchMessage()方法来分发消息。

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//当Message存在回调方法,回调msg.callback.run()方法;
handleCallback(msg);
} else {
if (mCallback != null) {
//当Handler存在Callback成员变量时,回调方法handleMessage();
if (mCallback.handleMessage(msg)) {
return;
}
}
//Handler自身的回调方法handleMessage()
handleMessage(msg);
}
}

对于很多情况下,消息分发后的处理方法是最后面那种情况,即Handler.handleMessage(),一般地往往通过覆写该方法从而实现自己的业务逻辑。
###  sendMessage
消息发送的调用链关系图如下,最终都是调用MessageQueue.enqueueMessage();
![消息调用链](http://upload-images.jianshu.io/upload_images/2043394-1fc95744fc8532f0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- sendEmptyMessage

public final boolean sendEmptyMessage(int what){
return sendEmptyMessageDelayed(what, 0);
}

-  sendEmptyMessageDelayed

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}

- 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) {
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}

-  sendMessageAtFrontOfQueue

public final boolean sendMessageAtFrontOfQueue(Message msg) {
MessageQueue queue = mQueue;
if (queue == null) {
return false;
}
return enqueueMessage(queue, msg, 0);
}

**post()方法** 
post方法也是用于发送消息,并设置消息的callback,用于处理消息。这个方法是在Handler.java中

public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}

**obtainMessage**

获取消息

public final Message obtainMessage()
{
return Message.obtain(this); 【见5.2】
}

Handler.obtainMessage()方法,最终调用Message.obtainMessage(this),其中this为当前的Handler对象。
**removeMessages**

public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null); 【见 4.5】
}```
Handler类似于辅助类,更多的实现都是MessageQueue, Message中的方法。Handler的目的是为了更加方便的使用消息机制。

MessageQueue

MessageQueue是消息机制的Java层和C++层的连接纽带,大部分核心方法都交给native层来处理,其中MessageQueue类中涉及的native方法如下:

private native static long nativeInit(); 
private native static void nativeDestroy(long ptr); 
private native void nativePollOnce(long ptr, int timeoutMillis); 
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);

Message

每个消息用Message表示,Message主要包含以下内容:

数据类似 成员变量 解释
int what 消息类别
long when 消息触发时间
int arg1 参数1
int arg2 参数2
object object 消息内容
Handler target 消息相应放方
Runnable callback 回调方法

用markdown写表格真不方便呀,

最后就是整个消息发送和接收的流程图:


消息流程图解
  • Handler通过sendMessage()发送Message到MessageQueue队列;
  • Looper通过loop(),不断提取出达到触发条件的Message,并将Message交给target来处理;
  • 经过dispatchMessage()后,交回给Handler的handleMessage()来进行相应地处理。
  • 将Message加入MessageQueue时,处往管道写入字符,可以会唤醒loop线程;
  • 如果MessageQueue中没有Message,并处于Idle状态,则会执行IdelHandler接口中的方法,往往用于做一些清理性地工作。

消息分发的优先级:

  1. Message的回调方法:message.callback.run(),优先级最高;
  2. Handler的回调方法:Handler.mCallback.handleMessage(msg),优先级仅次于1;
  3. Handler的默认方法:Handler.handleMessage(msg),优先级最低。

我们平时在activity的子线程通知更新主线程(MainThread)的view时很多都是通过handler的方式,也是因为在ActivityThread中有这么一个Looper存在

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");
    }

End
最后喝水不忘挖井人,很多都是对着大神的博客然后自己对android的源码来写了.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容