阅读 576

Android 结合源码全面深入剖析Handler机制原理

前言

在写这篇文章之前,先祝大家中秋佳节快乐,在这三天小假里,祝大家玩的开心!好了接下来狗哥给大家分析一下Handler的机制原理,这也算是我闲闲的三天假的一个补偿吧。其实Handler机制原理已经被许多大佬写透的东西了,这里我们为什么还要说呢?因为对于像我这种的菜狗来说,也只是会使用Handler而已,对于其机制,也只是大概知道,今天我们来结合源码深入剖析其原理。

Handler介绍

我们都知道,Android 中常用的网络请求都是在子线程中进行的,而更新UI的操作都是在主线程中进行的,说到这,猿猿们肯定会吐槽了,那这跟你写的Handler有啥关系啊。狗哥在这给你们解释一下: 从 Android 4.0 开始,Android 中就强制不允许在主线程中进行网络请求,也不允许在子线程中更新UI,所以Handler机制应运而生,作为一套线程间通信的机制,只不过经常被我们用于更新UI。

机制原理

首先介绍其原理之前,先给大家分析一下handler的四大类

 Handler         负责发送消息及其处理消息(发送者和处理者)
 Message         系统传递的消息
 MessageQueue    消息队列,负责存储消息
 Looper          消息泵,不断从消息队列中取出消息并且发送给持有本条消息的Handler
 
介绍完handler的四大类,我们简单理解一下他的机制原理 :
Handler发送一个 Message 到 MessageQueue ,这时 handler 是充当发送者对象然后通过消息泵 Looper ,不断从消息队列
中取出消息,然后再通过 handler 处理消息,这时的 handelr 是充当的一个处理者的形象。
复制代码

源码分析

来我们首先看一下 handler 类的初始化操作

public class Handler {
    final MessageQueue mQueue;  // 关联的MessageQueue
    final Looper mLooper;  // 关联的looper
    final Callback mCallback; 
  
