Handler机制

handler机制

handler Message MessageQueue Looper

1. MessageQueue 和Looper的创建

初始化Handler时

 public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) { // 默认为false
            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();    // 获取Looper实例,如果为null,则说明不是在线程中执行的
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //通过looper获取到looper中保存的messagequeue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
  1. Looper.myLooper()如何获取looper;
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

每一个线程对应一个Looper 对应一个MessageQueue,每一个线程对应一个Looper 是通过ThreadLocal(线程级的单例实现)。
当调用Looper.prepare() 创建looper的时候,会先从ThreadLocal中取出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));
}


private Looper(boolean quitAllowed) {
    // 创建looper时会创建一个MessageQueue;
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

// MessageQueue.java中,调用jni方法得到一个long类型的mPtr指针
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

  1. 在创建looper对象时会创建一个messageQueue 并且通过一个final类型的成员变量保存起来这样一个Looper就对应唯一的MessageQueue当创建MessageQueue的时候会调用 nativeinit() 这个jni方法

看一下nativeInit();

//  /frameworks/base/core/jni/android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    // 创建了一个c++ 层的NativeMessageQueue
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
    }
}
  1. NativeMessageQueue
    在构造的时候又会创建一个C++ ,并且通过jni的调用把c++ 层的NativeMessageQueue 的指针传递给MessageQueue 通过一个long类型的成员变量 mptr保存起来。关于C++ 中的nativeXXX方法不做过多分析,只要明白mPtr为native层的MessageQueue的指针即可。

2.取消息

Looper.loop()

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    final Looper me = myLooper();
    ...
    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;
        }
        ...
        msg.target.dispatchMessage(msg);
        ...
        msg.recycleUnchecked();
    }
}
  1. 通过这个方法让Looper不断去消息队中取消息,
    Looper.loop中有一个死循环,这个死循环不会对主线程的运行造成影响,因为所有的用户操作,都是通过消息机制来实现的,都会往消息队列中发消息通过Looper.loop能把消息出来,继续执行代码,如果没有死循环ActivityThread类的main方法执行完程序就退出了。
Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    ...
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        nativePollOnce(ptr, nextPollTimeoutMillis)
        ...
    }
}
  1. queue.next是可能阻塞的,queue.next也是一个死循环
    通过nativepollonce方法通过NativeMessageQueue,调用c++的loop 的pollonce方法
    去消息队列(NativeMessageQueue)中取消息,如果能取到,判断一下消息要执行的时间
    如果到了要执行的时间,就把消息返回去,那么queue.next()方法就会继续执行下去。
    取出的消息会通过handler去处理。
    nativepollonce 取消息时用到了Linux的一个进程间通信机制
    • Linux的一个进程间通信机制:管道(pipe)。原理:在内存中有一个特殊的文件,这个文件有两
      个句柄(引用),一个是读取句柄,一个是写入句柄
    • 主线程Looper从消息队列读取消息,当读完所有消息时,进入睡眠,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒。只是为了读取消息,当消息读取完毕,再次睡眠
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

3.向消息队列中发送消息

消息在消息队列中的排序就是根据消息要执行的时间进行排序的
通过handler向消息队列发送消息sendMessage sendEmptyMessage sendMessageAtTime
sendEmptyMessageAtTime sendMessageDelayed
实际上调用的都是sendMessageAtTime
sendMessageAtTime方法先获取到消息队列MessageQueue对象

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构造的时候,就会通过一个成员变量保存起来

 public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) { // 默认为false
            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());
            }
        }

        //通过myLooper()方法到当前线程中获取到对应的looper需要注意子线程中使用handler 要先调用
        //Looper.prepare()创建一个looper对象才能new handler 否则会抛异常
        mLooper = Looper.myLooper();    //获取Looper实例,如果为null,则说明不是在线程中执行的
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //通过looper获取到looper中保存的messagequeue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

sendMessageAtTime

是通过enqueueMessage方法,把消息发送到消息队列的enqueueMessage方法,会比较新发送来的消息要执行的时间和消息队列第一条消息要执行的时间。

如果新发送来的消息要立即执行(或者比当前消息队列第一条消息更早执行),那么就把这条消息放到消息队列的第一条消息,如果刚发来的消息不需要立即执行,那就遍历消息队列根据消息,要执行的时间找到一个合适的位置把消息放进去,如果消息队列有消息要立即执行就调用nativewake方法nativewake方法会通过jni调用找到nativeMessageQueue 调用c++ looper的wake方法向管道的文件描述符写端写一个w ,就会把Looper唤醒Looper就开始取消息了Message msg = Message.obtain();

4.处理消息

在Looper.loop方法取出消息之后,会调用msg.target是handlermsg.target.dispatchMessage()方法来处理消息

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

handler

  1. MessageQueue Looper创建(Looper.prepare()主线程Looper.prepareMainLooper());
    一个线程对应唯一的Looper和唯一的MessageQueue。MessageQueue在创建的时候通过jni 创建了一个NativeMesssageQueue->创建了一个c++Looper和MessageQueue中保存了一个long类型成员变量mptr,保存了NativeMesssageQueue的指针。
  2. Looper.loop();取消息死循环Message msg = queue.next(); 阻塞的Linux管道取出消息调用hander.dispatchMessage
    向消息队列发送消息 handler.sendMessageAtTime()->messqueue.enqueuemessage();消息排序(通过消息要执行的时间进行排序)
  3. Message.next();有消息需要马上执行调用nativewake()->把Looper.looper() ,唤醒 queue.next()开始工作。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容