Handler机制详解

Posted 一叶飘舟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler机制详解相关的知识,希望对你有一定的参考价值。

相关类

1. ThreadLocal

ThreadLocal存取的变量只能被当前线程访问,其他线程则无法访问和修改。

使用场景
数据以线程为作用域,不同的线程有不同的数据副本。
各个线程往***同一个***ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的
原理
  1. Thread中有变量ThreadLocalMap

  1. threadLocal调用set/get方法,实际是调用的各个线程的threadLocalMap.set/getEntry,并以该local实例为索引

虽然是在不同线程调用的local
但因为存取的map变量是在Thread内部,并且local是同一个
所以,能实现存储的变量属于当前线程,对其它线程隔离

使用

<!--#:ThreadLocal-->/**
* 实际上调用的是:各个线程中的threadLocalMap.set/getEntry
*/publicvoidset(T value) 
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);


ThreadLocalMap getMap(Thread t) 
    return t.threadLocals;

//Thead中的ThreadLocalMap是在ThreadLocal中被创建的voidcreateMap(Thread t, T firstValue) 
    t.threadLocals = new ThreadLocalMap(this, firstValue);


--------------------

<!--#:Thread-->
ThreadLocal.ThreadLocalMap threadLocals = null;//在ThreadLocal中被初始化//以local实例为索引privatevoidset(ThreadLocal<?> key, Object value) 
private Entry getEntry(ThreadLocal<?> key) 


/**
  虽然是在不同线程调用的local   
  但因为存取的map变量是在Thread内部,并且local是同一个    
  所以,能实现存储的变量属于当前线程,对其它线程隔离
*/
ThreadLocal<String> local = new ThreadLocal<>();
//#Thread$1
local.set("strValue");
String value = local.get();
//#Thread$2
local.set("strValue");
String value = local.get();

复制代码

2. HandlerThread

Android多线程:HandlerThread详细使用手册

Android多线程:一步步带你源码解析HandlerThread

原理: Handler机制 + Thread
  • 是Thread

  • 通过继承Thread类,快速地创建1个带有Looper对象的新工作线程

  • 通过新Handler使用该线程的Looper,在该Handler中处理任务,使工作任务在子线程中执行

HandlerThread实现
# HandlerThread:
@Override
public void run() 
    Looper.prepare();
    synchronized (this) 
        //这步才是最主要的,让处理耗时操作的workHandler使用mLooper,这样就能在当前的子线程中处理workHandler的耗时操作了。
        mLooper = Looper.myLooper();
        notifyAll();
    
    
    //空方法,loop前的准备onLooperPrepared();
    
    Looper.loop();

使用
protected void onCreate() 
    HandlerThread handlerThread = newHandlerThread("word_handler");
    handlerThread.start();
    //使用子线程的Looper,所以workHandler中的耗时操作在子线程中执行WorkHandler workHandler = newWorkHandler(handlerThread.getLooper());
    
    mainHandler = newMainHandler();
    
    Message msg = Message.obtain();
    msg.what = 1;
    workHandler.sendMessage(msg);//work发送消息


//和HandlerThread同线程,处理工作任务 
public classWorkHandlerextendsHandler
    @Override
    public void handleMessage(Message msg) 
        Message messag = Message.obtain();
        messag.obj = "from word Handler";
        mainHandler.sendMessage(messag);//通知主线程
     


classMainHandlerextendsHandler
    public void handleMessage(Message msg) 
        tvTest.setText((String)msg.obj);
    

封装HandlerThread
protected void initData() 
    mainHandler = newMainHandler();

    myHandlerThread = newWorkHandlerThread("my_handler_thread");
    myHandlerThread.start();


@OnClick(R.id.btn_start)
public void onViewClicked(View view)
    Message msg = Message.obtain();
    msg.what = 1;
    myHandlerThread.getWorkHandler().sendMessage(msg);


/**
 * 当前线程
 */classMainHandlerextendsHandler
    @Override
    public void handleMessage(Message msg) 
        tvTest.setText((String)msg.obj);
    