    public Handler() {
        // 默认关联当前线程的looper
        mLooper = Looper.myLooper();
        // looper不能为空,即默认的构造方法只能在looper线程中使用
        if (mLooper == null) {
            throw new RuntimeException(
                //无法在未调用loop .prepare()的线程内创建处理程序。
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        // 直接把关联looper的MessageQueue作为自己的MessageQueue,
        // 因此它的消息将发送到关联looper的MessageQueue上
        mQueue = mLooper.mQueue;
        mCallback = null;
    }
}
复制代码

我们通过源码发现,初始化中handler 关联了 messageQueue 及其 looper 对象。

我们通常会这样创建handler,在handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper。这也就是我们经常创建 handler 对象的两种方式。

    Handler handler = new Handler(Looper.getMainLooper());
    Handler handler = new Handler();
复制代码

handler关联了 looper 对象和 messagequeue 对象,才能完成调度工作,而在整个调度工作中,looper 对象才是真正的核心,也就相当于整个团队的leader 了,下面我们仔细、认真的分析一下 looper 的源码。

Looper.getMainLooper(),我们发现handler关联当前线程 looper 的入口其实是在 ActivityThread 的 main() 方法中进行的。而在 main() 方法中它还做了另外一件重要的事情。

public static void main(String[] args) {
   
        //主线程会创建一个Looper对象。
        Looper.prepareMainLooper();   
        
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        //执行消息循环
        Looper.loop();
}

复制代码

没错就是执行消息循环 Looper.loop(); 。 总结 main()中做的两件事 :

    1、Looper.prepareMainLooper(); //主线程会创建一个Looper对象。 
    2、Looper.loop();  //执行消息循环
复制代码

接下来我们逐个分析:

1、Looper.prepareMainLooper();

public static void prepareMainLooper() {
    //在主线程中,其默认初始化一个Looper对象,因此我们在主线程的操作中是不需要自己去调prepare()。
    prepare(false);
    synchronized (Looper.class) {
        //这里先进行判断,在主线程是否已经存在Looper了,
        // 避免我们手动去调用prepareMainLooper(),因为这个是给程序入口初始化的时候系统会自动调用的
        if (sMainLooper != null) {
            //看抛出的异常,初始化looper已经被准备好了
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //设置全局变量,主线程的looper
        sMainLooper = myLooper();
    }
}
复制代码

注意这个函数的注释,大概意思是:在主线程创建一个looper,是这个主线程的主looper,当这个app在初始化的时候就会自行创建,因此这个函数不是给你们调用的,是给系统自身在程序创建的时候调用的。

继续往下看,有个prepare(boolean)函数,我们去看看这个到底是用来干什么的。

private static void prepare(boolean quitAllowed) {
    //先判断当前线程是否已经存在Looper了,如果存在,不允许设置新的Looper对象,一个线程只允许存在一个Looper
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //在当前线程中,创建新的Looper对象,并绑定当前线程
    sThreadLocal.set(new Looper(quitAllowed));
}
复制代码

我们看到了sThreadLocal,我们先看看这个sThreadLocal在Looper是干什么用的。

//sThreadLocal在Looper中作为全局变量,用于保存每个线程中的数据,可以看做是容器
public static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
复制代码

Looper中,sThreadLocal作为一个全局变量,sThreadLocal其实是保存Looper的一个容器,我们继续往ThreadLocal的get、set进行分析。

public T get() {
    //获取当前线程保存的对象--通过get函数来获取Looper对象
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T) e.value;
    }
    return setInitialValue();
}
 
public void set(T value) {
    //把当前的looper保存到当前线程中
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
复制代码

关键的代码:

    Thread t=Thread.currentThread();
复制代码

也就是说,我们的Looper对象分别保存在相对应的线程中。我们再回来看刚才前面 的prepare(boolean)函数:

private static void prepare(boolean quitAllowed) {
    //先判断当前线程是否已经存在Looper了,如果存在,不允许设置新的Looper对象,一个线程只允许存在一个Looper
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //在当前线程中,创建新的Looper对象,并绑定当前线程
    sThreadLocal.set(new Looper(quitAllowed));
}
复制代码

Looper.prepare()函数总结:

    Looper.prepare(boolean)的作用就是创建一个Looper对象,并与当前线程绑定在一起。 
    在代码中,首先判断当前线程是否已经存在looper,如果不存在则创建新的looper并且绑定到当前的线程上。
复制代码

然后我们再会看之前 prepareMainLooper() 的代码:

public static void prepareMainLooper() {
    //在主线程中,其默认初始化一个Looper对象,因此我们在主线程的操作中是不需要自己去调prepare()。
    prepare(false);
    synchronized (Looper.class) {
        //这里先进行判断,在主线程是否已经存在Looper了,
        // 避免我们手动去调用prepareMainLooper(),因为这个是给程序入口初始化的时候系统会自动调用的
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //设置全局变量,主线程的looper
        sMainLooper = myLooper();
    }
}
复制代码

那么我们看到sMainLooper是什么,myLooper()又是什么呢?

//保存一个主线程的looper
private static Looper sMainLooper;  // guarded by Looper.class
 
public static Looper myLooper() {
    //使用当前线程的looper
    return sThreadLocal.get();
}
复制代码

总结:

    sMainLooper在Looper做为一个全局变量,保存主线程绑定的looper,myLooper()则是获取当前线程绑定的Looper。
 在prepareMainLooper()中,在主线程中创建一个新的Looper,并且绑定主线程中,同时把这个主线程的looper赋值给
 sMainLooer这个全局变量。
复制代码

刚才讲到 handler 再 AcitvityThread 中的 main() 方法中主要做了两件事,刚才我们分析了第一件事,也就是 Looper.prepareMainLooper();

接下来我们讲他做的第二件事 :Looper.loop();

补脑一下子,我们都把前期工作给做好了,怎样才能让让 Handler 从 MessageQueue 中获取到 Message 进行处理呢 ?没错就是通过 Looper.loop(); 让整个消息队列动起来。来我们分析一下 loop() 方法中的源码:

/**
 * 调用此函数用于启动消息队列循环起来,作用相当于一个团队的leader下达的命令,
 * 只有leader说今天加班,你今天就必须加班😂
 * Run the message queue in this thread. Be sure to call
 */
public static void loop() {
    //先进行判断当前线程是否有绑定looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //获取这个looper的消息队列
    final MessageQueue queue = me.mQueue;
 
    //确保这个线程的标识是本地进程的标识,
    //并跟踪标识符实际上是什么。
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
 
    //循环通过消息队列来获取消息
    for (; ; ) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            //没有消息退出消息队列。
            return;
        }
 
        // 这必须在本地变量中,以防UI事件设置日志记录器
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
 
        final long traceTag = me.mTraceTag;
        if (traceTag != 0) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            //关键点,这里的msg.target也就是hanlder.看回代码hanlder.enqueueMessage()
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
 
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
 
        //最后回收这个message
        msg.recycleUnchecked();
    }
}
复制代码

