美文网首页
死磕Handler(1)

死磕Handler(1)

作者: 程序员要多喝水 | 来源:发表于2019-11-21 13:49 被阅读0次

handler作为Android开发最中重要的模型之一,需要理解其工作原理包括Handler,Message,MessageQueue,Looper之间的关系,如下是一个网上摘抄的图片,MessageQueue是一个消息队列,Looper是一个循环体,Message是消息,而Handler是消息的具柄;


image.png

下面一步一步用代码来看:

Message

Message中有4个变量参数,what ,arg1,arg2,obj;经常使用其中的参数作为判断handleMessage的判断条件,其中一般有如下3种方式创建Message:
(1)通过Message的构造器模式创建:

Message msg = new Message();
msg.arg1 = 1;
msg.arg2 = 2;
msg.obj = 3;
msg.what = 4;
handle.sendMessage(msg);

(2)通过Message的obtain函数去操作:

Message msg = Message.obtain();
msg.what=1;
msg.arg1=2;
msg.arg2=3;
msg.obj=4;
handler.sendMessage(msg);

推荐使用后面obtain方式去创建,因为这种效率会高一些,减少了重复创建Message对象;
看源码:Message#obtain()

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

发现其实通过obtain是优先从缓存的Message池中去取,当取不到时候才会创建一个新的Message,因此效率更高,推荐使用;

Handler

常见的创建Handler方式如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
}

此中方式创建Handler时候会造成内存泄露,原因是因为非静态的匿名内部类和非静态内部类是可以持有一个外部类的引用的;因为在Java.class编译时候会将非静态的内部类和外部类编译成2个文件存储,非静态的内部类可以访问外部类的成员变量和成员方法这个是为什么呢?就是因为非静态的内部类是持有外部类的一个隐式引用,而静态内部类和外部类之间没有,两个几乎相当于两个独立的Class文件;编译后的代码应该差不多如下:

class Outer$Inner{
    final Outer this$0;
    
    public Outer$Inner(Outer outer){
        this.this$0 = outer;
        super();
    }
}

至于这个引用为什么会造成内存泄露呢?先说下根本原因吧,是因为handler持用Activity的引用,而Handler中如果消息没有处理完,Activity在销毁时候是没有办法被GC回收的,GC回收是根据对象可达性,这个Handler是绑定在主线程,主线程是应用入口,当Handler处理不完此消息必然造成Activity无法回收,最终内存泄露;如下这种会造成5分钟无法回收Activity:

public class HandlerActivity extends Activity{
 
    //可能引入泄露的方法
    private final Handler mLeakyHandler = new Handler(){
    
        @Override
        public void handleMessage(Message msg){
        
            //.....
        
        }
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        
        //延迟5分钟发送消息
        mLeakyHandler.postDelayed(new Runnable(){
        
            @Override
            public void run(){
                //......
            }
            
        }, 1000*60*5);
        
    
    }
}

知道原因解决方法也跟着出来了:
1.使用静态的内部类替代非静态内部类;
2.在onDestory方法时移除Handler中的消息也可以解决;

private static class InnerHandler extends Handler{
        
        private final WeakReference<HandlerActivity> mActivity;
        
        public InnerHandler(HandlerActivity activity){
            mActivity = new WeakReference<HandlerActivity>(activity);
        }
        
        @Override
        public void handleMessage(Message msg){
            HandlerActivity activity = mActivity.get();
            if(activity != null){
                //.....
            }
        }
    }
//清空当前Handler队列所有消息
 @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }

来看下handler的构造方法:

public Handler() {
        this(null, false);
    }
    
public Handler(Looper looper) {
        this(looper, null, false);
    }
    
 public Handler(Callback callback) {
        this(callback, false);
    }
    
public Handler(boolean async) {
        this(null, async);
    }

代码最常见的是使用1,2两种方式去创建handler,如果handler在主线程创建,比如不是在new Thread的线程内创建的话,那么handler的Looper绑定的是主线程的Looper,在子线程中使用时候需要自己Looper.loop<->Looper.prepare配套;
public Handler(Callback callback)可以用户拦截回调:
举个例子:

public class MainActivity extends Activity {
    private TextView textView;
    private Handler handler = new Handler(new Handler.Callback() {
        
        @Override
        public boolean handleMessage(Message msg) {
            Toast.makeText(getApplicationContext(), "callback handlemessage", 1000).show();    //代码1
            return true;   //这里返回值需要注意     // 代码3
        }
    }){
        public void handleMessage(Message msg) {
            Toast.makeText(getApplicationContext(), "handler handlemessage", 1000).show();  //代码2
        };
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView =(TextView) findViewById(R.id.textView);
    }
    