/**
 * 工作:处理耗时任务
 */classWorkHandlerThreadextendsMyHandlerThread
    public WorkHandlerThread(String name) super(name);

    @Override
    public void workMessage(Message msg) 
        Thread.sleep(2000);
        Message messag = Message.obtain();
        messag.obj = "from word My WordHandler";
        mainHandler.sendMessage(messag);
    


 /**
  * 封装基类
  */abstractclassMyHandlerThreadextendsHandlerThread
     privateWorkHandler mWorkHandler;

     @Overrideprotected void onLooperPrepared() 
         mWorkHandler = newWorkHandler(getLooper());
     

     public WorkHandler getWorkHandler()return mWorkHandler;
     public abstract void workMessage(Message msg);

     classWorkHandlerextendsHandler
         public WorkHandler(Looper looper) 
             super(looper);
         

         @Override
         public void handleMessage(Message msg) 
             super.handleMessage(msg);
             workMessage(msg);
         
     

 

IdleHandler

IdleHandler-干货集中营

你知道android的MessageQueue.IdleHandler吗?

IdleHandler,页面启动优化神器

是 Handler 机制提供的一种,可以在 Looper 事件循环的过程中,当出现空闲的时候,允许我们执行任务的一种机制
    Message next() 

        intpendingIdleHandlerCount= -1; // -1 only during first iterationintnextPollTimeoutMillis=0;
        for (;;) 
            synchronized (this) 
                finallongnow= SystemClock.uptimeMillis();
                MessageprevMsg=null;
                Messagemsg= mMessages;
                if (msg != null) 
                    if (now < msg.when) 
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                     else 
                        // Got a message.return msg;
                    
                 else 
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                


                // If first time idle, then get the number of idlers to run.// Idle handles only run if the queue is empty or if the first message// in the queue (possibly a barrier) is due to be handled in the future.//第一次时满足<0条件if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) 
                    pendingIdleHandlerCount = mIdleHandlers.size();
                
                //执行下面的for循环一次后,置为0,后面再到此处不再往下执行if (pendingIdleHandlerCount <= 0) 
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                
            

            // We only ever reach this code block during the first iteration.//只有第一次执行能到达for (inti=0; i < pendingIdleHandlerCount; i++) 
                finalIdleHandleridler= mPendingIdleHandlers[i];
                booleankeep=false;
                try 
                    keep = idler.queueIdle();
                 catch (Throwable t) 

                if (!keep) 
                    synchronized (this) 
                        mIdleHandlers.remove(idler);
                    
                
            

            // Reset the idle handler count to 0 so we do not run them again.//置为0,后面不再满足上面条件进入for循环
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered// so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        
    
  • 调用时机:消息队列empty或延迟消息还未执行

  • 如果queueIdle返回true,会一直有idleHandler,为什么不会导致空闲时next在for循环的过程中,一直重复执行

  • 在最后pendingIdleHandlerCount=0,不再满足条件

使用场景

IdleHandler,页面启动优化神器

  • 启动页面优化

  • onResume在wm.addView之前执行,可以将不必要的操作在页面绘制完成之后执行

  • 减少onResume中执行时间,达到页面启动优化

消息机制

Android Handler机制之总目录

Message中obtain()与recycle()的来龙去脉-系列

一、概述

什么是消息机制

android的消息机制主要是指Handler的运行机制

即:消息的发送、入队、出队、分发过程。

为什么需要消息机制

Android规定访问UI只能在主线程中进行,在子线程中访问UI就会抛异常。但是Android又不建议在主线程中做耗时操作,会可能导致ANR。所以,我们需要,能在子线程中做完耗时操作,然后去到主线程更新UI的办法。

Hander的主要作用是将一个任务切换到指定的线程中去执行。因此,系统提供Handler主要是***为了解决在子线程中无法访问UI的问题***(而不是把耗时操作放到子线程中的问题)。

单线程模型准则

在单线程模型中始终要记住两条法则:

1、不要阻塞UI线程

2、确保只在UI线程中访问UI

二、原理

分发机制:

  • Handler通过sendMessage()发送Message到MessageQueue队列;

  • Looper通过loop(),不断提取出达到触发条件的Message,并将Message交给target来处理;

  • 经过dispatchMessage()后,交回给Handler的handleMessage()或者runnable来进行相应地处理。

2.1 消息的发送、入队:

handler中有众多的send方法,时间点的区别而已,到最后都会调用下面的方法,把message放入消息队列

