美文网首页
Handler怎么实现跨线程?

Handler怎么实现跨线程?

作者: 千夜零一 | 来源:发表于2021-06-02 14:26 被阅读0次

一般来说,提起跨线程通信,都能想起来使用Handler,但它到底是怎么实现跨线程的呢?

跨线程实现

handler的基本用法:

            /**
        * Handler用法
        */
        @SuppressLint("HandlerLeak")
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what == 0x11) {
                    //更新ui
                    Toast.makeText(getApplicationContext(),"更新UI操作",Toast.LENGTH_SHORT).show();
                }
            }
        };

        new Thread(new Runnable() {
            @Override
            public void run() {
                //FIXME 这里直接更新ui是不行的
                //还有其他更新ui方式,runOnUiThread()等
                Message message = Message.obtain();
                message.what = 0x11;
                handler.sendMessage(message);
            }
        }).start();

在子线程中通过调用Handler.sendMessage(Message message)方法来发送消息,之后重写Handler的handlerMessag(Message message)方法拿到这个message对象,做UI更新操作。

那么思考一个问题:sendMessage之后的消息处理方法handleMessage()怎么就在主线程执行了呢?

首先,明确一个线程只能有一个Looper对象,这是个消息循环,内部维护着一个MessageQueue消息队列,主线程也有它对应的Looper,这个MessageQueue中的消息来源可以很多,它并不关心是哪个线程发送来的Message统统加入这个消息队列中,通过查看源码我们知道入队操作在该方法中进行:

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

可以看到在这个方法中有一个synchronized修饰的同步代码块,同步锁针对的资源this指代的是当前的Message对象。

简化一下代码结构看看:

boolean enqueueMessage(Message msg, long when) {
    // ... 处理异常
    synchronized (this) {
        // ...加入消息队列
    }
    return true;
}

Looper.loop()方法在线程中被调用之后,我们的MessageQueue中的消息就被轮询起来。

所以,当消息循环执行到我们发送的那个消息时,它自然就在调用Looper.loop()方法的线程内执行了,这就是跨线程的原因,就这么简单。

那到底怎么做线程间切换的呢?

首先,多线程中一个Thread对应一个Looper对象。

这是通ThreadLocal来实现的:Looper【类】

// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

Thread【类】

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. 
     * 与此线程有关的 ThreadLocal 值。这张地图得到维护
     * 通过 ThreadLocal 类。 
     */
 ThreadLocal.ThreadLocalMap threadLocals = null;
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));
}
  /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     * 设置这个线程局部变量的当前线程的副本
     * 到指定值。大多数子类将不需要
     * 重写此方法,仅依赖于 {@link #initialValue}
     * 设置线程局部变量值的方法。
     *
     * @param value 要存储在当前线程的副本中的值
     * 这个线程本地的。
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 * 返回当前线程的 this 副本中的值
 * 线程局部变量。如果变量没有值
 * 当前线程,首先初始化为返回值
 * 通过调用 {@link #initialValue} 方法。
 *
 * @return 这个线程本地的当前线程的值
 */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocalMap是Thread类的属性。它里面保存着以ThreadLocal为key,Looper为value的Map。

也就是说通过ThreadLocal中根据key查找的Looper消息循环来切换线程。

验证一下:

/**
         * Handler用法
         */
        @SuppressLint("HandlerLeak")
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what == 0x11) {
                    //更新ui
                    Toast.makeText(getApplicationContext(),"更新UI操作",Toast.LENGTH_SHORT).show();
                }
            }
        };

        new Thread(new Runnable() {
            @Override
            public void run() {
                //FIXME 这里直接更新ui是不行的
                //还有其他更新ui方式,runOnUiThread()等
                Message message = Message.obtain();
                message.what = 0x11;
//                handler.sendMessage(message);   //------方式(1)------

                //从子线程切换到UI线程做更新UI操作
                runOnUiThread(()->{      //------方式(2)-------
                    Toast.makeText(getApplicationContext(),"更新UI操作",Toast.LENGTH_SHORT).show();
                });

                //也就是说指定的主线程的Looper来进行消息循环,自然就跨线程了
                Looper.prepare();     //------方式(3)-------
                new Handler(Looper.getMainLooper()){
                    @Override
                    public void handleMessage(@NonNull Message msg) {
                        super.handleMessage(msg);
                        if (msg.what == 0x11) {
                            //更新ui
//                            Toast.makeText(getApplicationContext(),"更新UI操作",Toast.LENGTH_SHORT).show();
                        }
                    }
                }.sendMessage(message);
                Looper.loop();
            }
        }).start();

好家伙,线程中也可以进行(方式<2、3>)更新UI操作哦~

相关文章

网友评论

      本文标题:Handler怎么实现跨线程?

      本文链接:https://www.haomeiwen.com/subject/uphosltx.html