加载中......
输入验证码,即可复制
微信扫码下载好向圈APP, 登陆后即可进入消息页面查看验证码
只需要3秒时间

提到handler,大家都想到些什么呢,切换线程?延时操作? 那么你是否了解「IdleHandler,同步屏障,死循环」的设计原理?以及由Handler机制衍生的「IntentService,BlockCanary」? 这次我们说下Android中最常见的Handler,通过解析面试点或者知识点,带你领略Handler内部的神奇之处。
先上一张总结图,看看你是否了解真正的Handler


面试Handler都没答上来,你真的了解它吗?全面解析来了-1.jpg

基本的用法和工作流程

用法很简单,定义一个handler,重写handleMessage方法处理消息,用send系列方法发送消息。 但是主线程和新建线程用法却有点不一样!其实新线程里面的用法才是表达出完整流程的。
Handler handler = new Handler() {    @Override    public void handleMessage(@NonNull Message msg) {        super.handleMessage(msg);    }};handler.sendEmptyMessage(0);handler.sendEmptyMessageDelayed(0, 1000);new Thread(new Runnable() {    @Override    public void run() {        Looper.prepare();        Handler handler = new Handler() {            @Override            public void handleMessage(@NonNull Message msg) {                super.handleMessage(msg);            }        };        Looper.loop();    }}).start();Looper.prepare()方法就是创建looper对象,并且绑定到该线程。 然后定义一个handler,loop()方法中主要是looper对象会不断从MessageQueue中获取message并且发送给对应的hander,并且通过handleMessage方法处理。
所以looper相当于一个邮递员,负责从邮局(MessageQueue)获取信件(Message),并将信件传递给收件人(Handler)。
「handler相关四大天王」
    looper,关联线程并且负责从消息队列拿出消息分发给handlerMessageQueue,消息队列,负责消息存储管理Message,消息对象handler,负责发送和处理消息
「用法和流程就这么多,下面开始常见面试点讲解并附上简单的源码解析,具体剖析Handler内部奥秘」
知识点,面试点

「面试题1:主线程的looper呢??」

看下源码,这里需要涉及到的一个类android.app.ActivityThread,这个类中的main方法是整个app的最开始执行的方法,是app的入口,看下「main方法源码」
Looper.prepareMainLooper();// ***ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {    sMainThreadHandler = thread.getHandler();}if (false) {    Looper.myLooper().setMessageLogging(new    LogPrinter(Log.DEBUG, "ActivityThread"));}Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");Looper.prepareMainLooper();Looper.loop();其中最重要的就是这两句,调用了prepareMainLooper方法创建了主线程的Looper,然后调用loop方法开始死循环
ok,loop方法是找到了。那Looper为什么可以一直取消息呢?看看源码
//Looperpublic static void loop() {    //...    for (; ; ) {        // 不断从 MessageQueue 获取 消息        Message msg = queue.next();        if (msg == null) {            // No message indicates that the message queue is quitting.            return;        }        //...    }}找到原因了。其实就是一个死循环,所以Looper可以一直执行工具人的工作
「面试题2:为什么有死循环呢?这种写法科学吗?不会oom吗??」

说白了,其实死循环也是有意为之,线程在可执行代码执行完后,就会终止,而主线程肯定需要一直运行,所以死循环就能保证这一点。
「死循环之外怎么处理事务?」
既然主线程是死循环,那么如果有其他事务该怎么处理呢?创建新线程呗,在主线程创建之前会创建一些其他的binder线程,比如ApplicationThraed
「死循环是不是会浪费cpu资源」
主线程的messageQueue在没有消息的时候,会阻塞在loop的queue.next方法中,此时主线程会释放CPU资源,进入休眠状态,直到下个消息来到,所以不会一直消耗CPU资源。
「而activity的生命周期是怎么实现在死循环体外正常执行的呢?」
其实就是通过这个「handler」,比如onPause方法,当主线程Looper在loop的时候,收到暂停的消息,就会把消息分发给主线程的handleMessage处理,然后最后会调用到activity的onPause方法。 那主线程的消息又是哪里来的呢?刚才说到主线程之外还会创建一些binder线程,比如app线程,系统线程,一般是系统线程比如ApplicationThreadProxy传消息给APP线程ApplicationThread,然后再传给主线程,也就是ActivityThread所在的线程。
「面试题3:内存泄漏??」