MessageQueue是链表结构

//消息发送
<!--#Handler-->
public boolean sendMessageAtTime(Message msg, long uptimeMillis) 
    MessageQueue queue = mQueue;
    return enqueueMessage(queue, msg, uptimeMillis);


private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) 
        //注意此处,this指的是handler自己
        msg.target = this;
        //把消息插入队列
        return queue.enqueueMessage(msg, uptimeMillis);
    

//入队
<!--#MessageQueue-->
boolean enqueueMessage(Message msg, long when) 
    synchronized (this) 
        msg.markInUse();msg.when = when;
        Message p = mMessages;
        boolean needWake;
        //如果队列为空或者时间最小,插入到头部
        if (p == null || when == 0 || when < p.when) 
            msg.next = p;mMessages = msg;needWake = mBlocked;
         else 
            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.nextprev.next = msg;
        
        if (needWake) 
            nativeWake(mPtr);
        
    
    return true;
 

2.2 消息的出队、分发:

loop将队列中的msg一个一个取出,分发到各自的handler中处理。

handler根据是否有callback选择不同的分发方式。

//出队
<!--#Looper-->
publicstaticvoidloop() 
    finalLooperme= myLooper();
    finalMessageQueuequeue= me.mQueue;
    //真正决定阻塞的是queue.next()中的for循环for (;;) 
        //next方法会阻塞线程,有消息就取出,没消息就等待。退出返回null结束当前循环。Messagemsg= queue.next(); // might blockif (msg == null) 
            // No message indicates that the message queue is quitting.//Looper.quit退出循环return;
        
        //加入消息队列时,target == handler,分发的时候就可以找到原来的handler,让其自己处理。//所以,一个线程多个handler发送消息,虽然都在一个队列里,但是还是会分发到原来的handler处理消息。
        msg.target.dispatchMessage(msg);
    


//取出消息
<!--#MessageQueue-->
Message next() 
    //阻塞线程for (;;) 
        synchronized (this) 
            // Try to retrieve the next message.  Return if found.finallongnow= SystemClock.uptimeMillis();
            MessageprevMsg=null;
            Messagemsg= mMessages;
            if (msg != null && msg.target == null) 
                //异步消息?do 
                    prevMsg = msg;
                    msg = msg.next;
                 while (msg != null && !msg.isAsynchronous());
            
            if (msg != null) 
                //还不到取出时间if (now < msg.when) 
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                 else 
                    // 取出msg
                    mBlocked = false;
                    if (prevMsg != null) 
                        prevMsg.next = msg.next;
                     else 
                        mMessages = msg.next;
                    
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                
             else 
                // No more messages.
                nextPollTimeoutMillis = -1;
            
            
            //Looper.quit(),返回null。//此时,Looper中的for循环收到null也return,退出循环if (mQuitting) 
                dispose();
                returnnull;
            
        
        


    
//由target返回到Handler中执行分发//加入队列的:有的是Runnable封装的message,有的是callback,有的是message,在这里分发   
<!--#Handler-->
publicvoiddispatchMessage(Message msg) 
    //runnable封装的message。 handler.post/view.postif (msg.callback != null) //msg中的callback 优先级1
        handleCallback(msg);
     else 
        if (mCallback != null) 
            //Handler(Callback callback)的时候,mCallback !=nullif (mCallback.handleMessage(msg)) //handler中的callback,优先级2return;
            
        
        //正常的message那种形式,也即是new Handler时复写的方法。
        handleMessage(msg);// 优先级3
    

复制代码

三、其它过程

3.1 子线程中初始化Handler

<!--#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));


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


<!--#Handler-->
public Handler(Callback callback, boolean async) 
    //验证当前线程有没有Looper对象,所以new之前要prepare存入
    mLooper = Looper.myLooper();
    if (mLooper == null) 
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    

    
<!--#:Thread1-->
Looper.prepare()//当前线程存
Handler handler = new Handler();//当前线程取
Looper.loop();

<!--#:Thread2-->
Looper.prepare()//当前线程存
Handler handler = new Handler();//当前线程取
Looper.loop();

<!--#:Thread3-->
Looper.prepare()//当前线程存
Handler handler = new Handler();//当前线程取
Looper.loop();
主线程 Handler