    public void show(View view){
        handler.sendEmptyMessage(1);
    }
    
}

上面的代码中使用了public Handler(Callback callback)的构造函数。我们这里可以进行消息传递的拦截。当我们的“代码3”中return false的时候 “代码1” “代码2”会依次执行,当“代码3”中return true的时候“代码1”会先执行 ,但是“代码2”不会执行,此时有点类似事件传递中返回true事件消费,false继续向上传递的意思。我们可以使用Handler的这个构造方法,来进行消息传递的拦截。
具体实现源码Handler#dispatchMessage如下:

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                //如果这里返回true,那么handleMessage是不会执行了;
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

还有一个public Handler(boolean async)设置异步的,对于API没开放,设置后会影响消息是否为异步消息,不能保证插入的消息有序性;
源码如下:

 public Handler(Callback callback, boolean async) {
        ...
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    
    
     /**
     * Returns true if the message is asynchronous, meaning that it is not
     * subject to {@link Looper} synchronization barriers.
     *
     * @return True if the message is asynchronous.
     *
     * @see #setAsynchronous(boolean)
     */
    public boolean isAsynchronous() {
        return (flags & FLAG_ASYNCHRONOUS) != 0;
    }

下面说常见的几种handler发消息的方法:

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
public final boolean postAtTime(Runnable r, long uptimeMillis)
    {
        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    }  
    
public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
 
 public final boolean postAtFrontOfQueue(Runnable r)
    {
        return sendMessageAtFrontOfQueue(getPostMessage(r));
    }
    
  public final boolean sendMessageDelayed(Message msg, long         delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }  
    
 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);
    }
    
 public final boolean sendMessageAtFrontOfQueue(Message msg) {
        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, 0);
    }
    
   private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }   

可以看出来
postxxx-->sendxxx-->enqueueMessage将消息放入到消息队列里面

MessageQueue

消息队列,消息队列的实现就是一个队列从里面取消息,都知道Loop会让循环不断的去取消息队列里面消息,实现方式也就是一个死循环,但是光死循环会造成cpu负荷,因此需要休眠控制下减少cpu,那消息队列如何控制休眠的呢;其实就是通过linux的epoll机制完成的,C++类Looper中的睡眠和唤醒机制是通过pollOnce和wake函数提供的,它们又是利用操作系统(Linux内核)的epoll机制来完成的,这里深入不多展开,只从一个地方看mBlocked:

  // Indicates whether next() is blocked waiting in pollOnce()     //with a non-zero timeout.
    private boolean mBlocked;

mBlocked这个标识位控制着是否休眠唤醒;

先看唤醒时机enqueueMessage:

boolean enqueueMessage(Message msg, long when) {
        ...
            //这里处理当消息通过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 next():

 Message next() {
            ...
            //使用pollOnce取消息时候会休眠
            nativePollOnce(ptr, nextPollTimeoutMillis);
            ...
            if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                }
                  if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
        }

Looper

一个循环处理消息队列的东东;
使用方式也简单,如下是将当前Handler和Looper关联,如果在主线程,关联是主线程,如果是子线程管理就子线程;

        //子线程
        Looper.prepare();
        Handler handler = new Handler();
        Looper.loop();
        
        //子线程
        Looper looper = .....;
        //主线程
        Handler handler = new Handler(looper);

子线程创建Handler一般使用HandlerThread方式:

public class HandlerThreadActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        HandlerThread handlerThread = new HandlerThread("HandlerThread");
        handlerThread.start();

        Handler mHandler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("HandlerThreadActivity.class","uiThread2------"+Thread.currentThread());//子线程
            }
        };

        Log.d("HandlerThreadActivity.class","uiThread1------"+Thread.currentThread());//主线程
        mHandler.sendEmptyMessage(1);
    }
}

看看源码:

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));
    }

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

Looper对象实例化就是在Looper.prepare完成的,并且将当前线程绑定到mThread上,现在回过头看Handler创建时候就明白为什么没有单独开线程创建时候,绑定是主线程上;来看Handler创建方法:

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

到这里还是没有直观的找到绑定的线程;但是我们忽略了一个ActivityThread,ActivityThread是Android程序的入口,一直运行,看下其main函数:

public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        ...
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();
}


