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

xiaoxiao2021-02-28  104

一般用法:该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;     }
转载请注明原文地址: https://www.6miu.com/read-65355.html

最新回复(0)