首先为什么会发送内存泄漏?handler作为内部类持有外部类的引用,当发送延迟消息时,就有可能发生处理消息的时候,activity已经销毁了,从而导致内存泄漏
怎么解决?定义静态内部类,并且在ondestory里面移除所有消息
直接移除不就行了?还需要静态内部类?onDestory方法不一定执行哦。如果你的Activity不在栈顶,然后app被后台强杀,那么onDestory就不会被执行了。
上代码:
private static class MemoryLeakSafeHandler extends Handler {    private WeakReference<HandlerInfoActivity> ref;    public MemoryLeakSafeHandler(HandlerInfoActivity activity) {        this.ref = new WeakReference(activity);    }    @Override    public void handleMessage(final Message msg) {        HandlerInfoActivity activity = ref.get();        if (activity != null) {            activity.handleMessage(msg);        }    }}MemoryLeakSafeHandler handler;public void handleMessage(Message msg) {}@Overrideprotected void onDestroy() {    handler.removeCallbacksAndMessages(null);    super.onDestroy();}「面试题4:IdleHandler是什么,有什么用呢?」

IdleHandler是在Hanlder空闲时,也就是没有可处理的消息时候,用来处理空闲任务的一种机制。 有什么作用呢?主要是用于提升性能,可以在消息队列空闲时做一些事情,从而不影响到主线程的任务处理。(卑微小弟,你们重要的大哥们先用,我最后再用)。
用法如下:
Looper.myQueue().addIdleHandler(new IdleHandler() {      @Override      public boolean queueIdle() {          //do something        return false;        }  });这里queueIdle方法的返回参数是bool类型,true代表执行一次后不删除,下次进入空闲时还会调用该回掉方法。false代表执行一次后该IdleHandler就会被删除。 源码在MessageQueue类的next方法,其实就是在消息队列里面没有消息的时候会去查询mIdleHandlers队列,mIdleHandlers队列有数据,也就是有IdleHandler就会去处理执行。 还是简单放下源码吧:
Message next() {        for (;;) {            synchronized (this) {                // 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.                if (pendingIdleHandlerCount < 0                        && (mMessages == null || now < mMessages.when)) {                    pendingIdleHandlerCount = mIdleHandlers.size();                }                if (pendingIdleHandlerCount <= 0) {                    // No idle handlers to run.  Loop and wait some more.                    mBlocked = true;                    continue;                }                if (mPendingIdleHandlers == null) {                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];                }                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);            }            // Run the idle handlers.            // We only ever reach this code block during the first iteration.            for (int i = 0; i < pendingIdleHandlerCount; i++) {                final IdleHandler idler = mPendingIdleHandlers;                mPendingIdleHandlers = null; // release the reference to the handler                boolean keep = false;                try {                    keep = idler.queueIdle();                } catch (Throwable t) {                    Log.wtf(TAG, "IdleHandler threw exception", t);                }                if (!keep) {                    synchronized (this) {                        mIdleHandlers.remove(idler);                    }                }            }        }    }有人可能要问了,这玩意真的有用吗?确实有用,只是你没用到而已。下面举例两个场景
    比如我要提升页面的启动速度,就可以把onCreate,onResume里面的一些操作放到IdleHandler里面执行,减少启动时间。Leakcanary等三方库也用到了这个类,用来干嘛呢?监听主线程的UI操作已完成。既然都执行到我这里来了,就说明UI操作都完成了是吧。
「面试题5:同步屏障机制是什么,有什么用呢?」

还是看这个next获取消息的方法:
Message next() {        for (; ; ) {            synchronized (this) {                if (msg != null && msg.target == null) {                    // Stalled by a barrier.  Find the next asynchronous message in the queue.                    do {                        prevMsg = msg;                        msg = msg.next;                    } while (msg != null && !msg.isAsynchronous());                }                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;                        return msg;                    }                }             }        }    }可以看到一开始就会判断这个消息两个条件:
    msg不为空msg的target为空
那么这种消息就是属于同步屏障消息,如果遇到这种消息,就会进入一个dowhile循环,找出消息队列中的异步消息并返回。 所以这个同步屏障机制就是为了让handler能够先执行异步消息,再去执行同步消息,直到屏障被移除。 慢着,我之前咋没听过还有异步消息?哈哈。确实是有的,Message有个setAsynchronous方法,如果传入true,就代表这个消息是个异步消息,在同步屏障发生后就可以先执行。目的是为了插入一些比较重要的消息需要先行处理。
具体使用方法就是:
    postSyncBarrier方法加入屏障removeSyncBarrier移除屏障 但是这两个方法都已经标记为hide了,要使用的话必须使用反射。
ok,了解完之后又该有人问了,有什么用呢?这个同步屏障。如果你看过view绘制的源码,你就会发现ViewRootImpl类中就用到了这个,由于view绘制的优先级比较高,所以开启同步屏障后,就可以选择让某些操作先执行,提高了优先级,比如这个view的绘制处理操作。
咦,这个机制感觉跟之前的IdleHandler是对应关系啊,一个是卑微小弟?一个是在线大哥?
「面试题6:Handler机制还还还有什么其他的用处或者实际应用吗?」

当然有啦,举:
BlockCanary

一个用来检测应用卡顿耗时的三方库。它的原理就在于Handler相关的Looper类里面,上面说过,Activity的生命周期都是依靠主线程的Looper.loop(),所以主线程的操作基本都是在handler处理中发生的。那有没有什么办法可以查看handler处理的耗时时间呢?如果知道了耗时时间不就知道哪里卡顿了?上源码:
public static void loop() {    ...    for (;;) {        ...        // This must be in a local variable, in case a UI event sets the logger        Printer logging = me.mLogging;        if (logging != null) {            logging.println(">>>>> Dispatching to " + msg.target + " " +                    msg.callback + ": " + msg.what);        }        msg.target.dispatchMessage(msg);        if (logging != null) {            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);        }        ...    }}    /**     * Handle system messages here.     */    public void dispatchMessage(@NonNull Message msg) {        if (msg.callback != null) {            handleCallback(msg);        } else {            if (mCallback != null) {                if (mCallback.handleMessage(msg)) {                    return;                }            }            handleMessage(msg);        }    }在loop方法内,有个Printer类,用来打印日志。我们可以看到在dispatchMessage方法前后分别打印了一次日志,而dispatchMessage方法就是Handler用来处理消息的地方。那么,我们如果能获取到这两次打印日志的时间差,不就可以得到Handler处理消息的耗时时间了?所以我们直接替换这个Printer就可以达到我们的目的了:
Looper.getMainLooper().setMessageLogging(mainLooperPrinter);这么简单有效的方法,让我不得不给BlockCanary作者点个
IntentService

IntentService 是一个继承自Service,自带工作线程,并且线程任务结束后自动销毁的一个类。Service是啥?可以统一管理后台任务,运行在前台,所以可以获取到上下文。 而IntentService同样具有这些特性,并且可以在新线程管理任务,工作执行完自动销毁。就线程池来说区别就在与IntentService拥有Service的特性,所以在需要用到上下文的时候就可以选择IntentService。而IntentService内部其实就是用到了HandlerThread,也就是带有Handler机制的线程。还是来点源码:
@Override    public void onCreate() {        super.onCreate();        //创建新线程并start        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");        thread.start();        mServiceLooper = thread.getLooper();        //创建新线程对应的handler        mServiceHandler = new ServiceHandler(mServiceLooper);    }    @Override    public void onStart(@Nullable Intent intent, int startId) {       //service启动后发送消息给handler        Message msg = mServiceHandler.obtainMessage();        msg.arg1 = startId;        msg.obj = intent;        mServiceHandler.sendMessage(msg);    }    private final class ServiceHandler extends Handler {        public ServiceHandler(Looper looper) {            super(looper);        }        @Override        public void handleMessage(Message msg) {                //handler收到消息后调用onHandleIntent方法            onHandleIntent((Intent)msg.obj);            stopSelf(msg.arg1);        }    }源码解析

经过上面的知识点讲解,大家应该都大致了解了Handler内部原理,最后我们就跟随源码再复习一遍。 我们之前了解到,其实Handler真正做事其实就是两个方法:
    sendEmptyMessage(Handler发送消息)loop (Looper开始循环获取消息)
sendMessage

上源码:
public final boolean sendMessage(@NonNull Message msg) {        return sendMessageDelayed(msg, 0);    }    public final boolean sendEmptyMessage(int what)    {        return sendEmptyMessageDelayed(what, 0);    }    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {        Message msg = Message.obtain();        msg.what = what;        return sendMessageDelayed(msg, delayMillis);    }    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {        if (delayMillis < 0) {            delayMillis = 0;        }        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);    }    public boolean sendMessageAtTime(@NonNull 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);    }可以看到,不管是发送的什么消息,最后都会走到这个enqueueMessage方法中,那我们就继续看看enqueueMessage`方法
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,            long uptimeMillis) {        msg.target = this;        msg.workSourceUid = ThreadLocalWorkSource.getUid();        if (mAsynchronous) {            msg.setAsynchronous(true);        }        return queue.enqueueMessage(msg, uptimeMillis);    }enqueueMessage`方法有两个重要的点:
    msg.target = this,指定了消息的target对象为handler本身queue.enqueueMessage(msg, uptimeMillis),执行了MessageQueueenqueueMessage方法。
ok,继续往下看
//MessageQueue.java    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) {                // 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;    }enqueueMessage`这个方法主要做了两件事:
1、插入消息,msg。通过一个循环,找出msg应该插入的位置(按照时间排序),然后插入msg到mMessages(消息队列)
2、唤醒消息队列。消息队列在没有消息的时候,会阻塞在queue.next()方法这里,所以来了消息就要唤醒线程。这里的阻塞和唤醒主要依靠底层的epoll 机制,具体我也不太懂,有懂得大神可以在评论区留言
既然有了消息,那么Looper那端就要取消息了,怎么取的?就是我们要说的第二个重要方法loop
loop

//Looper.java    public static void loop() {        final Looper me = myLooper();        final MessageQueue queue = me.mQueue;        for (;;) {            Message msg = queue.next(); // might block            // This must be in a local variable, in case a UI event sets the logger            final Printer logging = me.mLogging;            if (logging != null) {                logging.println(">>>>> Dispatching to " + msg.target + " " +                        msg.callback + ": " + msg.what);            }            try {                msg.target.dispatchMessage(msg);            } catch (Exception exception) {                throw exception;            } finally {            }            if (logging != null) {                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);            }        }    }     /**     * Handle system messages here.     */    public void dispatchMessage(@NonNull Message msg) {        if (msg.callback != null) {            handleCallback(msg);        } else {            if (mCallback != null) {                if (mCallback.handleMessage(msg)) {                    return;                }            }            handleMessage(msg);        }    }这里截取了部分代码,可以看到,loop方法通过一个死循环,不断的从MessageQueue获取消息,并且通过msg.target的dispatchMessage方法进行处理,target上文说过也就是消息对应的Handler。 而dispatchMessage方法最后也会调用到handler的handleMessage方法了。至此,流程已走通。
ok,还剩最后一个重要的点没说了。就是到底MessageQueue是怎么取出消息的呢?
    死循环获取消息遇到同步屏障消息,就优先处理异步消息(上文知识点)队列空闲时就开启IdleHandler机制处理任务。(上文知识点)
代码贴上
//MessageQueue.java    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) {                    // Stalled by a barrier.  Find the next asynchronous message in the queue.                    do {                        prevMsg = msg;                        msg = msg.next;                    } while (msg != null && !msg.isAsynchronous());                }                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;                    }                } 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.                if (pendingIdleHandlerCount < 0                        && (mMessages == null || now < mMessages.when)) {                    pendingIdleHandlerCount = mIdleHandlers.size();                }                if (pendingIdleHandlerCount <= 0) {                    // No idle handlers to run.  Loop and wait some more.                    mBlocked = true;                    continue;                }                if (mPendingIdleHandlers == null) {                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];                }                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);            }            // Run the idle handlers.            // We only ever reach this code block during the first iteration.            for (int i = 0; i < pendingIdleHandlerCount; i++) {                final IdleHandler idler = mPendingIdleHandlers;                mPendingIdleHandlers = null; // release the reference to the handler                boolean keep = false;                try {                    keep = idler.queueIdle();                } catch (Throwable t) {                    Log.wtf(TAG, "IdleHandler threw exception", t);                }                if (!keep) {                    synchronized (this) {                        mIdleHandlers.remove(idler);                    }                }            }        }    }至此,Handler的大概已经了解的差不多了,是不是觉得Handler太神奇了,你也忍不住想去好好看看它的源码了呢?也许还有一些功能没被利用起来,等着你去发现
有说的不对的地方望指正,谢谢。
面试前的知识梳理,储备提升

自己的知识准备得怎么样,这直接决定了你能否顺利通过一面和二面,所以在面试前来一个知识梳理,看需不需要提升自己的知识储备是很有必要的。
关于知识梳理,这里再分享一下我面试这段时间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)
    架构师筑基必备技能:深入Java泛型+注解深入浅出+并发编程+数据传输与序列化+Java虚拟机原理+反射与类加载+动态代理+高效IOAndroid高级UI与FrameWork源码:高级UI晋升+Framework内核解析+Android组件内核+数据持久化360°全方面性能调优:设计思想与代码质量优化+程序性能优化+开发效率优化解读开源框架设计思想:热修复设计+插件化框架解读+组件化框架设计+图片加载框架+网络访问框架设计+RXJava响应式编程框架设计+IOC架构设计+Android架构组件JetpackNDK模块开发:NDK基础知识体系+底层图片处理+音视频开发微信小程序:小程序介绍+UI开发+API操作+微信对接Hybrid 开发与Flutter:Html5项目实战+Flutter进阶

面试Handler都没答上来,你真的了解它吗?全面解析来了-2.jpg

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

面试Handler都没答上来,你真的了解它吗?全面解析来了-3.jpg

《507页Android开发相关源码解析》

面试Handler都没答上来,你真的了解它吗?全面解析来了-4.jpg

《379页Android开发面试宝典》
3.项目复盘
实际上,面试的一二轮所问到的技术问题,很多都是围绕着你的项目展开,因此在面试前最后要做好的一件事情就是项目复盘。关于项目复盘,我个人的思路如下,可供参考:
    你在这个项目中承担了什么样的角色?这个项目的背景是什么,如果是技术项目,为什么要做?有哪些技术难点,是怎么解决的,是否还有更好的方案?你认为项目中是否有可以改进的点?这个项目解决了什么问题,最好用数据说话,这个数据又是怎么得出来的?
提前把思路捋一捋,上面这些问题好好思考或准备一下,做到心中有谱以后,自然能够面试官聊得融洽,保持一个好的心态,通过的几率就会更大一些。

以上文章中的资料,均可以免费分享给大家来学习,
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图;
需要的朋友,直接转发+点赞+私信回复【资料】一键领取!!!
C语言圈
12406 查看 4 0 反对

说说我的看法高级模式

您需要登录后才可以回帖 登录|立即注册

  • 帘卷西风舞黛眉

    2021-2-24 18:26:18 使用道具

    来自: 北京来自: 北京来自: 北京来自: 北京
    如果队列里有消息 并且队列头是同步屏障消息,就去遍历队列找到最近的一个异步消息优先去执行 队列头没有同步屏障消息 就去取队列头消息去执行 如果队列没有消息 就调底层方法休眠阻塞
  • j_amanda

    2021-2-25 16:47:00 使用道具

    来自: 中国来自: 中国来自: 中国来自: 中国
    转发了
  • caozy1314520

    2021-2-26 19:16:09 使用道具

    来自: 北京来自: 北京来自: 北京来自: 北京
    转发了
  • 月亮之下吃饭

    2021-2-27 15:11:53 使用道具

    来自: 北京来自: 北京来自: 北京来自: 北京
    转发了

相关阅读