Handler

image.png

Handler机制:

是一个消息传递机制,作用是将子线程需要的UI操作,传递到UI线程执行,保证线程安全。

image.png

相关类介绍

  • Message:线程间通讯的数据单元。

MessageQueue:消息队列,特点先进先出。存储Handler发送过来的消息(Message)

  • Handler:是一个消息处理器,将消息传递给消息队列,然后再由对应线程从消息队列中逐个取出数据,并执行

  • Looper:消息循环器,循环取出消息(Message),将取出的消息分发给对应的处理者(Handler)。
    特点:

  • 一个Handler只能拥有一个Looper。

  • 一个Looper可以绑定多个Handler

    public Handler(Callback callback, boolean async) {
       ...
        //关联Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //关联MessageQueue
        mQueue = mLooper.mQueue;
        ...
    }

在Handler的构造方法中,会关联Looper和MessageQueue(Looper的构造函数中会创建自己的MessageQueue)

源码分析

默认情况,消息队列只有一个,就是主线程的消息队列。这个线程由ActivityThread.main方法中创建,调用Looper.prepareMainLooper()创建,执行Looper.loop()来启动消息循环。

    public static void main(String[] args) {

       Looper.prepareMainLooper(); 
       // 1.创建主线程Looper
       ActivityThread thread = new ActivityThread(); 
       // 2. 创建主线程
       Looper.loop(); 
       // 3. 开启消息循环
}
    //设置和获取Looper,Looper保存在ThreadLocal中
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    
    //设置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));
    }

   //Looper的构造函数中会创建自己的MessageQueue
   private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

    //获取Looper
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

可以看到Looper存储在ThreadLocal,这就实现了线程安全。Looper可以对应多个Handler

/** 
  * 源码分析: Looper.loop()
  * 作用:消息循环,即从消息队列中获取消息、分发消息到Handler
  * 特别注意:
  *       a. 主线程的消息循环不允许退出,即无限循环
  *       b. 子线程的消息循环允许退出:调用消息队列MessageQueue的quit()
  */
  public static void loop() {
        
        ...// 仅贴出关键代码

        // 1. 获取当前Looper的消息队列
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            // myLooper()作用:返回sThreadLocal存储的Looper实例;若me为null 则抛出异常
            // 即loop()执行前必须执行prepare(),从而创建1个Looper实例
            
            final MessageQueue queue = me.mQueue;
            // 获取Looper实例中的消息队列对象(MessageQueue)

        // 2. 消息循环(通过for循环)
            for (;;) {
            
            // 2.1 从消息队列中取出消息
            Message msg = queue.next(); 
            if (msg == null) {
                return;
            }
            // next():取出消息队列里的消息
            // 若取出的消息为空,则线程阻塞

            // 2.2 派发消息到对应的Handler
            msg.target.dispatchMessage(msg);
            // 把消息Message派发给消息对象msg的target属性
            // target属性实际是1个handler对象

        // 3. 释放消息占据的资源
        msg.recycle();
        }
}

loop()是一个消息无限循环,不断从消息队列获取消息,将消息派发到对应的Handler

public final class Message implements Parcelable {

    Handler target;
    Runnable callback;
    Message next;
}

  /** dispatchMessage(msg)
  * 定义:属于处理者类(Handler)中的方法
  * 作用:派发消息到对应的Handler实例 & 根据传入的msg作出对应的操作
  */
  public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);

        }
    }

   private static void handleCallback(Message message) {
        message.callback.run();
    }


   public void handleMessage(Message msg) {  
          ... // 创建Handler实例时复写
   } 

dispatchMessage派发消息最终会调用HandlerMessage或者run方法处理

  1. 当调用Handler.post时msg.callback != null,会走run方法
  2. 当Message.obtain方法里设置时msg.callback != null,会走run方法
  3. 当Handler初始化时传入Callbackh时mCallback != null,会走mCallback.handleMessage(msg)方法
  4. 当前面两者都没有,直接调用sendMessage时,会走handleMessage(msg) 方法
