Android开发经验谈Android进阶之路Android技术进阶

浅谈Handler消息机制并手撸一套Handler

2019-07-20  本文已影响4人  Android师哥
浅谈Handler.png

前言

Handler是用于Android接收、传递、处理消息的处理类,结合Message、MessageQueue、Looper实现一个消息循环机制。


子线程向UI线程传递消息

    Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Log.i(TAG, Thread.currentThread().getName() + "收到消息:" + msg.obj);
        }
    };
    public void sendMessage(View view) {
        Thread mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                Message message = mHandler.obtainMessage();
                message.obj = Thread.currentThread().getName() + "发送消息";
                mHandler.sendMessage(message);
            }
        });
        mThread.setName("子线程");
        mThread.start();
    }

子线程之间传递消息

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Thread mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();  //  1
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        Log.i(TAG, Thread.currentThread().getName() + ": 收到" + msg.obj);
                    }
                };
                Looper.loop();   //  2
            }
        });
        mThread.setName("线程1");
        mThread.start();
    }
      public void sendMessage(View view) {
        Thread mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                Message message = mHandler.obtainMessage();
                message.obj = Thread.currentThread().getName() + "发送的消息";
                mHandler.sendMessage(message);
            }
        });
        mThread.setName("线程2");
        mThread.start();
    }

源码解析

    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();   //A
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

从源码A处可以看到,创建Handler需要获取一个Looper对象。继续看看myLooper()方法:

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

是从ThreadLocal中取出,问题来了,我们什么时候放进去的呢?这就回到了我们的起初的问题,为什么在子线程中创建Handler要调用Looper.prepare(),而在UI线程中却不需要。
首先看看Looper.prepare()方法做了什么

    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));   //B
    }

可以看到,在源码B处创建了Looper对象并存入ThreadLocal中。接下来就是解释为什么UI线程不用调用Looper.prepare(),其实UI线程也调用了prepare()方法,只是Android帮我们调用了,APP在初始化的时候会执行ActivityThread类的main()方法,看源码:

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();      //C

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();          //D

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

