Handler机制详解

1,630 阅读13分钟

相关类

1. ThreadLocal

image

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

使用场景

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

原理
  1. Thread中有变量ThreadLocalMap
  2. threadLocal调用set/get方法,实际是调用的各个线程的threadLocalMap.set/getEntry,并以该local实例为索引

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

使用

<!--#:ThreadLocal-->
/**
* 实际上调用的是:各个线程中的threadLocalMap.set/getEntry
*/
public void set(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中被创建的
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

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

<!--#:Thread-->
ThreadLocal.ThreadLocalMap threadLocals = null;//在ThreadLocal中被初始化
//以local实例为索引
private void set(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 = new HandlerThread("word_handler");
    handlerThread.start();
    //使用子线程的Looper,所以workHandler中的耗时操作在子线程中执行
    WorkHandler workHandler = new WorkHandler(handlerThread.getLooper());
    
    mainHandler = new MainHandler();
    
    Message msg = Message.obtain();
    msg.what = 1;
    workHandler.sendMessage(msg);//work发送消息
}

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

class MainHandler extends Handler{
    public void handleMessage(Message msg) {
        tvTest.setText((String)msg.obj);
    }
}
封装HandlerThread
protected void initData() {
    mainHandler = new MainHandler();

    myHandlerThread = new WorkHandlerThread("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);
}

/**
 * 当前线程
 */
class MainHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        tvTest.setText((String)msg.obj);
    }
}

/**
 * 工作:处理耗时任务
 */
class WorkHandlerThread extends MyHandlerThread{
    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);
    }
}

 /**
  * 封装基类
  */
 abstract class MyHandlerThread extends HandlerThread{
     private WorkHandler mWorkHandler;

     @Override
     protected void onLooperPrepared() {
         mWorkHandler = new WorkHandler(getLooper());
     }

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

     class WorkHandler extends Handler{
         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() {

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = 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 (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                boolean keep = 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.next
            prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
} 

2.2 消息的出队、分发:

loop将队列中的msg一个一个取出,分发到各自的handler中处理。
handler根据是否有callback选择不同的分发方式。

//出队
<!--#Looper-->
public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    //真正决定阻塞的是queue.next()中的for循环
    for (;;) {
        //next方法会阻塞线程,有消息就取出,没消息就等待。退出返回null结束当前循环。
        Message msg = queue.next(); // might block
        if (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.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = 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();
                return null;
            }
        }
    }    
}

    
//由target返回到Handler中执行分发
//加入队列的:有的是Runnable封装的message,有的是callback,有的是message,在这里分发   
<!--#Handler-->
public void dispatchMessage(Message msg) {
    //runnable封装的message。 handler.post/view.post
    if (msg.callback != null) {//msg中的callback 优先级1
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            //Handler(Callback callback)的时候,mCallback !=null
            if (mCallback.handleMessage(msg)) {//handler中的callback,优先级2
                return;
            }
        }
        //正常的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:

public Handler(Callback callback, boolean async) {
    ...
    mCallback = callback;
    ...
}

优先级3:
常用的发送Message方式,设置what,obj参数,发送的消息。

3.3 退出

  • loop.quit() --> mQueue.quit(false)
  • loop.quitSafely() --> mQueue.quit(true)
<!--#MessageQueue-->
void quit(boolean safe) {
    //主线程时,quit报错
    if (!mQuitAllowed) {
        throw new IllegalStateException("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();
         return null;
    }
}

public static void loop() {
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = 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入口 ActivityThread
public static void main(String[] args) {  
    ...
    Looper.prepareMainLooper();  
    ActivityThread thread = new ActivityThread();  
    ...
    Looper.loop();  
}

public static void prepareMainLooper() {
    prepare(false);
    ...
}
//初始化MessageQueue时设置
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

void quit(boolean safe) {
    //这个地方:主线程时,quit报错
    if (!mQuitAllowed) {
        throw new IllegalStateException("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) {  
        #3
        sMainThreadHandler = 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:
  • 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没有处理耗时操作的能力,只是能在处理以后通知主线程。