demo
public class HandlerActivity extends AppCompatActivity {
    @BindView(R.id.content_tv)
    TextView mContentTv;

    Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1) {
                mContentTv.setText((CharSequence) msg.obj);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        ButterKnife.bind(this);
    }

    @OnClick({R.id.post, R.id.obtain, R.id.Handler_Callback_is_null, R.id.Handler_Callback_is_not_null})
    public void onClick(View view) {
        switch (view.getId()) {
            //当调用Handler.post时msg.callback != null,会走run方法
            case R.id.post:
                new Thread() {
                    @Override
                    public void run() {
                        super.run();
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mContentTv.setText("post");
                            }
                        });
                    }
                }.start();
                break;
            //当Message.obtain方法里设置时msg.callback != null,会走run方法
            case R.id.obtain:
                new Thread() {
                    @Override
                    public void run() {
                        super.run();
                        Message message = Message.obtain(mHandler, new Runnable() {
                            @Override
                            public void run() {
                                mContentTv.setText("obtain");
                            }
                        });
                        mHandler.sendMessage(message);
                    }
                }.start();
                break;
            //当Handler初始化时传入Callback时mCallback != null,会走mCallback.handleMessage(msg)方法
            case R.id.Handler_Callback_is_null:
                new Thread() {
                    @Override
                    public void run() {
                        super.run();
                        Message msg = Message.obtain();
                        msg.what = 1; // 消息标识
                        msg.obj = "Handler_Callback_is_null"; // 消息内存存放
                        mHandler.sendMessage(msg);
                    }
                }.start();
                break;
            //当前面两者都没有,直接调用sendMessage时,会走handleMessage(msg) 方法
            case R.id.Handler_Callback_is_not_null:
                final Handler handler = new Handler(new Handler.Callback() {
                    @Override
                    public boolean handleMessage(Message msg) {
                        if (msg.what == 2) {
                            mContentTv.setText((CharSequence) msg.obj);
                        }
                        return true;

                    }
                });

                new Thread() {
                    @Override
                    public void run() {
                        super.run();
                        Message msg = Message.obtain();
                        msg.what = 2; // 消息标识
                        msg.obj = "Handler_Callback_is_not_null"; // 消息内存存放
                        handler.sendMessage(msg);
                    }
                }.start();
                break;
        }
    }
}

为什么可以直接post呢,因为post(Runnable r)会将Runnable包装成Message对象,并将Runnable对象设置给Message对象的callback,最好将Message对象插入消息队列中去。

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    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 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;
        }
        //将消息插入MessageQueue
        return enqueueMessage(queue, msg, uptimeMillis);
    }

Handler机制流程图

image.png

问题

1、为什么不new Message()而采用Message.obtain()?

因为Message内部维护了1个Message池,用于Message消息对象的复用,使用obtain()则是直接从池内获取,避免内存new Message重新分配内存。

    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();
    }
2、Handler可以在子线程创建吗?

通常Handler必须在主线程创建,因为Handler机制的主要作用是更新UI。当然也可以创建在子线程,那么就得自己手动去prepare和loop。

    new Thread(){
        @Override
        public void run() {
            super.run();
            //创建当前线程的Looper,并绑定到ThreadLocal中
            Looper.prepare();
            //创建Handler,关联Looper和MessageQueue
            Handler handler = new Handler();
            //启动消息循环
            Looper.loop();
        }
    }.start();
3、Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

loop虽然是死循环,但是如果messagequeue里面没有消息的话,会休眠在native层那个nativePollonce方法

问题参考:https://www.zhihu.com/question/34652589/answer/90344494?from=profile_answer_card](https://www.zhihu.com/question/34652589/answer/90344494?from=profile_answer_car

4、如何避免不当导致的内存泄漏

问题根源: 非静态匿名内部类持有外部类引用

1、静态Handler内部类, 如果在handMessage方法里需要用到上下文, 进行弱化

public class NoLeakActivity extends AppCompatActivity {

    private NoLeakHandler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mHandler = new NoLeakHandler(this);

        Message message = Message.obtain();

        mHandler.sendMessageDelayed(message,10*60*1000);
    }

    private static class NoLeakHandler extends Handler{
        private WeakReference<NoLeakActivity> mActivity;

        public NoLeakHandler(NoLeakActivity activity){
            mActivity = new WeakReference<>(activity);
        }

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

但是为什么为static类型就会解决这个问题呢?
因为在java中所有非静态的对象都会持有当前类的强引用,而静态对象则只会持有当前类的弱引用。
声明为静态后,handler将会持有一个Activity的弱引用,而弱引用会很容易被gc回收,这样就能解决Activity结束后,gc却无法回收的情况。

2、Handler内部自带的remove方法

学习参考:

https://www.jianshu.com/p/b4d745c7ff7a

https://www.zhihu.com/question/34652589/answer/90344494?from=profile_answer_card

Android开发进阶从小工到专家

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358