消息机制--handler

ThreadLocal: 用来存储不同线程中的数据,在安卓消息机制中,threadloacl用来存储每个线程的looper

looper:消息循环机制,通过Loop.prepare()方法去创建
新建的looper将持有一下几个变量

private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;

新建looper的过程:

public static void prepare(){
    prepare(true);
}

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


private Looper(boolean quitAllowed) {
     //初始化消息队列
    mQueue = new MessageQueue(quitAllowed);
    //取得当线线程
    mThread = Thread.currentThread();
}

这里着重介绍ThreadLocal中的get()和set()两个方法如下:
存取原则如下:通过线程来映射对应的value,value中维护了一个数组,数组中存放localThread当前对象及对应的数据,存储规则如下:ThreadLocal的值在table数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下一个位置

public T get() {
    // Optimized for the fast path.
    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 = initializeValues(currentThread);
    }

    return (T) values.getAfterMiss(this);
}

public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);   
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}

void put(ThreadLocal<?> key, Object value) {  
    cleanUp();  
  
    // Keep track of first tombstone. That's where we want to go back  
    // and add an entry if necessary.  
    int firstTombstone = -1;  
  
    for (int index = key.hash & mask;; index = next(index)) {  
        Object k = table[index];  
  
        if (k == key.reference) {  
            // Replace existing entry.  
            table[index + 1] = value;  
            return;  
        }  
  
        if (k == null) {  
            if (firstTombstone == -1) {  
                // Fill in null slot.  
                table[index] = key.reference;  
                table[index + 1] = value;  
                size++;  
                return;  
            }  
  
            // Go back and replace first tombstone.  
            table[firstTombstone] = key.reference;  
            table[firstTombstone + 1] = value;  
            tombstones--;  
            size++;  
            return;  
        }  
  
        // Remember first tombstone.  
        if (firstTombstone == -1 && k == TOMBSTONE) {  
            firstTombstone = index;  
        }  
    }  
}  


Values values(Thread current) {   
  return current.localValues;
}

handler:
这里先列举下handle的几个构造方法:在新建一个handler对象的时候,其实已经关联了对应线程的looper对象(1.当handle没有动态去设置looper时,根据创建handler的线程,取出looper对象,所以用最终处理消息对应的线程就是创建handler的线程。2.当手动去传对应的looper对象时,最终处理消息的线程是根据looper所在的线程决定的,handle只是looper在执行中的一个对象而已,处理对象是不分线程的),取出了looper的msgqueue

//可以传自定义的looper,callback
  public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

//平时没有传任何参数时,最终会走到这个方法,参数:null,false
//callback:用来在最后处理消息的时候调用,下文给出
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去发送消息的过程:

   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);  
       }  
       //这里msg被加入消息队列queue  
       return queue.enqueueMessage(msg, uptimeMillis);  

接下来讲解looper如何从msgqueue中取出消息进行处理:
在新建handler对象后,looper要调用loop()方法,不断从msgqueue中取出消息处理,代码如下

public static void loop() {  
     final Looper me = myLooper();  
     if (me == null) {  
         throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");  
     }  
     //从Looper中取出消息队列  
     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);  
         }  
  
         //将消息交给target处理,这个target就是Handler类型  
         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();  
     }  
 }  

从上面loop()方法中,当有msg时,将会执行msg.target.dispatchMessage(msg) 方法,其中msg.target就是handler,这个在sendMseeage时,对msg进行了处理,由此可得最后由handler的dispatchMessage(msg)方法得到执行:

public void dispatchMessage(Message msg) {  
    if (msg.callback != null) {  
        //这个方法很简单,直接调用msg.callback.run();  
        handleCallback(msg);  
    } else {  
        //如果我们设置了callback会由callback来处理消息  
        if (mCallback != null) {  
            if (mCallback.handleMessage(msg)) {  
                return;  
            }  
        }  
        //否则消息就由这里来处理,这是我们最常用的处理方式  
        handleMessage(msg);  
    }  
}  

其中msg.callback是个Runnable接口的实现,什么时候会设置这个callback:handler.post(runnable),post方法最终会被封装成msg对象传过去

public final boolean post(Runnable run){
  return sendMessageDelay(getPostMessage(run),0);
}

mCallback :这个是在新建handler对象的时候传进去的,当没有传时为null
最后才会执行handler的handleMessage(msg)方法

注意事项:

  1. 在主线程创建handle时,系统默认提供了mainLooper,所以不需要再自己去创建looper了。但是在子线程时,需要自己去创建looper,并要去手动执行loop()方法去轮询查找消息,
  2. msg在哪个线程执行是依赖handler创建时的线程?还是looper所在的线程?--- 答案是looper所在的线程,因为在处理消息时,最终是由looper的loop()方法调用,再去调用handler对应的handleMessage()方法。
    这里注意一点,对象是不分线程的

这里举几个典型的例子:

子线程想要在主线程中执行一段代码
new Handler(getMainLooper()).post(new Runnable() {

        @Override

        public void run() {

            //todo

        }

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

推荐阅读更多精彩内容