public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
            // MIUI ADD
            sMainLooper.enableAnrMonitor(true);
        }
    }

Handler,Looper在程序启动的时候就已经开始工作了,也就是说我们自己的ThreadLocal.get()获取的是ActivityThread线程即主线程;
在看Looper.loop:

    public static void loop() {
        ...
        boolean slowDeliveryDetected = false;
        //死循环,不断的取消息队列中消息
        for (;;) {
            //消息队列处理消息时候因为nativePollOnce阻塞休眠
            Message msg = queue.next(); // might block
            //无消息时候退出
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            
             try {
                //处理消息
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
        }
    }
    
    
    public void dispatchMessage(Message msg) {
        //如果是post方式传入一个runnable执行即可
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //如果是public Handler(Callback callback)执行此处
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                //事件分发,是否自己消费掉
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    

这里更加明白了在ondestroy时候removeCallbacksAndMessages的含义了,
就是移除添加的postxxx,sendMessagexxx等消息;
至此整个Looper,Message,Handler,MessageQueue到此应该梳理比较清楚了;

还有一个类:ThreadLocal,在源码的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();
    }
    
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();  
 

ThreadLocal是一个本地线程存放副本变量的工具,不同线程其变量值互不干扰,适用于多线程高并发场景完成多线程调用互不干扰的变量的值,可以类似理解为多进程操作同一个静态变量,其值也是互不干扰;

看看ThreadLocal代码:

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

实现原理就是set时候先从ThreadLocalMap去找,取不到再存放,get也是类似原理,看源码就明白了;

引深问题:
网上有一个很奇怪的问题,就是在为什么主线程执行循环不会引起ANR?
ActivityThread.main方法有如下:

        //将当前线程
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
        //这就是一个Hanlder处理事件
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        //开启循环
        Looper.loop();

之前已经分析了,这里主线程不是ActivityThread,首先说为什么要开启循环?
开启循环是保证应用主线程不会退出,如果退出了,应用也就消亡了,为了保证应用能一直持续运行下去就采用循环方式。
其次如果保证开启循环也不ANR,这就是之前分析利用操作系统(Linux内核)的epoll机制来完成的,在MessageQueue#next取出下一个Message时候,会调用nativePollOnce,nativePollOnce方法会阻塞,系统CPU进入休眠不会一直消耗殆尽;而唤醒时机是在MessageQueue#enqueueMessage调用nativeWake完成唤醒的;
因此实际就是一次阻塞唤醒的行为,一开始没有消息处理时候,因为nativePollOnce阻塞,系统休眠,等有消息来临时候调用nativeWake唤醒进行消息处理,这就类似于生产者和消费者模型;

相关文章

  • 死磕Handler(1)

    handler作为Android开发最中重要的模型之一,需要理解其工作原理包括Handler,Message,Me...

  • 死磕Handler(3)

    这节介绍些Handler的隐藏小技巧:(1)利用Handler统计耗时任务:Loop.loop方法源码可以看出,处...

  • 死磕Handler(2)

    Handler在Thread使用 在子线程中使用handler实例: 可以看到,在子线程中创建handler需要注...

  • “死磕”与学习

    也说“死磕” 死磕到底,死磕精神,死磕侠。互联网的发达,孕育了越来越多的网络词汇,“死磕”现在出现的频率颇高。 那...

  • 产品经理的三阶段修炼

    初级产品经理的三项修炼 1、死磕界面 2、死磕流程 3、死磕流畅度 以自我为中心的理念,我是专业人士心态 中级产品...

  • 天道酬勤

    1.死磕才是经营的本质

  • 这些“死磕成本”的店,却因高体验卖出了惊人销量

    有些店死磕服务,有些死磕产品,还有些死磕成本。可有些品牌除了这些,还死磕别的... 无论何时,店铺的人工成本、租金...

  • 死磕与磕死

    前天晚上,打开百度网盘,准备听梁冬的节目睡睡平安,突然发现所有的音频转哪转哪,就是不出声音。到底哪里出了毛病?听听...

  • 磕,死磕

    疫情期间,你做的最多的是什么? 我啊~大概是反省吧,自省。 我发现反省是扇隐秘的门,一旦打开,就像探险一样,不停的...

  • 死磕Tomcat系列(5)——容器

    死磕Tomcat系列(5)——容器 回顾 在死磕Tomcat系列(1)——整体架构中我们简单介绍了容器的概念,并且...

网友评论

      本文标题:死磕Handler(1)

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