美文网首页
Handler机制用法及源码解析

Handler机制用法及源码解析

作者: 林喜明 | 来源:发表于2019-01-05 14:27 被阅读0次

    Handler机制用法及源码解析

    1.Android 中 Handler 的作用

    在Android开发中,我们都知道不能在主线程中执行耗时的任务,避免产生ANR。那在子线程中处理完耗时操作要更新在UI线程中的界面该怎么办呢?
    这里就引入了Handler的概念,Handler的主要作用就是线程间异步任务的回调通知, 经常用在子线程中处理耗时操作后通知UI线程进行界面更新操作。

    比如我们有一个需求:界面上有一个按钮,点击后去下载一个很大的文件,下载完成后界面上的按钮需要消失。首先下载文件是一个耗时的操作,我们必须开启一个子线程来处理这个未知完成时间的耗时操作,而让按钮消失必须要在UI线程中处理。这就产生了一个问题,子线程与主线程之间如何进行通信。而Handler就是用于解决这个问题的方法之一。

    2.Handler 的使用:

    Handler 的使用方式可以分为两种
    1.通过 Handler + Message 的方式实现
    2.通过 Handler + Runnable 的方式实现
    下面我们就用demo来了解一下如何用Handler来解决上面描述的问题

    2.1通过Handler + Message的解决上面描述的需求
    package test.shop.cn.test;
    
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Looper;
    import android.os.Message;
    import android.support.v7.app.AppCompatActivity;
    import android.view.View;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import java.lang.ref.WeakReference;
    
    public class MainActivity extends AppCompatActivity {
        private static final int NET_SUCCESS = 0;
        private TextView mDownload;
        private UIHandler mHandler;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler = new UIHandler(this);
            mDownload = (TextView) findViewById(R.id.download);
            mDownload.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    ToDownloadFile();
                }
            });
        }
    
        /**
         * 模拟异步下载文件
         */
        private void ToDownloadFile(){
            //开启一个子线程
            Runnable mDownloadTask = new Runnable() {
                @Override
                public void run() {
                    try{
                        Thread.sleep(5000);//线程休眠5秒模拟下载操作
                        Message msg = mHandler.obtainMessage(NET_SUCCESS);
                        msg.obj = "从下载中获取的数据";
                        mHandler.sendMessage(msg);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            mDownloadTask.run();
        }
    
        private static class UIHandler extends Handler {
            WeakReference<MainActivity> mWeakRef;
    
            public UIHandler(MainActivity ref) {
                mWeakRef = new WeakReference<MainActivity>(ref);
            }
    
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                MainActivity activity = mWeakRef.get();
                if (activity == null || activity.isFinishing()) {
                    return;
                }
                switch (msg.what) {
                    case NET_SUCCESS:
                        String tip = (String)msg.obj;
                        Toast.makeText(activity, tip, Toast.LENGTH_SHORT).show();
                        activity.mDownload.setVisibility(View.GONE);
                        break;
                    default:
                        break;
                }
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if(mHandler != null){
                mHandler.removeCallbacksAndMessages(null);
                mHandler = null;
            }
        }
    }
    

    可能会有很多小伙伴会有疑问,为什么上面的代码里要自定义一个静态的UIHandler类继承Handler,而不用系统默认的Handler呢? 并且为什么要将这个界面的Activity在初始化的时候传入,并用弱引用( WeakReference<MainActivity>)引用当前Activity呢? 这是因为可能会存在内存泄露的问题,下面会详细说明

    这个就是系统默认使用handler的方式,直接在主线程中初始化:

        private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case NET_SUCCESS:
                        String tip = (String)msg.obj;
                        Toast.makeText(MainActivity.this, tip, Toast.LENGTH_SHORT).show();
                        mDownload.setVisibility(View.GONE);
                        break;
                    default:
                        break;
                }
            }
        };
    

    这里回答上面的疑问:我们知道在Java中,非静态内部类会隐性地持有外部类的引用,静态内部类则不会。在上面的代码中,我们在子线程中有5秒的耗时操作,如果用户在耗时操作还没完成的时候就退出这个Activity,就会产生内存泄露这是因为子线程持有Handler对象,Handler对象又隐性地持有了Activity的对象,当退出Activity发生GC内存回收时,因为Handler 持有Acitivity 的引用链导致Activity无法被回收,所以发生了内存泄露的问题。所以我们要在这里使用了静态内部类+弱引用持有当前Activity的方式,防止内存泄露问题,并且在onDestroy中及时清除消息。

    2.2 通过Handler + Runnable的方式实现
    public class MainActivity extends Activity{
          private TextView mDownload;
         @Override
         protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              mDownload = (TextView) findViewById(R.id.download);
              mDownload.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      ToDownloadFile();
                  }
            });
         }
        
         private void ToDownloadFile(){
              new Handler().postDelayed(new Runnable(){
                   @Override
                   public void run() {
                        mDownload.setVisibility(View.GONE);
                   }
              }, 2000);
          }
    }
    

    Handler + Runnable的方式在实际需求开发中用到的比较少,之所以分开Runnable和Message两种方式来实现,是因为很多人都搞不清楚为什么Handler可以推送Runnable和Message两种对象。

    其实,无论Handler将Runnable还是Message加入MessageQueue,最终都只是将Message加入到MessageQueue。Handler的post Runnable对象这个方法只是对post Message进行了一层封装,所以最终我们都是通过Handler推送了一个Message罢了。在下文源码中会对原理进行解析

    3.Handler 的源码解析

    文中贴出的源码删除了部分无关的代码,保留了重点部分,也会给出源码地址自行观看

    在了解源码之前首先要对以下的关键对象有个初步的了解:

    MessageQueue:消息队列,先进先出管理Message,当有消息的时候如果loop()处于休眠,则调用nativeWake(mPtr)方法唤醒loop()。在Looper对象初始化的时候会创建(new)一个与之关联的MessageQueue;

    Looper:通过TheadLocal保证每个线程只能够有一个Looper,通过轮询管理MessageQueue,不断地从队列中取出Message分发给对应的Handler处理;(原理是用了死循环不断执行循环,没消息时阻塞休眠等待消息)

    Handler:作用就是发送处理信息,如果希望Handler正常工作,在当前线程中要有个Looper对象

    MessageHandler接收与处理的消息对象,可用来传递的参数与不同消息的标识

    TheadLocalThreadLocal的作用是不同的线程拥有该线程独立的变量同名对象不会被受到不同线程间相互使用出现异常的情况。
    即:你的程序拥有多个线程,线程中要用到相同的对象,但又不允许线程之间操作同一份对象。那么就可以使用ThreadLocal来解决。每一个ThreadLocal能够放一个线程级别的变量,可是它本身能够被多个线程共享使用,并且又能够达到线程安全的目的,且绝对线程安全。

    初步了解完上述的关键对象后,我们开始从源码的角度来看是Handler 是如何实现线程间异步任务的回调通知

    首先我们从Handler这个类看起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());
                }
            }
            //上面中说的默认使用的方式 初始化后会调到这个方法,通过Looper.myLooper()获得主线中的Looper对象
            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;
        }
    

    Handler源码中我们可以看到,在我们代码中初始化Handler对象的时候源码中就会初始化Looper和MessageQueue对象,通过调用Looper.myLooper()获取当前线程绑定的Looper对象由于我们是在onCreate方法中初始化的,所以此线程是UI线程),然后将该Looper对象保存到名为mLooper的成员字段中。

    接着我们看一下Looper.myLooper()这个方法返回looper对象的原理Looper类的源码地址

    Looper类中通过ThreadLocal保存了所有线程的Looper引用,并提供get和set方法

        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    
        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));
        }
    

    这里可能很多人会疑问,为什么Looper.myLooper()能够直接获取到looper对象而不是空值,我们并没有传looper呀?我们看一下下面这段代码

    ActivityThread 类的源码地址

         public static void main(String[] args) {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
            //为当前线程(主线程)创建一个Looper对象
            Looper.prepareMainLooper();
    
            ActivityThread thread = new ActivityThread();
            thread.attach(false, startSeq);
    
            if (sMainThreadHandler == null) {
                //为当前线程设置Handler
                sMainThreadHandler = thread.getHandler();
            }
    
            if (false) {
                Looper.myLooper().setMessageLogging(new
                        LogPrinter(Log.DEBUG, "ActivityThread"));
            }
    
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            // 执行从消息队列中获取Message,并调用Handler进行处理的无限循环;
            //所有和主线程相关的消息处理都在该方法中执行
            Looper.loop();
    
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    

    这一段代码是ActivityThread类中的main方法,是Android应用的入口,每个apk中都包含一个且仅有一个ActivityThread类,并作为应用的主线程类。ActivityThread类管理主线程(UI线程)的执行,负责调度和运行应用中的activity,广播,以及其他操作

    上述代码涉及到了Android进程的启动原理,这里不过多叙述,不是本章的重点,大家有个概念就行,在apk启动的时候就会执行main方法,main方法中会初始化主线程中的Looper对象,并执行主线程Looper对象的loop()方法无限循环处理主线程中消息队列中的Message信息,这也解释了上面的问题,为什么Looper.myLooper()方法能够直接获取到主线程的looper对象,是因为apk启动的时候就已经初始化好了looper对象并保存在Looper类中的静态变量ThreadLocal<Looper> sThreadLocal中

    紧接着我们看一下Looper.loop();方法是如何处理MessageQueue中的消息的

    public static void loop() {
            final Looper me = myLooper();
            final MessageQueue queue = me.mQueue;
    
            for (;;) {
                //取出下一条消息,当next()返回为空时,next()中会休眠loop()
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                try {
                    msg.target.dispatchMessage(msg);
                } finally {
                }
                msg.recycleUnchecked();
            }
        }
    

    我们来看一下上面代码中几个关键的地方:

    1. final Looper me = myLooper() ; final MessageQueue queue = me.mQueue;
      获取到当前线程所绑定的Looper对象,以及当前线程所关联的MessageQueue

    2. for (;;){}
      开启死循环;

    3. Message msg = queue.next();
      不断从MessageQueue队列中取出消息,当next()返回为空时,next()中会休眠loop(),至于为什么next()方法能够休眠loop,本文不会进行讲解,有兴趣的小伙伴可以看一下这一篇文章 链接
      (注)如果有小伙伴想要了解为何Android中为什么主线程不会因为Looper.loop()里的死循环阻塞可以看一下这一篇文章 链接

    4. msg.target.dispatchMessage(msg);
      msgtarget属性是Handler对象,从而就是调用Handler的dispatchMessage方法将当前要处理的msg分发出去。(注1:下文中会说明为什么msg的target属性是Handler)

    到此Looper的所有工作就已经结束了,然后我们看一下dispatchMessage()是如何分发消息的

    public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    
         /**
         * Subclasses must implement this to receive messages.
         */
        public void handleMessage(Message msg) {
        }
    
        private static void handleCallback(Message message) {
            message.callback.run();
        }
    

    1.dispatchMessage方法中首先会判断msg.callback存不存在,msg.callbackRunnable类型,只有通过上文demo中Handler + Runnable的方式实现msg.callback才会存在,并回调run()方法

    2.如果是以上文demo中通过Handler + Message的方式实现,则在dispatchMessage(Message msg) 方法中会调用handleMessage(msg)方法; 源码中handleMessage方法是一个空方法,而在我们最开始的demo中我们重载了这方法

        private static class UIHandler extends Handler {
            WeakReference<MainActivity> mWeakRef;
    
            public UIHandler(MainActivity ref) {
                mWeakRef = new WeakReference<MainActivity>(ref);
            }
    
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                MainActivity activity = mWeakRef.get();
                if (activity == null || activity.isFinishing()) {
                    return;
                }
                switch (msg.what) {
                    case NET_SUCCESS:
                        String tip = (String)msg.obj;
                        Toast.makeText(activity, tip, Toast.LENGTH_SHORT).show();
                        activity.mDownload.setVisibility(View.GONE);
                        break;
                    default:
                        break;
                }
            }
        }
    

    到此handler的处理message的过程就结束了:
    Handler + Message的方式实现成功回调通知到我们重载的handleMessage方法中;
    Handler + Runnable的方式实现则成功回调Runnablerun方法;

    Handler除了分发的过程之外,还有一个发送message到MessageQueue中的过程,下面是我们之前demo中的一段代码

         /**
         * 模拟异步下载文件
         */
        private void ToDownloadFile(){
            //开启一个子线程
            Runnable mDownloadTask = new Runnable() {
                @Override
                public void run() {
                    try{
                        Thread.sleep(5000);//线程休眠5秒模拟下载操作
                        Message msg = mHandler.obtainMessage(NET_SUCCESS);
                        msg.obj = "从下载中获取的数据";
                        mHandler.sendMessage(msg);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            mDownloadTask.run();
        }
    
       private void ToDownloadFile(){
              new Handler().postDelayed(new Runnable(){
                   @Override
                   public void run() {
                        mDownload.setVisibility(View.GONE);
                   }
              }, 5000);
          }
    

    从代码中我们看到,
    1.我们创建了一个message对象,通过mHandler对象的sendMessage方法将message传递到Handler中;
    2.我们创建了一个Handler对象,调用了postDelayed方法,传入了创建的Runnable对象
    下面看一下源码中sendMessage()postDelayed()是如何发送消息的

       //以Message方式实现调用sendMessage方法
       public final boolean sendMessage(Message msg) {
            return sendMessageDelayed(msg, 0);
        }
    
       public final boolean sendMessageDelayed(Message msg, long delayMillis) {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
        //以Runnable 方式实现调用postDelayed方法
        public final boolean postDelayed(Runnable r, long delayMillis)  {
            return sendMessageDelayed(getPostMessage(r), delayMillis);
        }
       //通过getPostMessage(r)来将Runnable对象来转为Message
       private static Message getPostMessage(Runnable r) {
            Message m = Message.obtain();
            m.callback = r;
            return m;
        }
    
        public final boolean sendMessageDelayed(Message msg, long delayMillis) {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
        //最后其实都是调用sendMessageAtTime方法
        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);
        }
    
        private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;//将当前handler保存到msg.target中
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            //将Message绑定到MessageQueue中
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    1.共同点是:从Handler源码中我们可以了解到,无论是上面说的Handler+Message方式还是Handler+Runnable方式发送消息,最终都会调到sendMessageAtTime方法。

    2.区别不过是:post方法的底层调用sendMessageDelayed的时候,却是通过getPostMessage(r)来将Runnable对象来转为Message

    3.最后都通过enqueueMessage方法将Message绑定到MessageQueue中;并且将当前handler保存到msg.target

    4.这也解释了上文Looper类 注释1中为什么msg.target是handler的情况

    接着我们再看一下MessageQueue是如何添加Message的

    MessageQueue源码链接

        boolean enqueueMessage(Message msg, long when) {
            synchronized (this) { //同步锁
                // 消息队列正在退出,回收 Message
                if (mQuitting) {
                    msg.recycle();
                    return false;
                }
                //将消息添加到MessageQueue的具体操作
                //每来一个新的消息,就会按照延迟时间的先后重新进行排序
                msg.markInUse();  // 标记 Message 正在使用
                msg.when = when;  // 设置 Message 的触发时间
                Message p = mMessages;// mMessages 记录着 MessageQueue 的队头的消息 
                boolean needWake;
                // MessageQueue 没有消息、Message 触发时间为 0、Messgae 触发时间比队头 Message 早
                // 总之这个 Message 在 MessageQueue 中需要最先被分发
                if (p == null || when == 0 || when < p.when) {
                    // New head, wake up the event queue if blocked.
                    msg.next = p;     // 将以前的队头 Message 链接在这个 Message 后面
                    mMessages = msg;  // 将这个 Message 赋值给 mMessages
                    needWake = mBlocked;  // 队列是否阻塞
                } else {
                    // 标记队列是否阻塞
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                   // 按照时间顺序将 Message 插入消息队列
                   for (;;) {
                       prev = p;   // prev 记录队头
                       p = p.next; // p 记录队头的后一个
                       // 队头后面没有消息或者其触发事件比要插入的 Message 晚,跳出循环
                       if (p == null || when < p.when) {
                           break;
                       }
                      if (needWake && p.isAsynchronous()) {
                          needWake = false;
                      }
                   }
                   // 将 Message 插入队列
                   msg.next = p; 
                   prev.next = msg;
                }
                //如果Looper.loop()是休眠状态
                //则调用native方法唤醒loop()
                //---重点---Looper的唤醒
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    

    整个enqueueMessage方法的过程就是先持有MessageQueue.this锁,然后将Message放入队列中,放入队列的过程是:

    1. 如果队列为空,或者当前处理的时间点为0(when的数值,when表示Message将要执行的时间点),或者当前Message需要处理的时间点先于队列中的首节点,那么就将Message放入队列首部,否则进行第2步。

    2. 遍历队列中Message,找到when比当前Messagewhen大的Message,将Message插入到该Message之前,如果没找到则将Message插入到队列最后。

    3. 判断是否需要唤醒loop(),当loop()处于休眠情况下,则调用nativeWake(mPtr)方法,会调用native层方法唤醒loop()

    4. 执行完后,会释放持有的MessageQueue.this的锁。这样整个enqueueMessage方法算是完了。

    4.到此整个Handler+Message+MessageQueue+Looper的源码已经全部讲完了

    总结一下原理,整个过程就好比是一个工厂的流水线,MessageQueue是一条传送带,Message是个货物,传送带的动力是Looper,Handler负责从子线程将Message放到传送带上,并且在传送带终点主线程中将货物取走。

    备注:在本文中并未对native中以下两点进行说明:
    1.MessageQueuenativeWake(mPtr)方法是如何唤醒Looper
    2.以及上文提到的Looper类中queue.next()方法为空时是如何休眠loop()
    这两点设计到了native层的原理,以下两个链接会对这两点进行详细的说明,有兴趣的同学可以了解一下

    https://www.2cto.com/kf/201610/556770.html
    https://www.jianshu.com/p/4fac27284475

    相关文章

      网友评论

          本文标题:Handler机制用法及源码解析

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