这一段代码比较长,我们逐个注释,发现它是先判断当前的线程是否存在looper,如果存在获取保存在Looper的消息队列messagequeue,然后无限循环这个消息队列来获取message,下面这句代码是比较重要的:

    //关键点,这里的msg.target也就是hanlder.看回代码hanlder.enqueueMessage()
    msg.target.dispatchMessage(msg);
复制代码

分析:

    msg.target其实就是我们的handler,无论是handler通过post或者sendEmptyMessage,最终都会调用到调到这个
    enqueueMessage(),在这里会将handler赋值到msg.target中。
复制代码

好,那我们再继续分析这个 enqueueMessage() 函数

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //在message中放一个标记
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //在这里把消息放到队列里面去
    return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码

既然 Looper 中的 loop() 调用了 msg.target.dispatchMessage,我们就看看 Handler的dispatchMessage 是如何进行处理这个msg的。

注意: dispatchMessage() 分发函数是 Handler 作为处理者的身份进行的,自然这个函数也就在 Handler 类中了。

来我们上源码继续逐个分析:

public void dispatchMessage(Message msg) {
    //这里先判断callback是否为空
    // callback就是我们使用handler.post(Runnable r)的入参runnable
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        //如果hanlder的入参callback不为空,优先处理
        if (mCallback != null) {
            //如果回调返回true.则拦截了handler.handleMessage的方法
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //这就是为什么我们使用hanlder的时候,需要重写handleMessage的方法
        handleMessage(msg);
    }
}
复制代码

分析:

在dispatchMessage函数中,意思就是分发这个消息,在代码中先判断msg.callback是否为空,msg.callback是什么?其实
就是handler.post中的runnable对象,通俗的来说就是handler如果有post操作的,就处理post的操作。
复制代码

我们在看看 handlerCallback这个函数。

private static void handleCallback(Message message) {
    message.callback.run();
}
复制代码

很简单,就一行代码,我们看到了熟悉的run方法,这个不就是我们使用post的时候传进去的Runnbale对象的run方法吗? 来,我们模拟一段代码:

/**
 * 模拟开始
 */
private void doSometh() {
    //开启个线程,处理复杂的业务业务
    new Thread(new Runnable() {
        @Override
        public void run() {
            //模拟很复杂的业务,需要1000ms进行操作的业务
            ......
            handler.post(new Runnable() {
                @Override
                public void run() {
                    //在这里可以更新ui
                    mTv.setText("在这个点我更新了:" + System.currentTimeMillis());
                }
            });
        }
    }).start();
}
复制代码

写到这里或许大佬们会有个疑问,Handler 的 post 方法创建的线程和UI线程有什么关系?来我们还是继续分析源码解释:

public final boolean post(Runnable r)  {  
      return sendMessageDelayed(getPostMessage(r), 0);  
}  
复制代码
private static Message getPostMessage(Runnable r) {  
      Message m = Message.obtain();  
      m.callback = r;  
      return m;  
}  
复制代码

可以看到,在getPostMessage中,得到了一个 Message 对象,然后将我们创建的Runable对象作为 callback 属性,赋值给了当前 message。我们继续回到 handler.dispatchMessage(Message) 中,如果不是通过 post 那么 callback 就为空,我们看到了一个 mCallback变量,我们看看这个Callback的定义:

public interface Callback {
    public boolean handleMessage(Message msg);
}
public Handler(Callback callback) {
    this(callback, false);
}
复制代码

我们可以通过实现这个接口,并作为一个参数传进去 Handler 来达到处理这个消息的效果。 我们再回到之前的 dispatchMessage() 函数中理解一下刚才的思路:

public void dispatchMessage(Message msg) {
    //这里先判断callback是否为空
    // callback就是我们使用handler.post(Runnable r)的入参runnable
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        //如果hanlder的入参callback不为空,优先处理
        if (mCallback != null) {
            //如果回调返回true.则拦截了handler.handleMessage的方法
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //这就是为什么我们使用hanlder的时候,需要重写handleMessage的方法
        handleMessage(msg);
    }
}
复制代码

分析:最后一行代码中,我们看到了熟悉的handleMessage,这不就是我们经常 handler.handlerMessage 的方法吗?

