Android开发经验谈Android技术知识Android开发

征服Android面试官路漫漫(五):5分钟了解Handler机

2020-11-13  本文已影响0人  Android_until

Handler的相关博客太多了,随便一搜都一大把,但是基本都是上来就贴源码,讲姿势,短时间不太好弄明白整体的关系,和流程,本文就以生活点餐的例子再结合源码原理进行解析。希望对你有一点帮助。

来,咱们进入角色。

Hander,Looper,MessageQueue,Message的全程协作的关系就好比一个餐厅的整体运作关系

Handler好比点餐员
Looper好比后厨厨师长。
MessageQueue好比订单打单机。
Message好比一桌一桌的订单。


接下来我们回顾下我们餐厅点餐的场景,餐厅点餐分为标准点餐和特殊点餐,我们分解来看。

标准流程

特殊流程

总结

一家店可以有多个点餐员,但是厨师长只能有一个。打单机也只能有一个。

映射到以上场景中,一家店就好比一个Thread,而一个Thread中可以有多个Handler(点餐员),但只能有一个Looper(厨师长),一个MessageQueue(打单机),和多个Message(订单)。


根据以上的例子我们类比看下源码,充分研究下整个机制的流程,和实现原理。

Looper的工作流程

ActivityThread.main();//初始化入口
    1. Looper.prepareMainLooper(); //初始化
          Looper.prepare(false); //设置不可关闭
              Looper.sThreadLocal.set(new Looper(quitAllowed)); //跟线程绑定
                    1.1.Looper.mQueue = new MessageQueue(quitAllowed); //Looper和MessageQueue绑定
                    1.2.Looper.mThread = Thread.currentThread();
    2. Looper.loop();
        2.1.myLooper().mQueue.next(); //循环获取MessageQueue中的消息
              nativePollOnce(); //阻塞队列
                  native -> pollInner() //底层阻塞实现
                        native -> epoll_wait();
        2.2.Handler.dispatchMessage(msg);//消息分发

myLooper().mQueue.next()实现原理

Handler.dispatchMessage(msg)实现原理


Hander发送消息的流程

1.Handler handler = new Handler();//初始化Handler
        1.Handler.mLooper = Looper.myLooper();//获取当前线程Looper。
        2.Handler.mQueue = mLooper.mQueue;//获取Looper绑定的MessageQueue对象。

2.handler.post(Runnable);//发送消息
        sendMessageDelayed(Message msg, long delayMillis);
            sendMessageAtTime(Message msg, long uptimeMillis);
                Handler.enqueueMessage();//Message.target 赋值为this。
                    Handler.mQueue.enqueueMessage();//添加消息到MessageQueue。

MessageQueue.enqueueMessage()方法实现原理

经常有人问为什么主线程的Looper阻塞不会导致ANR?

以上的所有内容均围绕原理,源码,接下来我们举几个特殊场景的例子

1.为什么子线程不能直接new Handler()?

       new Thread(new Runnable() {
           @Override
           public void run() {
              Handler handler = new Handler();
           }
       }).start();

       以上代码会报以下下错误

java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
       at android.os.Handler.<init>(Handler.java:207)
       at android.os.Handler.<init>(Handler.java:119)
       at com.example.test.MainActivity$2.run(MainActivity.java:21)
       at java.lang.Thread.run(Thread.java:919)

 public Handler(Callback callback, boolean async) {
        ...省略若干代码

       //通过 Looper.myLooper()获取了mLooper 对象,如果mLooper ==null则抛异常
        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() {
        //而myLooper()是通过sThreadLocal.get()获取的,那sThreadLocal又是个什么鬼?
        return sThreadLocal.get();
    }

 //可以看到sThreadLocal 是一个ThreadLocal<Looper>对象,那ThreadLocal值从哪赋值的?
 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

//sThreadLocal 的值就是在这个方法里赋值的
 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));
    }

2. 为什么主线程可以直接new Handler?

    //我们看下ActivityMain的入口main方法,调用了 Looper.prepareMainLooper();
    public static void main(String[] args) {
       ...
        Looper.prepareMainLooper();
        ...
    }

  //看到这一下就明白了,原来主线程在启动的时候默认就调用了prepareMainLooper(),而在这个方法中调用了prepare()。  
 //提前将sThreadLocal 进行赋值了。
  public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

3.Handler为什么会内存泄露?

public class HandlerActivity extends AppCompatActivity {
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        handler.sendEmptyMessageDelayed(1,5000);
    }
}

当以上代码写完后编译器立马会报黄并提示 “this handler should be static or leaks might occur...Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.”

大致意思就说 “由于这个处理程序被声明为一个内部类,它可以防止外部类被垃圾回收。如果处理程序正在对主线程以外的线程使用Looper或MessageQueue,则不存在问题。如果处理程序正在使用主线程的Looper或MessageQueue,则需要修复处理程序声明,如下所示:将处理程序声明为静态类;并且通过WeakReference引用外部类”。

说了这么一大堆,简单意思就是说以上这种写法,默认会引用HandlerActivity,当HandlerActivity被finish的时候,可能Handler还在执行不能会回收,同时由于Handler隐式引用了HandlerActivity,导致了HandlerActivity也不能被回收,所以内存泄露了。

我们来写一种正确的写法

public class HandlerActivity extends AppCompatActivity {
      MyHandler handler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        handler.sendEmptyMessageDelayed(1,5000);
    }
    private static class MyHandler extends Handler{
        private WeakReference<HandlerActivity> activityWeakReference;

        public MyHandler(HandlerActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

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

4. 补充个小知识点,啥是隐式引用?

注意 : 不是所有内部类都建议使用静态内部类,只有在该内部类中的生命周期不可控的情况下,建议采用静态内部类。其他情况还是可以使用非静态内部类的。

如何进阶Android?

有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

最后我在这里分享一下这段时间从朋友,大佬那里收集到的一些2019-2020BAT 面试真题解析,里面内容很多也很系统,包含了很多内容:Android 基础、Java 基础、Android 源码相关分析、常见的一些原理性问题等等,可以很好地帮助我们深刻理解Android相关知识点的原理以及面试相关知识。

1、确定好方向,梳理成长路线图

不用多说,相信大家都有一个共识:无论什么行业,最牛逼的人肯定是站在金字塔端的人。所以,想做一个牛逼的程序员,那么就要让自己站的更高,成为技术大牛并不是一朝一夕的事情,需要时间的沉淀和技术的积累。

关于这一点,在我当时确立好Android方向时,就已经开始梳理自己的成长路线了,包括技术要怎么系统地去学习,都列得非常详细。

知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。

2、通过源码来系统性地学习

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

《486页超全面Android开发相关源码精编解析》

3、阅读前辈的一些技术笔记

《960全网最全Android开发笔记》

4、刷题备战,直通大厂

历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?

1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

《379页Android开发面试宝典》

以上内容均放在了开源项目:github 中已收录,大家可以自行获取(或者关注主页扫描加微信获取)。

系列文章:
征服Android面试官路漫漫(一):线程攻略,夯实基础很重要!
征服Android面试官路漫漫(二):关于OutOfMemoryError是否可以被try catch的探讨
征服Android面试官路漫漫(三):从源码深扒一下四大组件和 Context
征服Android面试官路漫漫(四):5 张图带你搞懂Android系统启动的核心流程

上一篇 下一篇

猜你喜欢

热点阅读