为什么主线程 new Handler之前不用prepare,也不用loop?

UI线程是主线程,系统已经自动帮我们调用了Looper.prepare()和Looper.loop()方法

3.2 优先级:依次降低。

优先级1:

<!--封装成Message,放入队列-->
//view.post(runnable)和handler.post(runnable)     
//最后runnable都会分装成Message。分发的时候,在第一处调用。
private static Message getPostMessage(Runnable r) 
    Message m = Message.obtain();
    m.callback = r;
    return m;

    
<!--分发调用-->
private static void handleCallback(Message message) 
    message.callback.run();


注意:虽然是runnable,但并不是新的线程。
此处的run()就是正常的方法,Runnable中不要做耗时操作。

优先级2:

publicHandler(Callback callback, boolean async)
    ...
    mCallback = callback;
    ...

优先级3:

常用的发送Message方式,设置what,obj参数,发送的消息。

3.3 退出

  • loop.quit() --> mQueue.quit(false)

  • loop.quitSafely() --> mQueue.quit(true)

<!--#MessageQueue-->
voidquit(boolean safe) 
    //主线程时,quit报错if (!mQuitAllowed) 
        thrownewIllegalStateException("Main thread not allowed to quit.");
    

    synchronized (this) 
        //mQuitting该标记使next方法中返回null,使loop结束循环。
        mQuitting = true;

        if (safe) 
            removeAllFutureMessagesLocked();
         else 
            removeAllMessagesLocked();
        
    


Message next() 
    //MessageQueue的next返回null,就能结束loop循环了if (mQuitting) 
         dispose();
         returnnull;
    


publicstaticvoidloop() 
    finalMessageQueuequeue= me.mQueue;
    for (;;) 
        Messagemsg= queue.next();
        /*
        * 不是指消息队列里没有消息了(没有消息,队列也会一直循环)
        * 是调用了Loop的quit()、quitSafely()方法,结束循环
        */if (msg == null) 
            //退出循环return;
        
        ...
    

安全退出
<!--#MessageQueue-->
void quit(boolean safe) 
    if (safe) 
        removeAllFutureMessagesLocked();
     else 
        removeAllMessagesLocked();
    


//回收所有消息
private void removeAllMessagesLocked() 
    Message p = mMessages;
    while (p != null) 
        Message n = p.next;
        p.recycleUnchecked();p = n;
    
    mMessages = null;


//回收延迟消息
private void removeAllFutureMessagesLocked() 
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) 
        if (p.when > now) 
            removeAllMessagesLocked();
         else 
            Message n;
            for (;;) n = p.next;
                if (n == null) 
                    return;
                
                if (n.when > now) 
                    break;
                
                //找到 <= now的msg ==> p
                p = n;
            
            //把p以后的消息回收
            p.next = null;
            do 
                p = n;n = p.next;
                p.recycleUnchecked();
             while (n != null);
        
    

quit()方法的作用:
1_标记mQuitting = true,使next方法返回null,结束循环。
2_移除消息队列中的消息。

3.4 主线程quit报错:

源码中可以看到,子线程中preaper时候,默认设置为true。

主线程中:prepareMainLooper的时候,默认是false。

所以,子线程的Loop是可以quit的,而主线程的不可以。

//app入口 ActivityThreadpublicstaticvoidmain(String[] args)   
    ...
    Looper.prepareMainLooper();  
    ActivityThread thread = newActivityThread();  
    ...
    Looper.loop();  


publicstaticvoidprepareMainLooper() 
    prepare(false);
    ...

//初始化MessageQueue时设置MessageQueue(boolean quitAllowed) 
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();


voidquit(boolean safe) 
    //这个地方:主线程时,quit报错if (!mQuitAllowed) 
        thrownewIllegalStateException("Main thread not allowed to quit.");
    
    ...

3.5 runOnUiThread

public final void runOnUiThread(Runnable action)
        if (Thread.currentThread() != mUiThread) 
            //非UI线程,到主线程的handler执行
            mHandler.post(action);
         else 
            //UI线程直接run
            action.run();
        
    

3.6 内存泄露

Handler内部类引用Activity,msg.target = this引用handler,造成内存泄漏

  • 1.退出时,remove掉handler(onStop中移除,onDestory不一定会调用)

  • 2.静态内部类,弱引用