但注意之前我们所看到的,如果我们mCallback.handlerMessage(msg)返回为true的话,这样就不交给handler.handleMessage处理了。这里我们再 看看 handleMessage() 函数 :

  public void handleMessage(Message msg) {  
  
  } 
复制代码

在这里我们发现了这个方法是空的,为什们呢 ?这里补充一句。因为消息的最终回调是由我们控制的,我们在创建handler的时候都是复写handleMessage方法,然后根据msg.what进行消息处理。产生一个Message对象,可以new ,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。

我们继续看回来我们的 Looper.loop() :

public static void loop() {
    .....
    .....
    //循环通过消息队列来获取消息
    for (; ; ) {
        ......
        //最后回收这个message
        msg.recycleUnchecked();
    }
}
复制代码

在无限循环每个消息的时候,除了调用 handler.dispatchMessage,最后还会调用 msg.recycleUnchecked() 进行回收这个消息。

机制原理总结

1.为什么在主线程中创建Handler不需要我们调用 Looper.prepare().因为在程序的入口中系统会调用Looper.prepareMain
  Looper()来创建,并且让其主线程的Looper启动起来。如果我们在子线程创建 handler,需要手动创建 looper 并且启动。

2.每一个线程只能存在一个 Looper, Looper有一个全局变量 sThreadLocal 用来保存每一个线程的looper,通过 get、set 进行
  存取 looper。

3.Handler 可以通过通过 post 或者sendMessage进行发送消息,因为其最终会调用 sendMessageDelayed,我们可以通过
  runnable 方式或者重写handleMessage进行消息的处理,当然如果通过 handler.sendMessage(msg) 的方式的话,我们
  可以实现Callback接口达到消息的处理。

4.为什么不能在子线程更新UI?其实更准确的来说应该是UI只能在创建UI的线程中进行更新,也就是主线程,如果子线程创建UI,其可以在子线程进行更新。
复制代码

Handler用法总结:

转自 :www.cnblogs.com/devinzhang/…

方法一:Thread

new Thread( new Runnable() {     
    public void run() {     
         myView.invalidate();    
     }            
}).start();
复制代码

分析: 可以实现功能,刷新UI界面。但是这样是不合理的,因为它违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中执行。

方法二 :Thread + Handler

Handler myHandler = new Handler() {  
          public void handleMessage(Message msg) {   
               switch (msg.what) {   
                    case 0:   
                         myView.invalidate();  
                         break;   
               }   
               super.handleMessage(msg);   
          }   
};  
复制代码
class MyThread implements Runnable {   
          public void run() {  
               while (!Thread.currentThread().isInterrupted()) {    
                       
                    Message message = new Message();   
                    message.what = 0;   
                      
                    myHandler.sendMessage(message);   
                    try {   
                         Thread.sleep(100);    
                    } catch (InterruptedException e) {   
                         Thread.currentThread().interrupt();   
                    }   
               }   
          }   
     }   
复制代码

Handler来根据接收的消息,处理UI更新。Thread线程发出Handler消息,通知更新UI。

方法三:TimerTask + Handler 实现 Timer 功能

public class TestTimer extends Activity {  
  
    Timer timer = new Timer();  
    Handler handler = new Handler(){   
        public void handleMessage(Message msg) {  
            switch (msg.what) {      
            case 1:      
                setTitle("狗哥最帅");  
                break;      
            }      
            super.handleMessage(msg);  
        }  
          
    };  

    TimerTask task = new TimerTask(){    
        public void run() {  
            Message message = new Message();      
            message.what = 1;      
            handler.sendMessage(message);    
        }            
    };  

    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
      
        timer.schedule(task, 10000);  
    }  
}  
复制代码

方法四:Runnable + Handler.postDelayed(runnable,time)

private Handler handler = new Handler();  
  
    private Runnable myRunnable= new Runnable() {    
        public void run() {  
             
            if (run) {  
                handler.postDelayed(this, 1000);  
                count++;  
            }  
            tvCounter.setText("Count: " + count);  

        }  
    }; 
复制代码

声明

这里面的内容有我去年初学的时候在 CDSD上写的一些,在这里我不得不吐槽一下,由于CSDN社区真的是各种广告太多了,看个文章还要付钱,受不了,所以转战掘金社区,我的csdn原文地址:blog.csdn.net/MrZhao_Perf… ,这篇也算是一个总结与补充吧。 OK了,Handler 机制原理通过源码的方式给大家说了个大概,如果大家有什么疑问或者可以留言给我。

关注下面的标签,发现更多相似文章
评论