关注代码C处,Looper.prepareMainLooper()方法,同样最后调用了prepare()方法

    public static void prepareMainLooper() {
        prepare(false);   //实例化Looper
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

到现在,Handler的创建和Looper的实例化已经完成,接下来就是消息的发送。

    boolean enqueueMessage(Message msg, long when) {
        //  ...  代码省略  ...
        synchronized (this) {
            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) {//  E
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {    //F
                // 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;
    }

从代码中发现,Message都是存放在Message.next上,头消息(代码片段E),还是下面消息(代码片段F),都会将next空出来。犹如链表的方式储存。

    public static void loop() {
        final Looper me = myLooper();//获取Looper对象
        //  ... 代码省略  ...
        final MessageQueue queue = me.mQueue; //获取MessageQueue对象

        //  ... 代码省略  ...

        for (;;) {
            Message msg = queue.next(); // might block
       //  ... 代码省略  ...
            msg.target.dispatchMessage(msg); //分发消息
        //  ... 代码省略  ...   
        }
    }

不断从MessageQueue中取消息,并分发消息。接下来看看消息分发:

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

常规情况下会调用handleMessage()方法,也就是我们在创建Handler时重写的方法,将分发的消息给我们反了回来。

Handler消息流程图

消息流程

手撸Handler

class Handler {
   private Looper mLooper;
   private MessageQueue mMessageQueue;

   public Handler() {
       this.mLooper = Looper.myLooper();
       if(mLooper==null){
           throw new RuntimeException("当前线程未初始化Looper");
       }
       this.mMessageQueue = mLooper.mMessageQueue;
   }

   /**
    * 发送消息
    * @param message
    */
   public void sendMessage(Message message) {
       message.target = this;
       mMessageQueue.enqueueMessage(message);
   }

   /**
    * 分发消息
    * @param message
    */
   public void dispatchMessage(Message message) {
       handleMessage(message);
   }

   public void handleMessage(Message message) {

   }
}
class Looper {
    private static final String TAG = "Looper";
    final MessageQueue mMessageQueue;
    /**
     * 多线程情况下保证变量的隔离
     */
    private static ThreadLocal<Looper> mThreadLocals = new ThreadLocal<>();

    public Looper() {
        mMessageQueue = new MessageQueue();
    }

    /**
     * 初始化Looper
     */
    public static void prepare() {
        if (mThreadLocals.get() != null) {
            //当前线程已维护Looper
            throw new RuntimeException("同一个线程只可以维护一个Looper");
        }
        mThreadLocals.set(new Looper());
        System.out.println("初始化线程Looper");
    }

    /**
     * 获取当前线程维护的Looper对象
     *
     * @return 前线程维护的Looper对象
     */
    public static Looper myLooper() {
        return mThreadLocals.get();
    }

    public static void loop() {
        Looper looper = myLooper();
        Message msg;
        for (; ; ) {
            msg = looper.mMessageQueue.next();
            if (msg == null || msg.target == null) {
                continue;
            }
            System.out.println("分发消息");
            msg.target.dispatchMessage(msg);
        }
    }
}
class Message {
    /**
     * 携带消息体
     */
    public Object obj;
    /**
     * 目标Handler
     */
    public Handler target;

    @Override
    public String toString() {
        return super.toString();
    }
}
class MessageQueue {
    private static final String TAG = "MessageQueue";
    Message[] messages;
    Lock mLock;
    Condition getCondition;
    Condition addCondition;
    int mCount;
    int mPutIndex;
    int mTakeIndex;

    public MessageQueue() {
        this.messages = new Message[50];
        this.mLock = new ReentrantLock();
        this.getCondition = mLock.newCondition();
        this.addCondition = mLock.newCondition();
    }

    /**
     * 出队
     *
     * @return
     */
    Message next() {
        Message message = null;
        try {
            mLock.lock();
            while (mCount <= 0) {
                System.out.println("空队列,读锁阻塞");
                getCondition.await();
            }
            message = messages[mTakeIndex];
            messages[mTakeIndex] = null;
            mTakeIndex = ++mTakeIndex >= messages.length ? 0 : mTakeIndex;
            mCount--;
            addCondition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mLock.unlock();
        }
        return message;
    }

    /**
     * 进消息队列
     *
     * @param message
     */
    public void enqueueMessage(Message message) {
        try {
            mLock.lock();
            while (mCount >= messages.length) {
                System.out.println("队列已满,写锁阻塞");
                //队列已满
                addCondition.await();
            }
            messages[mPutIndex] = message;
            //防止越界
            mPutIndex = (++mPutIndex >= messages.length) ? 0 : mPutIndex;
            mCount++;
            getCondition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mLock.unlock();
        }
    }
}

测试及结果

public class MainActivity extends AppCompatActivity {
    private Handler mHandler;
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Thread mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                //初始化当前线程Looper
                Looper.prepare();
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message message) {
                        System.out.println(Thread.currentThread().getName() + "收到消息:" + message.obj);
                    }
                };
                //开启消息循环
                Looper.loop();
            }
        });
        mThread.setName("曹操");
        mThread.start();
    }

    public void sendCustomMessage(View view) {
        Thread mThread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                Message mMessage = new Message();
                mMessage.obj = Thread.currentThread().getName() + "发送消息";
                mHandler.sendMessage(mMessage);
            }
        });

        Thread mThread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                Message mMessage = new Message();
                mMessage.obj = Thread.currentThread().getName() + "发送消息";
                mHandler.sendMessage(mMessage);
            }
        });

        mThread1.setName("典韦");
        mThread2.setName("许褚");
        mThread1.start();
        mThread2.start();
    }
}
测试结果

疑惑问答

  1. Q:是一个线程维护一个Looper,一个Looper对应一个MessageQueue吗?
    答:是,从源码可以发现,在调用Looper.prepare()时,如果在ThreadLocal中获取Looper不为null,就会抛出异常throw new RuntimeException("Only one Looper may be created per thread")。MessageQueue的初始化又在Looper的构造方法中,所以他们时一一对应的。
  2. Q:Condition.await()方法为什么要放在while循环中
    答:为了确保在唤醒阻塞锁的时候,MessageQueue中还又消息可操作。如果时if语句,唤醒就不会在判断,容易出现异常。
上一篇下一篇

猜你喜欢

热点阅读