/**
 为避免handler造成的内存泄漏
 1、使用静态的handler,对外部类不保持对象的引用
 2、但Handler需要与Activity通信,所以需要增加一个对Activity的弱引用
*/
private static class MyHandler extends Handler 
  private final WeakReference<Activity> mActivityReference;    

  MyHandler(Activity activity) 
      this.mActivityReference = new WeakReference<Activity>(activity);
  

  @Override
  public void handleMessage(Message msg) 
  super.handleMessage(msg);
  MainActivity activity = (MainActivity) mActivityReference.get();  //获取弱引用队列中的activity
  //若引用,判断null
  if(activity != null && !activity.isFinish())
      byte[] data = (byte[]) msg.obj;
      activity.threadIv.setImageBitmap(activity.getBitmap(data));
      break;   
  

3.7 loop()为什么不会阻塞主线程造成ANR?

Android中为什么主线程不会因为Looper.loop()里的死循环卡死

  • 首先,loop()会导致线程阻塞

class LooperThread extends Thread  
    public Handler mHandler; 
    public void run()  
       Looper.prepare(); 
       mHandler = new Handler();
       Looper.loop(); 
       Log.d("loop()是死循环,阻塞线程,后面的代码不执行");
     
 
  • 其次,主线程确实是阻塞的
    没有loop的话,程序启动,执行完代码就结束APP就退出了

#ActivityThread:
public static void main(String[] args)   
    ...
    Looper.prepareMainLooper();  #1
    ActivityThread thread = new ActivityThread(); #2
    thread.attach(false);  
    if (sMainThreadHandler == null)   
        #3sMainThreadHandler = thread.getHandler();  
      
    #4
    Looper.loop();  
    throw new RuntimeException("Main thread loop unexpectedly exited");  
  
  • 不会因为loop引起ANR
    因为在loop处理消息的过程中,进行耗时操作才会引起ANR。

Android的各个生命周期和事件,都是在loop循环中接收、处理消息的。引起ANR是因为处理消息时阻塞了,而不是因为loop循环阻塞的。

主线程的死循环一直运行是不是特别消耗CPU资源呢?

其实不然,这里就涉及到Linux pipe/epoll机制;

简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,详情见Android消息机制1-Handler(Java层),此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。

这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。

所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

3.8 Handler的异步理解

  • Handler没有处理耗时操作的能力

  • Handler的异步只是体现在,在其它线程操作完以后发送msg,在handler线程处理收到消息后的任务。

3.9 View.post(runnable)

#HandlerActionQueue:
public boolean post(Runnable action) 
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) 
        //如果当前View加入到了window中,直接调用UI线程的Handler发送消息
        return attachInfo.mHandler.post(action);
    

    //View未加入到window,放入HandlerActionQueue的mActions(数组)中
    getRunQueue().post(action);
    return true;


#View:
void dispatchAttachedToWindow(AttachInfo info, int visibility) 
    //View加入window后,直接执行mActions保存的
    mRunQueue.executeActions(info.mHandler);
  • View加入window之前post:

  • ViewRootImpl.getRunQueue().post(action)

  • getRunQueue().post(action);

  • 7.0前后都有改动,实现runnable方式不同,不细看

  • View加入window之后post:直接用主线程handler发送消息

3.10 子线程一定不能更新UI吗?

Android子线程更新UI就会Crash么

  • 有些控件支持在子线程更新,比如:SurfaceViw

  • 在Activity的onResume()生命周期函数之前是可以在子线程中更新UI的

  • ViewRootImpl还未创建

  • 更新时不会checkThread

3.11 Android实现异步的方式

  • 继承Thread类

  • HandlerThread

  • IntentService

  • AsyncTask

  • 线程池

注意事项

  • 主线程创建Handler不用prepare,是因为系统创建主线程时已调用

  • Handler没有处理耗时操作的能力,只是能在处理以后通知主线程。

以上是关于Handler机制详解的主要内容,如果未能解决你的问题,请参考以下文章

Android Handler机制

Android之Handler消息传递机制详解zz

Android之Handler消息传递机制详解zz

Android Handler和他的小伙伴们,消息机制详解

Android的消息机制Handler详解

Android的消息机制Handler详解