Android基于Handler、Looper、MessageQueue、ThreadLocal的跨线程通信

来源:互联网 发布:人民网舆情监测室 知乎 编辑:程序博客网 时间:2024/06/05 16:09

一般用法:该demo没有使用Handler

    class SoundPoolListenerThread extends Thread {
        public SoundPoolListenerThread() {
            super("SoundPoolListenerThread");
        }

        @Override
        public void run() {

            Looper.prepare();
            mSoundPoolLooper = Looper.myLooper();

            synchronized (mSoundEffectsLock) {
                if (mSoundPool != null) {
                    mSoundPoolCallBack = new SoundPoolCallback();
                    mSoundPool.setOnLoadCompleteListener(mSoundPoolCallBack);
                }
                mSoundEffectsLock.notify();
            }
            Looper.loop();
        }
    }

这种Looper用法:目的跑起来个线程,在线程中执行work,对应在另一个地方应该调用Looper对象的quit来结束线程

2:上面代码竟然没有使用mSoundPoolLooper对象创建的Handler对象,没用的废物;

来看另一个demo
        mLooperThread = new Thread() {
            @Override
            public void run() {
                Log.v(TAG, "starting looper");
                Looper.prepare();
                mHandler = new Handler();
                sem.release();
                Looper.loop();
                Log.v(TAG, "quit looper");
            }
        };
        mLooperThread.start();

在某个地方我们会看到如下的操作:都是套路
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    command.run();
                } finally {
                    sem.release();
                }
            }
        });

分析一下第二个demo:
1-我们在调用mLooperThread.start()时,会去调用Thread的run(),run()中会(注意:套路来了)Looper.prepare() -> new Handler -> Looper.loop(),这三个方法都是在新的线程中执行的,即都运行在mLooperThread的空间中;

2- Looper.prepare():在sThreadLocal中创建一个与(调用prepare()对应的)线程对应的Looper对象;
    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));
    }
    我们在new Looper的时候还要new 一个MessageQueue,想让工人挑水,总得给个水桶吧,这个MessageQueue就是这个水桶
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

3- new Handler():
    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());
            }
        }

//new Handler()运行的线程所对应的Looper对象,也是通过ThreadLocal来管理不同线程的Looper对象
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
//取出MessageQueue:此时mLooper都是在线程空间创建的了,更别说Looper的成员变量的 创建空间了,肯定也是在线程空间中
//获取mQueue的目的:在sendMessage时好知道往哪个MessageQueue中丢消息
//在把Message入队到MessageQueue中时,还会把自己作为Message的target,一起放到MessageQueue中
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

和prepare对应的一个操作
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }   
 

先得出来一个小结论:Looper 需要在目的线程运行时创建,以便能够与目的线程关联,关联手段就是通过ThreadLocal实现;若需要向目的线程发消息,则Handler对象也应该在目标线程运行时创建,以便能够与目标进程对应的Looper绑定,本质就是持有了目标线程的Looper对象;

4- Looper.loop(): 记得恒小金服一个面试官问我 子线程转换到主线程的切入点是什么?应该就是在这里吧,主线程通过handler发送消息,入队到Handler的mQueue中,该mQueue由Looper创建,通过子线程Looper.loop不断循环,获取由主线程持有的子线程的Handler引用发出来的Message,进而切换到子线程;
可是那个半瓶醋面试官非要说是MessageQueue切换~~~,估计是跟我一样中午没睡醒吧~~

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对应的MessageQueue
        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);
            }
//调用Message对应的Handler,该Handler是和Looper绑定的,也不能乱发Message对不,谁发的谁处理
            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.recycleUnchecked();
        }
    }
注意:MessageQueue.next()可能会阻塞、也可能会返回null: 阻塞表示没有消息在MessageQueue中了,当有新消息到来时再运行;而返回null表明有一个null的消息,调用MessageQueue的quit方法才会有return null这种操作,即停止loop循环,退出线程。
再瞅一眼Handler发送消息的操作:实质就是:把Message对象放入到目标线程的Looper对象所持有的MessageQueue中去

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//mQueue从对应的Looper中得到,发消息不能没有目的的发
        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);
        }
//放到MessageQueue中等待 Looper.loop调用 queue.next来取出Message了
        return queue.enqueueMessage(msg, uptimeMillis);
    }


总结下如何实现线程切换:
先类比一下:有A和B两个工人,这两个工人之间有一条通信线路,当A工人想要B工人做某件任务时,就会通过通信线路告诉B工人;A和B两个工人通过监听通信线路来检查是不是有新的任务要去做,有的话就记在一个小册子上,然后执行,如果任务太多,小册子就起到一个记录的做作用;

类比到Android的线程通信:工人:Looper+Thread(反正Looper.loop是运行在Thread中)、通信线路:Handler、小册子:MessageQueue、任务:Message;A线程持有通过B线程的Looper创建的Handler(A工人持有通过B工人创建的线路),A线程通过Handler给B线程发消息,即将Message入队到B线程的Looper对应的MessageQueue中,然后运行在B线程的Looper.loop通过循环取出Message进行处理,实现了A线程到B线程的切换。
关键点:B线程暴露了一个Looper给A线程

还有一种操作

就是在Thread的run中创建了Handler,但是该Handler的构造方法中传入的是主线程的Looper,此时我们就可以往主线程中发消息了
    public Handler(Looper looper, Callback callback, boolean async) {
//无非就是跟不同的线程的Looper对象关联
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }


阅读全文
0 0
原创粉丝点击