美文网首页安卓性能知识学习
安卓handler线程通信以及源码学习

安卓handler线程通信以及源码学习

作者: 笑对浮华 | 来源:发表于2019-12-11 17:34 被阅读0次

    安卓是单线程模型,就是说更新UI的操作只能在主线程中进行,但在开发一款应用时,只用一条主线程是不行的,因为一些耗时操作在主线程中进行时,就会造成线程阻塞,用户体验不好,所以当我们需要做一些耗时操作,比如网络请求、图片加载等,就需要开启一个子线程,在子线程中进行这些耗时操作。既然开启一个子线程,那么这其中就涉及到了线程之间的通信,今天就通过这篇文章记录一下通过handler进行线程之间的通信。

    Handler通信机制是通过Handler、Looper、MessageQueue、Message这几个类建立起来的,下面我们通过这几个类来展开对Handler通信机制的学习。

    下面是我对这几个类的一个认识理解和概括:

    1、Handler,负责线程间消息的发送和接收处理;
    2、Looper,负责创建消息队列(MessageQueue),并循环读取消息队列中的消息;
    3、MessageQueue,负责存放由Handler发送的消息;
    4、Message,负责对需要通信的消息进行封装。

    下面我通过一个实现倒计时的demo来加深对上面四个类的认识以及应用。先贴上布局以及其代码:


    布局
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="START"
            android:id="@+id/start_btn"
            />
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:textColor="#000000"
            android:textSize="32sp"
            android:id="@+id/time_text"/>
    
    </RelativeLayout>
    

    布局里面就一个button和一个text view,点击button,text view每一秒刷新一次,实现倒计时的效果。
    下面先贴上MainActivity的代码:

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
        private Button mStartBtn;
        private TextView mTimeText;
    
        private static final int TIME_START_ACTION = 1;
        private static int TIME = 60;
        //创建一个Handler对象并实例化  重写handleMessage()方法对消息进行处理
        @SuppressLint("HandlerLeak")
        private Handler mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case TIME_START_ACTION:
                        mTimeText.setText(String.valueOf(msg.obj));
                        break;
                }
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
          
            //初始化控件
            initView();
        }
    
        private void initView() {
            mStartBtn = findViewById(R.id.start_btn);
            mTimeText = findViewById(R.id.time_text);
    
            mStartBtn.setOnClickListener(this);
            mTimeText.setText(String.valueOf(TIME));
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.start_btn:
                    //开启一个子线程,传入一个Runnable对象,重写run方法执行耗时操作
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                              //循环发送消息
                              for (int i = 0;i<60;i++){
                                  //通过Handler.sendMessage()发送消息
                                  mHandler.sendEmptyMessage(TIME_START_ACTION);
                                  try {
                                      //线程睡眠1s
                                      Thread.sleep(1000);
                                  } catch (InterruptedException e) {
                                      e.printStackTrace();
                                  }
                              }
                        }
                    }).start();
                    break;
            }
        }
    }
    

    从代码中可以看出,在按钮的点击事件中创建一个子线程,在子线程中让子线程每睡眠1s发送一次消息,一次达到倒计时的效果。发送消息我调用的sendEmptyMessage(int what)方法。这里概括一下发送消息的几种方法:

    1、第一类:send方案
    sendEmptyMessage(int what)——发送一个空消息,传入一个int类型标识符what,因为消息队列中可以存放多条消息,所以发送消息的时候传入标识符,handler在处理消息的时候靠这个来区分并作出不同的处理;
    sendEmptyMessageDelayed(int what, long delayMillis)——发送一个延时空消息,从方法名也能大概猜出这个方法能实现的功能了,其中传入了两个参数,第一个就不说了,和上面的都一样,第二个参数表示延时多少毫秒后发送消息;
    sendEmptyMessageAtTime(int what, long uptimeMillis)——在固定的时间发送一个消息,其中第二个参数类型是long型,所以需要将设置的时间转换成long型的毫秒值;
    以上是发送一个空消息,消息中不包含Message对象,handler接收到消息后,知道了在某个子线程中发生了这个事件,并随之做出相应的操作。
    sendMessage(Message msg)——发送一个消息,传入一个Message对象,将需要传送的信息封装在Message这个类里;
    sendMessageDelayed(Message msg, long delayMillis)——发送一个延时消息,和上面发送一个空消息差不多,区别在于这个方法需要传入一个Message对象;
    sendMessageAtFrontOfQueue(Message msg)——发送的消息放在队列的最前面;
    sendMessageAtTime(Message msg, long uptimeMillis)——在固定的事件发送一个消息;
    上面几种方法和发送一个空消息的区别在于,sendMessage()方法需要传入一个Message对象。下面通过一段代码看看这个方法的使用:

    Message message = new Message();//实例化一个Message对象
    message.obj = i;//填充数据
    message.what = TIME_START_ACTION;//设置标识
    mHandler.sendMessage(message);//发送消息
    

    在代码中填充数据我是通过message.obj来赋值的,此外还可以通过message.arg来填充一些int类型的数据,也可以用message.setData(Bundle data)方法,传入一个Bundle对象,使用Bundle传递数据就跟fragment之间传递数据时一样的。
    此时,有了Message对象,也可以通过message.setTarget(Handler target)方法设置一个目标Handler(消息Message时通过Handler发送和接收的,所以每一个Message总会有一个目标Handler与之对应),绑定了目标,就可以通过方法message.sendToTarget()方法发送消息。

    上面提到的send方案中,发送消息时我是直接new了一个Message对象,一般不推荐这样,在平时开发中,多用obtianMessage()方法从消息池中获取一个消息对象,因为直接new的话会为Meaage分配新的内存,性能优化时是不推崇的,所以在开发中一般这样来发送一个消息:

    mHandler.obtainMessage(TIME_START_ACTION,i).sendToTarget();
    

    以上就是对send方案的一个介绍,下面我们看看另外一种方案:

    2、第二类:post方案
    先贴上点击事件下使用post发送一个消息的代码:

    new Thread(() -> {
            for (int i = 60;i>0;i--){
                    try {
                           Thread.sleep(1000);
                        } catch (InterruptedException e) {
                           e.printStackTrace();
                        }
                    mHandler.post(() -> mTimeText.setText(String.valueOf(--TIME)));
            }
    }).start();
    

    使用post发送消息,其中又传入了一个Runnable对象,重写了run方法,在run方法中可以直接进行ui的相关的操作,使用lambda表达式,一行代码就搞定,第二种方案相较于第一种非常的简洁。
    以上两种方案都可以发送消息,实现线程之间的通信。


    下面我们通过Handler源码看看这几个类之间的关系,看看底层消息通信的实现。
    消息Message的发送和接收处理都是通过Handler来实现的,我们就从最简单的sendMessage(Message msg)看看Handler时如何将Message存放到MessageQueue的:

        /**
         * Pushes a message onto the end of the message queue after all pending messages
         * before the current time. It will be received in {@link #handleMessage},
         * in the thread attached to this handler.
         *  
         * @return Returns true if the message was successfully placed in to the 
         *         message queue.  Returns false on failure, usually because the
         *         looper processing the message queue is exiting.
         */
        public final boolean sendMessage(Message msg)
        {
            return sendMessageDelayed(msg, 0);
        }
    

    注释已经有了简要说明,这个方法是推送一条消息到MessageQueue中所有待定消息的最下面,这也就意味着不出意外最后进来的消息将最后被取出。传进来的消息是通过附属在当前线程的handler调用handlerMessage(Message msg)方法来接收处理的。
    方法下面返回了执行sendMessageDelayed(msg,0)方法后返回的值,所以sendMessage()方法也可以看成是延迟时间为0的一个sendMessageDelayed()方法,再往下走,看看sendMessageDelayed()方法做了啥操作:

        /**
         * Enqueue a message into the message queue after all pending messages
         * before (current time + delayMillis). You will receive it in
         * {@link #handleMessage}, in the thread attached to this handler.
         *  
         * @return Returns true if the message was successfully placed in to the 
         *         message queue.  Returns false on failure, usually because the
         *         looper processing the message queue is exiting.  Note that a
         *         result of true does not mean the message will be processed -- if
         *         the looper is quit before the delivery time of the message
         *         occurs then the message will be dropped.
         */
        public final boolean sendMessageDelayed(Message msg, long delayMillis)
        {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
    

    在这个方法里面,对传进来的long型时间参数做了个判断,当时间小于0时,重新赋值为0,然后返回了调用sendMessageAtTime()方法返回的值。
    接着往下看sendMessageAtTime()方法:

        /**
         * Enqueue a message into the message queue after all pending messages
         * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
         * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
         * Time spent in deep sleep will add an additional delay to execution.
         * You will receive it in {@link #handleMessage}, in the thread attached
         * to this handler.
         * 
         * @param uptimeMillis The absolute time at which the message should be
         *         delivered, using the
         *         {@link android.os.SystemClock#uptimeMillis} time-base.
         *         
         * @return Returns true if the message was successfully placed in to the 
         *         message queue.  Returns false on failure, usually because the
         *         looper processing the message queue is exiting.  Note that a
         *         result of true does not mean the message will be processed -- if
         *         the looper is quit before the delivery time of the message
         *         occurs then the message will be dropped.
         */
        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对象,并作了一个非空判断,最后返回调用enqueueMessage()方法返回的值,将MessageQueue对象、Message对象,以及一个时值传入进去,下面看看enqueueMessage()中对这三个值做了什么操作:

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
    }
    

    因为Message时通过Handler来发送和接收的,所以Message一定会有一个目标Handler,就是在这里通过msg.target = this;绑定的,最后MessageQueue对象调用了自身的enqueueMessage()方法,将消息对象传了进去,下面是enqueueMessage()方法:

    boolean enqueueMessage(Message msg, long when) {
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
    
            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) {
                    // 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;
        }
    

    这个方法里面主要就是将Message放到MessageQueue的操作了。这里总结一下Handler发送消息Message到消息队列MessageQueue的过程:

    Handler通过sendMessage()方法将Message发送给MessageQueue,然后MessageQueue将Message存放到底部去(若无特别指定)。


    以上是Handler发送消息的过程,下面来看看Handler接收个处理消息的过程,首先是Looper.prepare()方法创建Looper对象和Looper.loop()方法循环消息队列:

         /** Initialize the current thread as a looper.
          * This gives you a chance to create handlers that then reference
          * this looper, before actually starting the loop. Be sure to call
          * {@link #loop()} after calling this method, and end it by calling
          * {@link #quit()}.
          */
        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));
        }
    

    通过注释可以知道,prepare()方法是给当前线程初始化一个循环类,并且指出要确保在循环开始前调用方法loop(),结束后调用方法quit()。
    代码中,prepare()方法又调用了prepare()方法,并且传入一个boolean类型的参数,从参数名称可以看出这个参数决定是否允许退出,这里给定一个恒值true,具体意义先放一放。
    下面这个prepare()方法中首先做了一个非空判断,这里面有一个sThreadLocal,找到它后有一个介绍:

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

    sThreadLocal.get()在没有调用过prepare()方法时会返回一个null,所以当从sThreadLocal这里面取出的value不为空时,说明prepare()方法已经调用过了,这时就会抛出一个异常Only one Looper may be created per thread(每个线程只能创建一个Looper),从这里我们可以得知线程和Looper的关系:每个线程只能有一个Looper,在一个线程中,Looper只能调用一次prepare()方法
    接着往下看,当初次调用prepare()方法进来,执行了sThreadLocal.set()方法,新建了一个Looper对象,并传了进去,下面看看Looper中的构造方法做了啥操作。

    private Looper(boolean quitAllowed) {
         mQueue = new MessageQueue(quitAllowed);
         mThread = Thread.currentThread();
    }
    

    在构造方法中可以看到,在这里创建了一个消息队列MessageQueue,这也就是之前概念提到的Looper创建一个MessageQueue,此外将当前线程赋值给了一个全局变量。
    最开始调用方法prepare()时就传入的一个boolean类型的参数,一直没有用到,并且在创建消息队列的时候传了进去,点进去可以看到,在MessagQueue的构造方法中,将这个值又赋值给了一个全局变量。

    MessageQueue(boolean quitAllowed) {
            mQuitAllowed = quitAllowed;//赋值给了一个全局变量
            mPtr = nativeInit();
    }
    

    这个全局变量在quit()方法中的一个判断语句中用到,当是false的时候抛出异常Main thread not allowed to quit.,表明主线程不允许执行退出操作,从这儿就能看出,创建子线程时,这个参数为true,表明可以执行退出操作,主线程在创建时,这个参数时false,不能执行退出操作,这也表明,安卓系统最少会有一条主线程。

    void quit(boolean safe) {
            if (!mQuitAllowed) {
                throw new IllegalStateException("Main thread not allowed to quit.");
            }
    
            synchronized (this) {
                if (mQuitting) {
                    return;
                }
                mQuitting = true;
    
                if (safe) {
                    removeAllFutureMessagesLocked();
                } else {
                    removeAllMessagesLocked();
                }
    
                // We can assume mPtr != 0 because mQuitting was previously false.
                nativeWake(mPtr);
            }
        }
    

    小结:通过查看prepare()方法的源码,我们知道这个方法主要创建了Looper对象,Looper又创建了MessageQueue对象。

    下面来看Looper.loop()方法的源码:

        /**
         * Run the message queue in this thread. Be sure to call
         * {@link #quit()} to end the loop.
         */
    1    public static void loop() {
    2       final Looper me = myLooper();
    3        if (me == null) {
    4            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    5        }
    6        final MessageQueue queue = me.mQueue;
    7
    8        // Make sure the identity of this thread is that of the local process,
    9        // and keep track of what that identity token actually is.
    10        Binder.clearCallingIdentity();
    11        final long ident = Binder.clearCallingIdentity();
    12
    13       // Allow overriding a threshold with a system prop. e.g.
    14        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
    15        final int thresholdOverride =
    16                SystemProperties.getInt("log.looper."
    17                       + Process.myUid() + "."
    18                        + Thread.currentThread().getName()
    19                        + ".slow", 0);
    20
    21        boolean slowDeliveryDetected = false;
    22
    23        for (;;) {
    24            Message msg = queue.next(); // might block
    25            if (msg == null) {
    26                // No message indicates that the message queue is quitting.
    27                return;
    28            }
    29
    30            // This must be in a local variable, in case a UI event sets the logger
    31            final Printer logging = me.mLogging;
    32            if (logging != null) {
    33                logging.println(">>>>> Dispatching to " + msg.target + " " +
    34                        msg.callback + ": " + msg.what);
    35            }
    36
    37            final long traceTag = me.mTraceTag;
    38            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
    39            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
    40            if (thresholdOverride > 0) {
    41                slowDispatchThresholdMs = thresholdOverride;
    42                slowDeliveryThresholdMs = thresholdOverride;
    43            }
    44            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
    45            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
    46
    47            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
    48            final boolean needEndTime = logSlowDispatch;
    49
    50            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
    51                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
    52            }
    53
    54            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
    55            final long dispatchEnd;
    56            try {
    57                msg.target.dispatchMessage(msg);
    58                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
    59            } finally {
    60                if (traceTag != 0) {
    61                    Trace.traceEnd(traceTag);
    62                }
    63            }
    64            if (logSlowDelivery) {
    65                if (slowDeliveryDetected) {
    66                    if ((dispatchStart - msg.when) <= 10) {
    67                        Slog.w(TAG, "Drained");
    68                        slowDeliveryDetected = false;
    69                    }
    70                } else {
    71                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
    72                            msg)) {
    73                        // Once we write a slow delivery log, suppress until the queue drains.
    74                        slowDeliveryDetected = true;
    75                    }
    76                }
    77            }
    78            if (logSlowDispatch) {
    79                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
    80            }
    81
    82            if (logging != null) {
    83                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
    84            }
    85
    86            // Make sure that during the course of dispatching the
    87            // identity of the thread wasn't corrupted.
    88            final long newIdent = Binder.clearCallingIdentity();
    89            if (ident != newIdent) {
    90                Log.wtf(TAG, "Thread identity changed from 0x"
    91                        + Long.toHexString(ident) + " to 0x"
    92                        + Long.toHexString(newIdent) + " while dispatching to "
    93                        + msg.target.getClass().getName() + " "
    94                        + msg.callback + " what=" + msg.what);
    95            }
    96
    97            msg.recycleUnchecked();
    98        }
    99    }
    

    在loop()方法上面有段注释,要确保在循环结束调用quit()方法。
    首先通过myLooper()方法获取了一个Looper对象,其实myLooper()方法中就是执行了sThreadLocal.get();这个操作,跟prepare()方法下一样,获取一个Looper对象。
    3-5行是一个判断语句,如果获取到的Looper对象为null,会抛出异常,提示没有Looper对象,在Looper调用方法loop()之前要先调用方法prepare()方法。
    第6行是获取一个MessageQueue对象,在之前prepare()方法下创建的Looper下,其构造方法中创建了一个MessageQueue对象,并将这个对象赋值给了全局变量mQueue,所以在这里,直接通过me.mQueue获取到这个MessageQueue对象。
    7-22行看不大懂,跟我想要了解的业务逻辑貌似关联不大,这里先不去探究。
    23-98行是做的一个无限循环,for(;;)这种循环方式跟while(true)一样,没有外力干扰的情况下就会一直循环下去,地球不爆炸,它就不放假。
    第24行就是读取消息队列中的消息,并且提示有可能造成阻塞,在主线程中也是调用的这个方法进行一个无限循环,读取队列中的消息,也有可能在去除消息的时候造成阻塞,在子线程中影响不大,在主线程中,如果超过5s未响应,就会出现ANR。这就是为什么说耗时操作在子线程中进行。
    25-28是一个判断语句,当取出的消息为null时,表明MessageQueue已经退出了,此时就跳出循环。
    30-55行也看不大懂,跟业务逻辑关联也不大,这里先不做探究。
    第57行我又看到了对取出的消息msg的处理,msg.target.dispatchMessage(msg),在这行代码中,首先Message类的对象msg通过msg.target获取到它的目标Handler,记得上面在讲到handler发送消息的send方案中有一种是hander.sendMessage(Message msg),在这个方案中需要传入一个Message对象,在创建Message对象后,可以通过message.setTarget(Handler handler)方法给Message设置目标Handler,在这个方法中就是将传进来的handler赋值给了全局变量target,所以这里就直接通过msg.target获取到它的目标Handler,然后调用方法dispatchMessage()将消息发出去。
    中间的代码好像跟业务逻辑关系也不大,这里也不做探究了。
    最后,第97行代码,执行了msg.recycleUnchecked()这个操作,当消息从队列中取出后,释放它在队列中所占的资源,以便其他消息能够进入队列复用资源。

    小结:Looper.loop()方法主要是循环读取MessageQueue中的Message,并由与Message绑定的Handler去发送消息。
    下面我们就看看Handler是如何通过dispatchMessage(msg)发送消息的:

        /**
         * Handle system messages here.
         */
        public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    

    在这个方法中,只有一个判断语句,不同的条件执行不同的操作。在这里面msg.callback先不去看,后面配合post方案一起讲,else里面,对mCallback这个全局变量进行了一个判断,那我先去看看这个全局变量mCallback是什么东西:

    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();
            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;
    }
    
    public Handler(Looper looper, Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
    }
    

    在上面两个Handler构造方法中,对mCallback进行了声明,但我们一般创建Handler对象都没用这两个构造方法,而是用的这个:

    Handler构造方法
    为了方便阅读,我截图了这个构造方法,从图中可以看出,我们一般创建Handler对象时啥都没传,那么进入到这个方法,callback就为null,所以此时Handler发送消息就会调用dispatchMessage(Message msg)下的handleMessage(msg)方法。
    接着,我们来看看handleMessage(msg)方法:
        /**
         * Subclasses must implement this to receive messages.
         */
        public void handleMessage(Message msg) {
        }
    

    这个方法并没有具体的方法,其中只包含一个Message对象,但是注释说明了,子类必须实现这个方法才能接收到消息,所以这就有了我们在使用Hander的时候,在主线程中创建Hnadler对象的时候要重写这个方法,然后在在这个方法中对msg进行处理,这个msg就是从子线程传过来的消息。

    以上就是对Handler通信的源码学习,在这里做个总结:

    1、线程间Message的发送和接收处理是在Handler中完成的;
    2、Message存放于MessageQueue中,MessageQueue由Looper调用prepare()方法创建,并通过loop()方法循环读取;
    3、一个Thread只能有一个Looper,一个Looper只能由一个MessageQueue,一个MessageQueue中可以存放多条Message。


    上面再Handler发送消息的时候说到另外一种更加简洁的方案,就是通过post发送消息,我们就通过源码来看看post方案为什么会更加简洁:

        /**
         * Causes the Runnable r to be added to the message queue.
         * The runnable will be run on the thread to which this handler is 
         * attached. 
         *  
         * @param r The Runnable that will be executed.
         * 
         * @return Returns true if the Runnable was successfully placed in to the 
         *         message queue.  Returns false on failure, usually because the
         *         looper processing the message queue is exiting.
         */
        public final boolean post(Runnable r)
        {
           return  sendMessageDelayed(getPostMessage(r), 0);
        }
    

    注释中说到,因为传入的Runnable对象被添加到了MessageQueue中,所以,Runnable会运行在当前handler附属的线程上,也就是说,当前Handler在哪个线程创建的,那么Runnable中的操作将在那个线程中运行。从代码可以看到post底层的实现方法其实也是sendMessage,不过这里使用getPostMessage(Runnable r)方法传入了一个Message对象:

    private static Message getPostMessage(Runnable r) {
            Message m = Message.obtain();
            m.callback = r;
            return m;
    }
    

    这个方法下面主要是从消息池里面获取一个消息对象,然后通过m.callback = r;将Runnable对象设置给Message对象,记得在Handler处理消息的时候有一个判断是这样的:

    if (msg.callback != null) {
           handleCallback(msg);
    }
    

    所以此时Message对象里的callback不为空了,那么在处理消息的时候将走handleCallback(msg);这个方法,并且把Message对象传入了这个方法里面,我们看看这个方法里是怎么对Message做处理的:

    private static void handleCallback(Message message) {
            message.callback.run();
    }
    

    看到这里,我们就已经可以得知,post方案代码更加简洁的原因:
    因为post方案处理消息是通过Runnable中的run方法来处理的,而Runnable又是在当前Handler附属的Thread中运行的,所以当我们的Handler在主线程中创建,在子线程中调用post(Runnable r)方法发送消息时,可以直接在run方法下对UI进行操作。
    最后,众所周知,如果在子线程中接收消息,则需要通过Looper.prepare()方法来创建Looper对象,然后通过Looper.loop()来对消息队列进行循环读取,但这些操作在主线程中都是不需要的,这是因为,安卓系统是单线程模型,在应用启动时候会创建一条主线程,此时系统会自动完成Looper的相关操作,下面贴上主线创建的源码:

    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();//创建一个Looper对象
    
            // 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();//开启循环
    
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    

    好了,以上就是本人对Handler的学习和理解,在此记录一下。若读者朋友发现文中有描述不当的地方,还请指正,万分感谢。

    相关文章

      网友评论

        本文标题:安卓handler线程通信以及源码学习

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