GVKun编程网logo

太赞了!2021京东最新Android面试真题解析,已拿offer(京东安卓面试题)

23

在这里,我们将给大家分享关于太赞了!2021京东最新Android面试真题解析,已拿offer的知识,让您更了解京东安卓面试题的本质,同时也会涉及到如何更有效地2017-2020历年字节跳动Andro

在这里,我们将给大家分享关于太赞了!2021京东最新Android面试真题解析,已拿offer的知识,让您更了解京东安卓面试题的本质,同时也会涉及到如何更有效地2017-2020历年字节跳动Android面试真题解析、2020Android面试重难点之Handler机制,含字节、京东、腾讯经典面试真题解析!、2021京东最新Android面试真题解析,文末领取面试资料、2021京东最新Android面试真题解析,深夜思考的内容。

本文目录一览:

太赞了!2021京东最新Android面试真题解析,已拿offer(京东安卓面试题)

太赞了!2021京东最新Android面试真题解析,已拿offer(京东安卓面试题)

前言

疫情一过,我相信将会是面试求职的高峰时期,如果此时手里有份高质量的面试宝典,那么你将得心应手面对考官各种问题。虽然不敢保证你能应聘上心仪的职位,但是能保证看完这些内容你的收获将超乎你的想象! 此份面试宝典搜集各大网络平台(如果侵权,请您告知),在此感谢他们的用心总结,才有这份足够全面的面试宝典!

内容点较丰富,建议找工作的小伙伴一定要慢慢细细品,我这里随意展示一下,保证不会让你失望!

一丶BAT相关面试点:

1.Binder通信原理和机制
2.多进程通信
3.组件化.插件化的区别以及如何选择
4.插件化的理解
5.热修复原理
6.对于AMS的认识
7.AOP 与OOP 有什么区别, AOP 的原理
8.QQ换肤原理以及大致思想
9.Android虚拟机
10.图片压缩的具体步骤和应用
11.反编译和加密如何进行
12.序列化和反序列化的原理
13.Handler的原理和认识

二丶算法合集笔记

1.Hash
2.最小生成树算法
3.最短路径算法
4.KMP算法
5.查找算法
6.排序算法

三丶线程.多线程.线程池和面试中的小问题

1.开启线程的方式
2.run()和 start()方法区别
3.如何控制某个方法允许并发访问线程的个数?
4.在 Java 中 wait 和 seelp 方法的不同
5.导致线程阻塞的原因
6.线程如何关闭的思路?
7.如何同步以及保证线程安全
8.减少APK包的大小
9.关于内存泄漏,内存抖动的优化以及解决方案

四丶小知识的清单

1.面向对象和面向对象的区别
2.Java和C++的区别
3.面向对象的特征
4.接口和抽象类的区别

五丶数据结构和设计模式

  1. 设计模式六大原则简要概述
  2. 设计模式的分类
    3.HashMap 实现原理

六丶java核心小知识清单

1 .线程中 sleep 和 wait 区别
2.Thread 中的 start()和 run()的区别
3.Java 中重载和重写的区别
4.Tcp /IP三次握手,四次挥手

七丶性能优化

1.图片的三级缓存中,图片加载到内存中,如果内存快爆了,会发生什么?怎么处理?
2.WebView 的性能优化 ?
3.加载一张高清图片应该占用多少内存
4.内存泄露和内存溢出的区别 ?AS 有什么工具可以检测内存泄露
5.Bitmap 如何处理大图,如何预防 OOM?
6.性能优化,怎么保证应用启动不卡顿? 黑白屏怎么处理?

最后

针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

  • Android前沿技术大纲

  • 全套体系化高级架构视频

资料领取:点赞+点击GitHub免费获取

往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。**

2017-2020历年字节跳动Android面试真题解析

2017-2020历年字节跳动Android面试真题解析

大家好!给大家介绍一下,这是我们持续更新整理的2017-2020字节跳动历年Android面试真题解析!

早在2017年我们就建了第一个字节跳动的面试群给大家讨论面试的东西。期间累计有1825个群友分享了自己的Android面试真经,并提供了参考答案。

这其中就有很多成员已经斩获今日头条、抖音等岗位的offer。有很多成员面试虽然失败了,但也分享了很多失败的经验教训。在这里一并对他们表示感谢!正是因为大家的奉献和支持,让我们的这份面试真题解析已经累计下载1082万次!


字节跳动Android面试真题解析目录如下:

第一章 计算机基础面试题

1、网络面试题 1

2、操作系统面试题 (⭐⭐⭐) 21

3、数据库面试题 (⭐) 23

第二章 数据结构和算法面试题

数据结构与算法 25

第三章 Java面试题

1、Java基础面试题 33

2、Java并发面试题 81

3、Java虚拟机面试题 (⭐⭐⭐) 121

第四章 Android面试题

1、Android基础面试题 (⭐⭐⭐) 140

2、Android高级面试题 (⭐⭐⭐) 208

第五章 其他扩展面试题

1、Kotlin (⭐⭐) 346

2、大前端 (⭐⭐) 346

3、脚本语言 (⭐⭐) 349

第六章 非技术面试题

1、高频题集 (⭐⭐⭐) 350

2、次高频题集 (⭐⭐) 352

字节跳动Android面试真题解析目录

每个问题我们都附上1个标准参考答案,都是我们反复摸索消化(真心花了很多时间),觉得写的比较好的文章作为答案。这样就可以节省大家自己去搜索的时间,把时间用在正确的东西上。

其实我们也可以直接以简易的、群友分享的答案写出来,但是这并帮助不了同学们去深刻理解,三思之下还是采用标准答案作为参考。不明白或者想通俗了解的,可加入我们字节跳动面试交流q群一起讨论,加入我们字节跳动Android面试群给大家讨论长篇or精简的答案,希望大家理解。下面是我们每章知识点的概述:

第一章 计算机基础面试题

字节跳动面试也会考察计算机基础,主要考察我们是否系统的学习了操作系统和计算机组成原理,因为只有我们看完操作系统后才能系统的认识计算机的原理。

第一章 计算机基础面试题

第二章 数据结构和算法面试题

对于算法面试准备,无疑就是刷《剑指Offer》+ LeetCode 效果最佳。刷《剑指Offer》是为了建立全面的算法面试思维,打下坚实的基础,刷LeetCode则是为了不断强化与开阔我们自己的算法思想。这两块 CS-Notes 中已经实现地很完美了,建议大家将《剑指Offer》刷完,然后再至少刷100道LeetCode题目以上。


《剑指Offer》


LeetCode中文版

第三章 Java面试题

Java 是 Android App 开发默认的语言, Android Framework 也是默认使用 Java 语言,熟练掌握 Java 语言是 Android 开发者的必备技能。当然也是我们字节跳动青睐的考题选择方向!

第三章 Java面试题

第四章 Android面试题

Android面试分为基础面试题+高级面试题两个部分。其中高级面试题部分的性能优化、Framework、三方源码属于我们考察的重点、难点方向!

第四章 Android面试题

第五章、第六章 其他扩展面试题+非技术面试题

Google 几年前就开始走“Kotlin First”的路线,目前很多官方的文档和 Demo 都是使用 Kotlin 语言作为默认,Kotlin 的重要性不言而喻。

第五章、第六章 其他扩展面试题+非技术面试题

简历制作+春招困惑解答+经典HR面试解析

以上是我们整理总结字节跳动Android面试遇到的历年真题解析,希望对大家有帮助;同时我们经常也会遇到很多关于简历制作,职业困惑、HR经典面试问题回答等有关面试的问题。同样的我们搜集整理了全套简历制作、春招困惑、HR面试等问题解析,我们在q群中,都提供了专业的解答(群号码:936903570)。

img

如何做好面试突击,规划学习方向?

面试题集可以帮助你查漏补缺,有方向有针对性的学习,为之后进大厂做准备。但是如果你仅仅是看一遍,而不去学习和深究。那么这份面试题对你的帮助会很有限。最终还是要靠资深技术水平说话。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。建议先制定学习计划,根据学习计划把知识点关联起来,形成一个系统化的知识体系。

学习方向很容易规划,但是如果只通过碎片化的学习,对自己的提升是很慢的。

我们搜集整理过这几年字节跳动,以及腾讯,阿里,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

我们在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多;

上述所有资料!均可免费分享!点击我 领取

扫码进群!联系管理员免费获取!

img

qq群号码:936903570

2020Android面试重难点之Handler机制,含字节、京东、腾讯经典面试真题解析!

2020Android面试重难点之Handler机制,含字节、京东、腾讯经典面试真题解析!

Handler 在整个 Android 开发体系中占据着很重要的地位,对开发者来说起到的作用很明确,就是为了实现线程切换或者是执行延时任务,稍微更高级一点的用法可能是为了保证多个任务在执行时的有序性。

由于 Android 系统中的主线程有特殊地位,所以像 EventBus 和 Retrofit 这类并非 Android 独有的三方库,都是通过 Handler 来实现对 Android 系统的特殊平台支持。大部分开发者都已经对如何使用 Handler 很熟悉了,这里就再来了解下其内部具体是如何实现的。

一、动手实现 Handler

本文不会一上来就直接介绍源码,而是会先根据我们想要实现的效果来反推源码,一步步来自己动手实现一个简单的 Handler

1、Message

首先,我们需要有个载体来表示要执行的任务,就叫它 Message 吧,Message 应该有什么参数呢?

  • 需要有一个唯一标识,因为要执行的任务可能有多个,我们要分得清哪个是哪个,用个 Int 类型变量就足够表示了
  • 需要能够承载数据,需要发送的数据类型会有很多种可能,那就直接用一个 Object 类型变量来表示吧,由开发者自己在使用时再来强转类型
  • 需要有一个 long 类型变量来表示任务的执行时间戳

所以,Message 类就应该至少包含以下几个字段:

/**
 * @Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc:
 * GitHub:https://github.com/leavesC
 */
public final class Message {
    //唯一标识
    public int what;
    //数据
    public Object obj;
    //时间戳
    public long when;
}

2、MessageQueue

因为 Message 并不是发送了就能够马上被消费掉,所以就肯定要有个可以用来存放的地方,就叫它 MessageQueue 吧,即消息队列。Message 可能需要延迟处理,那么 MessageQueue 在保存 Message 的时候就应该按照时间戳的大小来顺序存放,时间戳小的 Message 放在队列的头部,在消费 Message 的时候就直接从队列头取值即可

那么用什么数据结构来存放 Message 比较好呢?

  • 用数组?不太合适,数组虽然在遍历的时候会比较快,但需要预先就申请固定的内存空间,导致在插入数据和移除数据时可能需要移动大量数据。而 MessageQueue 可能随时会收到数量不定、时间戳大小不定的 Message,消费完 Message 后还需要将该其移出队列,所以使用数组并不合适
  • 用链表?好像可以,链表在插入数据和移除数据时只需要改变指针的引用即可,不需要移动数据,内存空间也只需要按需申请即可。虽然链表在随机访问的时候性能不高,但是对于 MessageQueue 而言无所谓,因为在消费 Message 的时候也只需要取队列头的值,并不需要随机访问

好了,既然决定用链表结构,那么 Message 就需要增加一个字段用于指向下一条消息才行

/**
 * @Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc:
 * GitHub:https://github.com/leavesC
 */
public final class Message {
    //唯一标识
    public int what;
    //数据
    public Object obj;
    //时间戳
    public long when;
    //下一个节点
    public Message next;
}

MessageQueue 需要提供一个 enqueueMessage方法用来向链表插入 Message,由于存在多个线程同时向队列发送消息的可能,所以方法内部还需要做下线程同步才行

/**
 * @Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc:
 * GitHub:https://github.com/leavesC
 */
public class MessageQueue {

    //链表中的第一条消息
    private Message mMessages;

    void enqueueMessage(Message msg, long when) {
        synchronized (this) {
            Message p = mMessages;
            //如果链表是空的,或者处于队头的消息的时间戳比 msg 要大,则将 msg 作为链表头部
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
            } else {
                Message prev;
                //从链表头向链表尾遍历,寻找链表中第一条时间戳比 msg 大的消息,将 msg 插到该消息的前面
                for (; ; ) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                }
                msg.next = p;
                prev.next = msg;
            }
        }
    }
}

此外,MessageQueue 要有一个可以获取队头消息的方法才行,就叫做next()吧。外部有可能会随时向 MessageQueue 发送 Message,next()方法内部就直接来开启一个无限循环来反复取值吧。如果当前队头的消息可以直接处理的话(即消息的时间戳小于或等于当前时间),那么就直接返回队头消息。而如果队头消息的时间戳比当前时间还要大(即队头消息是一个延时消息),那么就计算当前时间和队头消息的时间戳的差值,计算 next() 方法需要阻塞等待的时间,调用 nativePollOnce()方法来等待一段时间后再继续循环遍历

    //用来标记 next() 方法是否正处于阻塞等待的状态
    private boolean mBlocked = false;

    Message next() {
        int nextPollTimeoutMillis = 0;
        for (; ; ) {
            nativePollOnce(nextPollTimeoutMillis);
            synchronized (this) {
                //当前时间
                final long now = SystemClock.uptimeMillis();

                Message msg = mMessages;
                if (msg != null) {
                    if (now < msg.when) {
                        //如果当前时间还未到达消息的的处理时间,那么就计算还需要等待的时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //可以处理队头的消息了,第二条消息成为队头
                        mMessages = msg.next;
                        msg.next = null;
                        mBlocked = false;
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                mBlocked = true;
            }
        }
    }

    //将 next 方法的调用线程休眠指定时间
    private void nativePollOnce(long nextPollTimeoutMillis) {

    }

此时就需要考虑到一种情形:next()还处于阻塞状态的时候,外部向消息队列插入了一个可以立即处理或者是阻塞等待时间比较短的 Message。此时就需要唤醒休眠的线程,因此 enqueueMessage还需要再改动下,增加判断是否需要唤醒next()方法的逻辑

    void enqueueMessage(Message msg, long when) {
        synchronized (this) {
            //用于标记是否需要唤醒 next 方法
            boolean needWake = false;         
            Message p = mMessages;
            //如果链表是空的,或者处于队头的消息的时间戳比 msg 要大,则将 msg 作为链表头部
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;     
                //需要唤醒
                needWake = mBlocked;
            } else {
                Message prev;
                //从链表头向链表尾遍历,寻找链表中第一条时间戳比 msg 大的消息,将 msg 插到该消息的前面
                for (; ; ) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                }
                msg.next = p;
                prev.next = msg;
            }  
            if (needWake) {
                //唤醒 next() 方法
                nativeWake();
            }
        }
    }

    //唤醒 next() 方法
    private void nativeWake() {

    }

3、Handler

既然存放消息的地方已经确定就是 MessageQueue 了,那么自然就还需要有一个类可以用来向 MessageQueue 发送消息了,就叫它 Handler 吧。Handler 可以实现哪些功能呢?

  • 希望除了可以发送 Message 类型的消息外还可以发送 Runnable 类型的消息。这个简单,Handler 内部将 Runnable 包装为 Message 即可
  • 希望可以发送延时消息,以此来执行延时任务。这个也简单,用 Message 内部的 when 字段来标识希望任务执行时的时间戳即可
  • 希望可以实现线程切换,即从子线程发送的 Message 可以在主线程被执行,反过来也一样。这个也不难,子线程可以向一个特定的 mainMessageQueue 发送消息,然后让主线程负责循环从该队列中取消息并执行即可,这样不就实现了线程切换了吗?

所以说,Message 的定义和发送是由 Handler 来完成的,但 Message 的分发则可以交由其他线程来完成

根据以上需求:Runnable 要能够包装为 Message 类型,Message 的处理逻辑要交由 Handler 来定义,所以 Message 就还需要增加两个字段才行

/**
 * @Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc:
 * GitHub:https://github.com/leavesC
 */
public final class Message {
    //唯一标识
    public int what;
    //数据
    public Object obj;
    //时间戳
    public long when;
    //下一个节点
    public Message next;
    //用于将 Runnable 包装为 Message
    public Runnable callback;
    //指向 Message 的发送者,同时也是 Message 的最终处理者
    public Handler target;
}

Handler 至少需要包含几个方法:用于发送 Message 和 Runnable 的方法、用来处理消息的 handleMessage 方法、用于分发消息的 dispatchMessage方法

/**
 * @Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc:
 * GitHub:https://github.com/leavesC
 */
public class Handler {

    private MessageQueue mQueue;

    public Handler(MessageQueue mQueue) {
        this.mQueue = mQueue;
    }

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

    public final void postDelayed(Runnable r, long delayMillis) {
        sendMessageDelayed(getPostMessage(r), delayMillis);
    }

    public final void sendMessage(Message r) {
        sendMessageDelayed(r, 0);
    }

    public final void sendMessageDelayed(Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public void sendMessageAtTime(Message msg, long uptimeMillis) {
        msg.target = this;
        mQueue.enqueueMessage(msg, uptimeMillis);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = new Message();
        m.callback = r;
        return m;
    }

    //由外部来重写该方法,以此来消费 Message
    public void handleMessage(Message msg) {

    }

    //用于分发消息
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            msg.callback.run();
        } else {
            handleMessage(msg);
        }
    }

}

之后,子线程就可以像这样来使用 Handler 了:将子线程持有的 Handler 对象和主线程关联的 mainMessageQueue 绑定在一起,主线程负责循环从 mainMessageQueue 取出 Message 后再来调用 Handler 的 dispatchMessage 方法,以此实现线程切换的目的

        Handler handler = new Handler(mainThreadMessageQueue) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 1: {
                        String ob = (String) msg.obj;
                        break;
                    }
                    case 2: {
                        List<String> ob = (List<String>) msg.obj;
                        break;
                    }
                }
            }
        };
        Message messageA = new Message();
        messageA.what = 1;
        messageA.obj = "https://github.com/leavesC";
        Message messageB = new Message();
        messageB.what = 2;
        messageB.obj = new ArrayList<String>();
        handler.sendMessage(messageA);
        handler.sendMessage(messageB);

4、Looper

现在就再来想想怎么让 Handler 拿到和主线程关联的 MessageQueue,以及主线程怎么从 MessageQueue 获取 Message 并回调 Handler。这之间就一定需要一个中转器,就叫它 Looper 吧。Looper 具体需要实现什么功能呢?

  • 每个 Looper 对象应该都是对应一个独有的 MessageQueue 实例和 Thread 实例,这样子线程和主线程才可以互相发送 Message 交由对方线程处理
  • Looper 内部需要开启一个无限循环,其关联的线程就负责从 MessageQueue 循环获取 Message 进行处理
  • 因为主线程较为特殊,所以和主线程关联的 Looper 对象要能够被子线程直接获取到,可以考虑将其作为静态变量存着

这样,Looper 的大体框架就出来了。通过 ThreadLocal 来为不同的线程单独维护一个 Looper 实例,每个线程通过 prepare()方法来初始化本线程独有的 Looper 实例 ,再通过 myLooper()方法来获取和当前线程关联的 Looper 对象,和主线程关联的 sMainLooper 作为静态变量存在,方便子线程获取

/**
 * @Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc:
 * GitHub:https://github.com/leavesC
 */
final class Looper {

    final MessageQueue mQueue;

    final Thread mThread;

    private static Looper sMainLooper;

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

    public static void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }

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

    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

    public static Looper myLooper() {
        return sThreadLocal.get();
    }

}

Looper 还需要有一个用于循环从 MessageQueue 获取消息并处理的方法,就叫它loop()吧。其作用也能简单,就是循环从 MessageQueue 中取出 Message,然后将 Message 再反过来分发给 Handler 即可

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn''t called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        for (; ; ) {
            Message msg = queue.next();//可能会阻塞
            msg.target.dispatchMessage(msg);
        }
    }

这样,主线程就先通过调用prepareMainLooper()来完成 sMainLooper 的初始化,然后调用loop()开始向 mainMessageQueue 循环取值并进行处理,没有消息的话主线程就暂时休眠着。子线程拿到 sMainLooper 后就以此来初始化 Handler,这样子线程向 Handler 发送的消息就都会被存到 mainMessageQueue 中,最终在主线程被消费掉

5、做一个总结

这样一步步走下来后,读者对于 Message、MessageQueue、Handler、Looper 这四个类的定位就应该都很清晰了吧?不同线程之间就可以依靠拿到对方的 Looper 来实现消息的跨线程处理了

例如,对于以下代码,即使 Handler 是在 otherThread 中进行初始化,但 handleMessage 方法最终是会在 mainThread 被调用执行的,

        Thread mainThread = new Thread() {
            @Override
            public void run() {
                //初始化 mainLooper
                Looper.prepareMainLooper();
                //开启循环
                Looper.loop();
            }
        };

        Thread otherThread = new Thread() {
            @Override
            public void run() {
                Looper mainLooper = Looper.getMainLooper();
                Handler handler = new Handler(mainLooper.mQueue) {
                    @Override
                    public void handleMessage(Message msg) {
                        switch (msg.what) {
                            case 1: {
                                String ob = (String) msg.obj;
                                break;
                            }
                            case 2: {
                                List<String> ob = (List<String>) msg.obj;
                                break;
                            }
                        }
                    }
                };
                Message messageA = new Message();
                messageA.what = 1;
                messageA.obj = "https://github.com/leavesC";
                Message messageB = new Message();
                messageB.what = 2;
                messageB.obj = new ArrayList<String>();
                handler.sendMessage(messageA);
                handler.sendMessage(messageB);
            }
        };

再来做个简单的总结:

  • Message:用来表示要执行的任务
  • Handler:子线程持有的 Handler 如果绑定到的是主线程的 MessageQueue 的话,那么子线程发送的 Message 就可以由主线程来消费,以此来实现线程切换,执行 UI 更新操作等目的
  • MessageQueue:即消息队列,通过 Handler 发送的消息并非都是立即执行的,需要先按照 Message 的优先级高低(延时时间的长短)保存到 MessageQueue 中,之后再来依次执行
  • Looper:Looper 用于从 MessageQueue 中循环获取 Message 并将之传递给消息处理者(即消息发送者 Handler 本身)来进行消费,每条 Message 都有个 target 变量用来指向 Handler,以此把 Message 和其处理者关联起来。不同线程之间通过互相拿到对方的 Looper 对象,以此来实现跨线程发送消息

有了以上的认知基础后,下面就来看看实际的源码实现 ~ ~

二、Handler 源码

1、Handler 如何初始化

Handler 的构造函数一共有七个,除去两个已经废弃的和三个隐藏的,实际上开发者可以使用的只有两个。而不管是使用哪个构造函数,最终的目的都是为了完成 mLooper、mQueue、mCallback、mAsynchronous 这四个常量的初始化,同时也可以看出来 MessageQueue 是由 Looper 来完成初始化的,而且 Handler 对于 Looper 和 MessageQueue 都是一对一的关系,一旦初始化后就不可改变

大部分开发者使用的应该都是 Handler 的无参构造函数,而在 Android 11 中 Handler 的无参构造函数已经被标记为废弃的了。Google 官方更推荐的做法是通过显式传入 Looper 对象来完成初始化,而非隐式使用当前线程关联的 Looper

Handler 对于 Looper 和 MessageQueue 都是一对一的关系,但是 Looper 和 MessageQueue 对于 Handler 可以是一对多的关系,这个后面会讲到
    @UnsupportedAppUsage
    final Looper mLooper;
    final MessageQueue mQueue;
    @UnsupportedAppUsage
    final Callback mCallback;
    final boolean mAsynchronous;

    //省略其它构造函数

    /**
     * @hide
     */
    public Handler(@Nullable Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

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

2、Looper 如何初始化

在初始化 Handler 时,如果外部调用的构造函数没有传入 Looper,那就会调用Looper.myLooper()来获取和当前线程关联的 Looper 对象,再从 Looper 中取 MessageQueue。如果获取到的 Looper 对象为 null 就会抛出异常。根据异常信息 Can''t create handler inside thread that has not called Looper.prepare() 可以看出来,在初始化 Handler 之前需要先调用 Looper.prepare()完成 Looper 的初始化

走进 Looper 类中可以看到,myLooper()方法是 Looper 类的静态方法,其只是单纯地从 sThreadLocal 变量中取值并返回而已。sThreadLocal 又是通过 prepare(boolean) 方法来进行初始化赋值的,且只能赋值一次,重复调用将抛出异常

我们知道,ThreadLocal 的特性就是可以为不同的线程分别维护单独的一个变量实例,所以,不同的线程就会分别对应着不同的 Looper 对象,是一一对应的关系

      @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

    /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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));
    }

此外,Looper 类的构造函数也是私有的,会初始化两个常量值:mQueue 和 mThread,这说明了 Looper 对于 MessageQueue 和 Thread 都是一一对应的关系,关联之后不能改变

    @UnsupportedAppUsage
    final MessageQueue mQueue;

    final Thread mThread;    

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

在日常开发中,我们在通过 Handler 来执行 UI 刷新操作时,经常使用的是 Handler 的无参构造函数,那么此时肯定就是使用了和主线程关联的 Looper 对象,对应 Looper 类中的静态变量 sMainLooper

    @UnsupportedAppUsage
    private static Looper sMainLooper;  // guarded by Looper.class

    //被标记为废弃的原因是因为 sMainLooper 会交由 Android 系统自动来完成初始化,外部不应该主动来初始化
    @Deprecated
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

    /**
     * Returns the application''s main looper, which lives in the main thread of the application.
     */
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

prepareMainLooper()就用于为主线程初始化 Looper 对象,该方法又是由 ActivityThread 类的 main() 方法来调用的。该 main() 方法即 Java 程序的运行起始点,所以当应用启动时系统就自动为我们在主线程做好了 mainLooper 的初始化,而且已经调用了Looper.loop()方法开启了消息的循环处理,应用在使用过程中的各种交互逻辑(例如:屏幕的触摸事件、列表的滑动等)就都是在这个循环里完成分发的

正是因为 Android 系统已经自动完成了主线程 Looper 的初始化,所以我们在主线程中才可以直接使用 Handler 的无参构造函数来完成 UI 相关事件的处理

public final class ActivityThread extends ClientTransactionHandler {

    public static void main(String[] args) {
        ···
        Looper.prepareMainLooper();
        ···
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

3、Handler 发送消息

Handler 用于发送消息的方法非常多,有十几个,其中大部分最终调用到的都是 sendMessageAtTime() 方法。uptimeMillis 即 Message 具体要执行的时间戳,如果该时间戳比当前时间大,那么就意味着要执行的是延迟任务。如果为 mQueue 为 null,就会打印异常信息并直接返回,因为 Message 是需要交由 MessageQueue 来处理的

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

需要注意 msg.target = this 这句代码,target 指向了发送消息的主体,即 Handler 对象本身,即由 Handler 对象发给 MessageQueue 的消息最后还是要交由 Handler 对象本身来处理

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //将消息交由 MessageQueue 处理
        return queue.enqueueMessage(msg, uptimeMillis);
    }

4、MessageQueue

MessageQueue 通过 enqueueMessage 方法来接收消息

  • 因为存在多个线程同时往一个 MessageQueue 发送消息的可能,所以 enqueueMessage 内部肯定需要进行线程同步
  • 可以看出 MessageQueue 内部是以链表的结构来存储 Message 的(Message.next),根据 Message 的时间戳大小来决定其在消息队列中的位置
  • mMessages 代表的是消息队列中的第一条消息。如果 mMessages 为空(消息队列是空的),或者 mMessages 的时间戳要比新消息的时间戳大,则将新消息插入到消息队列的头部;如果 mMessages 不为空,则寻找消息列队中第一条触发时间比新消息晚的非空消息,将新消息插到该消息的前面

到此,一个按照时间戳大小进行排序的消息队列就完成了,后边要做的就是从消息队列中依次取出消息进行处理了

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            ···
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            //用于标记是否需要唤醒线程
            boolean needWake;
            //如果链表是空的,或者处于队头的消息的时间戳比 msg 要大,则将 msg 作为链表头部
            //when == 0 说明 Handler 调用的是 sendMessageAtFrontOfQueue 方法,直接将 msg 插到队列头部 
            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 {
                //如果当前线程处于休眠状态 + 队头消息是屏障消息 + msg 是异步消息
                //那么就需要唤醒线程
                needWake = mBlocked && p.target == null && msg.isAsynchronous();

                Message prev;
                //从链表头向链表尾遍历,寻找链表中第一条时间戳比 msg 大的消息,将 msg 插到该消息的前面
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        //如果在 msg 之前队列中还有异步消息那么就不需要主动唤醒
                        //因为已经设定唤醒时间了
                        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 是如何保存的了,再来看下 MessageQueue 是如何取出 Message 并回调给 Handler 的。在 MessageQueue 中读取消息的操作对应的是next() 方法。next() 方法内部开启了一个无限循环,如果消息队列中没有消息或者是队头消息还没到可以处理的时间,该方法就会导致 Loop 线程休眠挂起,直到条件满足后再重新遍历消息

    @UnsupportedAppUsage
    Message next() {
        ···
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            //将 Loop 线程休眠挂起
            nativePollOnce(ptr, nextPollTimeoutMillis);

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

next() 方法又是通过 Looper 类的 loop() 方法来循环调用的,loop() 方法内也是一个无限循环,唯一跳出循环的条件就是 queue.next()方法返回为 null。因为 next() 方法可能会触发阻塞操作,所以没有消息需要处理时也会导致 loop() 方法被阻塞着,而当 MessageQueue 有了新的消息,Looper 就会及时地处理这条消息并调用 msg.target.dispatchMessage(msg) 方法将消息回传给 Handler 进行处理

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn''t called on this thread.");
        }
        ···    
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ···
            msg.target.dispatchMessage(msg);
            ···
        }
    }

Handler 的dispatchMessage方法就是在向外部分发 Message 了。至此,Message 的整个分发流程就结束了

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

5、消息屏障

Android 系统为了保证某些高优先级的 Message(异步消息) 能够被尽快执行,采用了一种消息屏障(Barrier)机制。其大致流程是:先发送一个屏障消息到 MessageQueue 中,当 MessageQueue 遍历到该屏障消息时,就会判断当前队列中是否存在异步消息,有的话则先跳过同步消息(开发者主动发送的都属于同步消息),优先执行异步消息。这种机制就会使得在异步消息被执行完之前,同步消息都不会得到处理

Handler 的构造函数中的async参数就用于控制发送的 Message 是否属于异步消息

    public class Handler {

        final boolean mAsynchronous;

        public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
            mAsynchronous = async;
        }

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

    }

MessageQueue 在取队头消息的时候,如果判断到队头消息就是屏障消息的话,那么就会向后遍历找到第一条异步消息优先进行处理

    @UnsupportedAppUsage
    Message next() {
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);
            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) { //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());
                }
            }
        }
    }

6、退出 Looper 循环

Looper 类本身做了方法限制,除了主线程外,子线程关联的 MessageQueue 都支持退出 Loop 循环,即 quitAllowed 只有主线程才能是 false

public final class Looper {

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

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

}

MessageQueue 支持两种方式来退出 Loop:

  • safe 为 true,只移除所有尚未执行的消息,不移除时间戳等于当前时间的消息
  • safe 为 false,移除所有消息
    void quit(boolean safe) {
        if (!mQuitAllowed) {
            //MessageQueue 设置了不允许退出循环,直接抛出异常
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
        synchronized (this) {
            if (mQuitting) {
                //避免重复调用
                return;
            }
            mQuitting = true;
            if (safe) {
                //只移除所有尚未执行的消息,不移除时间戳等于当前时间的消息
                removeAllFutureMessagesLocked();
            } else {
                //移除所有消息
                removeAllMessagesLocked();
            }
            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

7、IdleHandler

IdleHandler 是 MessageQueue 的一个内部接口,可以用于在 Loop 线程处于空闲状态的时候执行一些优先级不高的操作

    public static interface IdleHandler {
        boolean queueIdle();
    }

MessageQueue 在获取队头消息时,如果发现当前没有需要执行的 Message 的话,那么就会去遍历 mIdleHandlers,依次执行 IdleHandler

    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

    @UnsupportedAppUsage
    Message next() {
        ···
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            ···
            synchronized (this) {
                ···
                //如果队头消息 mMessages 为 null 或者 mMessages 需要延迟处理
                //那么就来执行 IdleHandler
                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);
            }
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler
                boolean keep = false;
                try {
                    //执行 IdleHandler
                    //如果返回 false 的话说明之后不再需要执行,那就将其移除
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
        }
    }

例如,ActivityThread 就向主线程 MessageQueue 添加了一个 GcIdler,用于在主线程空闲时尝试去执行 GC 操作

public final class ActivityThread extends ClientTransactionHandler {

    @UnsupportedAppUsage
    void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            //添加 IdleHandler
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

    final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            //尝试 GC
            doGcIfNeeded();
            purgePendingResources();
            return false;
        }
    }

}

8、做一个总结

再来总结下以上的所有内容

  1. 每个 Handler 都会和一个 Looper 实例关联在一起,可以在初始化 Handler 时通过构造函数主动传入实例,否则就会默认使用和当前线程关联的 Looper 对象
  2. 每个 Looper 都会和一个 MessageQueue 实例关联在一起,每个线程都需要通过调用 Looper.prepare()方法来初始化本线程独有的 Looper 实例,并通过调用Looper.loop()方法来使得本线程循环向 MessageQueue 取出消息并执行。Android 系统默认会为每个应用初始化和主线程关联的 Looper 对象,并且默认就开启了 loop 循环来处理主线程消息
  3. MessageQueue 按照链接结构来保存 Message,执行时间早(即时间戳小)的 Message 会排在链表的头部,Looper 会循环从链表中取出 Message 并回调给 Handler,取值的过程可能会包含阻塞操作
  4. Message、Handler、Looper、MessageQueue 这四者就构成了一个生产者和消费者模式。Message 相当于产品,MessageQueue 相当于传输管道,Handler 相当于生产者,Looper 相当于消费者
  5. Handler 对于 Looper、Handler 对于 MessageQueue、Looper 对于 MessageQueue、Looper 对于 Thread ,这几个之间都是一一对应的关系,在关联后无法更改,但 Looper 对于 Handler、MessageQueue 对于 Handler 可以是一对多的关系
  6. Handler 能用于更新 UI 包含了一个隐性的前提条件:Handler 与主线程 Looper 关联在了一起。在主线程中初始化的 Handler 会默认与主线程 Looper 关联在一起,所以其 handleMessage(Message msg) 方法就会由主线程来调用。在子线程初始化的 Handler 如果也想执行 UI 更新操作的话,则需要主动获取 mainLooper 来初始化 Handler
  7. 对于我们自己在子线程中创建的 Looper,当不再需要的时候我们应该主动退出循环,否则子线程将一直无法得到释放。对于主线程 Loop 我们则不应该去主动退出,否则将导致应用崩溃
  8. 我们可以通过向 MessageQueue 添加 IdleHandler 的方式,来实现在 Loop 线程处于空闲状态的时候执行一些优先级不高的任务。例如,假设我们有个需求是希望当主线程完成界面绘制等事件后再执行一些 UI 操作,那么就可以通过 IdleHandler 来实现,这可以避免拖慢用户看到首屏页面的速度

三、Handler 在系统中的应用

1、HandlerThread

HandlerThread 是 Android SDK 中和 Handler 在同个包下的一个类,从其名字就可以看出来它是一个线程,而且使用到了 Handler

其用法类似于以下代码。通过 HandlerThread 内部的 Looper 对象来初始化 Handler,同时在 Handler 中声明需要执行的耗时任务,主线程通过向 Handler 发送消息来触发 HandlerThread 去执行耗时任务

class MainActivity : AppCompatActivity() {

    private val handlerThread = HandlerThread("I am HandlerThread")

    private val handler by lazy {
        object : Handler(handlerThread.looper) {
            override fun handleMessage(msg: Message) {
                Thread.sleep(2000)
                Log.e("MainActivity", "这里是子线程,可以用来执行耗时任务:" + Thread.currentThread().name)
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn_test.setOnClickListener {
            handler.sendEmptyMessage(1)
        }
        handlerThread.start()
    }

}

HandlerThread 的源码还是挺简单的,只有一百多行

HandlerThread 是 Thread 的子类,其作用就是为了用来执行耗时任务,其 run()方法会自动为自己创建一个 Looper 对象并保存到 mLooper,之后就主动开启消息循环,这样 HandlerThread 就会来循环处理 Message 了

public class HandlerThread extends Thread {

    //线程优先级
    int mPriority;
    //线程ID
    int mTid = -1;
    //当前线程持有的 Looper 对象
    Looper mLooper;

    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        //触发当前线程创建 Looper 对象
        Looper.prepare();
        synchronized (this) {
            //获取 Looper 对象
            mLooper = Looper.myLooper();
            //唤醒所有处于等待状态的线程
            notifyAll();
        }
        //设置线程优先级
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        //开启消息循环
        Looper.loop();
        mTid = -1;
    }

}

此外,HandlerThread 还包含一个getLooper()方法用于获取 Looper。当我们在外部调用handlerThread.start()启动线程后,由于其run()方法的执行时机依然是不确定的,所以 getLooper()方法就必须等到 Looper 初始化完毕后才能返回,否则就会由于wait()方法而一直阻塞等待。当run()方法初始化 Looper 完成后,就会调用notifyAll()来唤醒所有处于等待状态的线程。所以外部在使用 HandlerThread 前就记得必须先调用 start() 方法来启动 HandlerThread

    //获取与 HandlerThread 关联的 Looper 对象
    //因为 getLooper() 可能先于 run() 被执行
    //所以当 mLooper 为 null 时调用者线程就需要阻塞等待 Looper 对象创建完毕
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

HandlerThread 起到的作用就是方便了主线程和子线程之间的交互,主线程可以直接通过 Handler 来声明耗时任务并交由子线程来执行。使用 HandlerThread 也方便在多个线程间共享,主线程和其它子线程都可以向 HandlerThread 下发任务,且 HandlerThread 可以保证多个任务执行时的有序性

2、IntentService

IntentService 是系统提供的 Service 子类,用于在后台串行执行耗时任务,在处理完所有任务后会自动停止,不必来手动调用 stopSelf() 方法。而且由于IntentService 是四大组件之一,拥有较高的优先级,不易被系统杀死,因此适合用于执行一些高优先级的异步任务

Google 官方以前也推荐开发者使用 IntentService,但是在 Android 11 中已经被标记为废弃状态了,但这也不妨碍我们来了解下其实现原理

IntentService 内部依靠 HandlerThread 来实现,其 onCreate()方法会创建一个 HandlerThread,拿到 Looper 对象来初始化 ServiceHandler。ServiceHandler 会将其接受到的每个 Message 都转交由抽象方法 onHandleIntent来处理,子类就通过实现该方法来声明耗时任务

public abstract class IntentService extends Service {

    private volatile Looper mServiceLooper;
    @UnsupportedAppUsage
    private volatile ServiceHandler mServiceHandler;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        //触发 HandlerThread 创建 Looper 对象
        thread.start();
        //获取 Looper 对象,构建可以向 HandlerThread 发送 Message 的 Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);

}

每次 start IntentService 时,onStart()方法就会被调用,将 intentstartId 包装为一个 Message 对象后发送给mServiceHandler。需要特别注意的是 startId 这个参数,它用于唯一标识每次对 IntentService 发起的任务请求,每次回调 onStart() 方法时,startId 的值都是自动递增的。IntentService 不应该在处理完一个 Message 之后就立即停止 IntentService,因为此时 MessageQueue 中可能还有待处理的任务还未取出来,所以如果当调用 stopSelf(int)方法时传入的参数不等于当前最新的 startId 值的话,那么stopSelf(int) 方法就不会导致 IntentService 被停止,从而避免了将尚未处理的 Message 给遗漏了

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

四、Handler 在三方库中的应用

1、EventBus

EventBus 的 Github 上有这么一句介绍:EventBus is a publish/subscribe event bus for Android and Java. 这说明了 EventBus 是普遍适用于 Java 环境的,只是对 Android 系统做了特殊的平台支持而已。EventBus 的四种消息发送策略包含了ThreadMode.MAIN 用于指定在主线程进行消息回调,其内部就是通过 Handler 来实现的

EventBusBuilder 会去尝试获取 MainLooper,如果拿得到的话就可以用来初始化 HandlerPoster,从而实现主线程回调

    MainThreadSupport getMainThreadSupport() {
        if (mainThreadSupport != null) {
            return mainThreadSupport;
        } else if (AndroidLogger.isAndroidLogAvailable()) {
            Object looperOrNull = getAndroidMainLooperOrNull();
            return looperOrNull == null ? null :
                    new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
        } else {
            return null;
        }
    }

    static Object getAndroidMainLooperOrNull() {
        try {
            return Looper.getMainLooper();
        } catch (RuntimeException e) {
            // Not really a functional Android (e.g. "Stub!" maven dependencies)
            return null;
        }
    }
public class HandlerPoster extends Handler implements Poster {
    protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
        super(looper);
    }
    @Override
    public void handleMessage(Message msg) {
  
    }
}

2、Retrofit

和 EventBus 一样,Retrofit 的内部实现也不需要依赖于 Android 平台,而是可以用于任意的 Java 客户端,Retrofit 只是对 Android 平台进行了特殊实现而已

在构建 Retrofit 对象的时候,我们可以选择传递一个 Platform 对象用于标记调用方所处的平台

public static final class Builder {

    private final Platform platform;

    Builder(Platform platform) {
      this.platform = platform;
    }
}

Platform 类只具有一个唯一子类,即 Android 类。其主要逻辑就是重写了父类的 defaultCallbackExecutor()方法,通过 Handler 来实现在主线程回调网络请求结果

static final class Android extends Platform {

    @Override
    public Executor defaultCallbackExecutor() {
      return new MainThreadExecutor();
    }
    static final class MainThreadExecutor implements Executor {
      private final Handler handler = new Handler(Looper.getMainLooper());
      @Override
      public void execute(Runnable r) {
        handler.post(r);
      }
    }
  }

五、面试环节

1.Handler

  • Handler Looper Message 关系是什么?
  • Messagequeue 的数据结构是什么?为什么要用这个数据结构?
  • 如何在子线程中创建 Handler?
  • Handler post 方法原理?
  • Android消息机制的原理及源码解析
  • Android Handler 消息机制

由于篇幅有限,仅展示部分内容,所有的知识点 整理的详细内容都放在了我的【GitHub】,有需要的朋友自取。

2.Activity 相关

  • 启动模式以及使用场景?
  • onNewIntent()与onConfigurationChanged()
  • onSaveInstanceState()与onRestoreInstanceState()
  • Activity 到底是如何启动的
  • 启动模式以及使用场景
  • onSaveInstanceState及onRestoreInstanceState使用
  • onConfigurationChanged使用以及问题解决
  • Activity 启动流程解析

3.Fragment

  • Fragment 生命周期和 Activity 对比
  • Fragment 之间如何进行通信
  • Fragment的startActivityForResult
  • Fragment重叠问题
  • Fragment 初探
  • Fragment 重叠, 如何通信
  • Fragment生命周期

由于篇幅有限,仅展示部分内容,所有的知识点 整理的详细内容都放在了我的【GitHub】,有需要的朋友自取。

4.Service 相关

  • 进程保活
  • Service的运行线程(生命周期方法全部在主线程)
  • Service启动方式以及如何停止
  • ServiceConnection里面的回调方法运行在哪个线程?
  • startService 和 bingService区别
  • 进程保活一般套路
  • 关于进程保活你需要知道的一切

5.Android布局优化

  • 什么情况下使用 ViewStub、include、merge?
  • 他们的原理是什么?
  • ViewStub、include、merge概念解析
  • Android布局优化之ViewStub、include、merge使用与源码分析

6.BroadcastReceiver 相关

  • 注册方式,优先级
  • 广播类型,区别
  • 广播的使用场景,原理
  • Android广播动态静态注册
  • 常见使用以及流程解析
  • 广播源码解析

#### 7.AsyncTask相关

  • AsyncTask是串行还是并行执行?
  • AsyncTask随着安卓版本的变迁
  • AsyncTask完全解析
  • 串行还是并行

8.Android 事件分发机制

  • onTouch和onTouchEvent区别,调用顺序
  • dispatchTouchEvent,onTouchEvent,onInterceptTouchEvent 方法顺序以及使用场景
  • 滑动冲突,如何解决
  • 事件分发机制
  • 事件分发解析
  • dispatchTouchEvent,onTouchEvent,onInterceptTouchEvent方法的使用场景解析

由于篇幅有限,仅展示部分内容,所有的知识点 整理的详细内容都放在了我的【GitHub】,有需要的朋友自取。

对于Android开发的朋友来说应该是非常完整的面试资料了,为了更好地整理每个模块,我参考了很多网上的优质博文和项目,力求不漏掉每一个知识点。很多朋友靠着这些内容进行复习,拿到了BATJ等大厂的offer,这个资料也已经帮助了很多的安卓开发者,希望也能帮助到你。

2021京东最新Android面试真题解析,文末领取面试资料

2021京东最新Android面试真题解析,文末领取面试资料

除了Bug,最让你头疼的问题是什么?单身?秃头?996?面试造火箭,工作拧螺丝?

作为安卓开发者,除了Bug,经常会碰到下面这些问题:

应用卡顿,丢帧,屏幕画面撕裂,操作界面刷新缓慢,UI不美观,布局混乱…这些问题频发的话,年后可能就不用来了。

开发App的时候,你是否会觉得界面卡顿?尤其是自定义view的时候。

Android 应用的卡顿、丢帧等,这些影响用户体验的因素绝大部分都与 16ms 这个值有关。Android 设备的刷新率也是 60Hz,Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,如果超过了16ms,我们则认为发生了卡顿。

一、Android面试题

Android面试题包括Android基础,还有一些源码级别的、原理这些等。所以想去美团面试,一定要多看看源码和实现方式,常用框架可以试试自己能不能手写实现一下,锻炼一下自己。 (一)Android基础知识点

  • 四大组件是什么

  • 四大组件的生命周期和简单用法

  • Activity之间的通信方式

  • Activity各种情况下的生命周期

  • 横竖屏切换的时候,Activity 各种情况下的生命周期

  • Activity与Fragment之间生命周期比较

  • Activity上有Dialog的时候按Home键时的生命周期

  • 两个Activity 之间跳转时必然会执行的是哪几个方法?

  • 前台切换到后台,然后再回到前台,Activity生命周期回调方法。弹出Dialog,生命值周期回调方法。

  • Activity的四种启动模式对比

  • Activity状态保存于恢复

  • fragment各种情况下的生命周期

  • Fragment状态保存startActivityForResult是哪个类的方法,在什么情况下使用?

  • 如何实现Fragment的滑动?

  • fragment之间传递数据的方式?

  • Activity 怎么和Service 绑定?

  • 怎么在Activity 中启动自己对应的Service?

  • service和activity怎么进行数据交互?

  • Service的开启方式

  • 请描述一下Service 的生命周期

  • 谈谈你对ContentProvider的理解

  • 说说ContentProvider、ContentResolver、ContentObserver 之间的关系

  • 请描述一下广播broadcastReceiver的理解

  • 广播的分类

  • 广播使用的方式和场景

  • 在manifest 和代码中如何注册和使用broadcastReceiver?

  • 本地广播和全局广播有什么差别?

  • broadcastReceiver,LocalbroadcastReceiver 区别

  • AlertDialog,popupWindow,Activity区别

  • Application 和 Activity 的 Context 对象的区别

  • Android属性动画特性

  • 如何导入外部数据库?

  • LinearLayout、RelativeLayout、FrameLayout的特性及对比,并介绍使用场景。

  • 谈谈对接口与回调的理解

  • 回调的原理

  • 写一个回调demo

  • 介绍下SurfView

  • RecycleView的使用

  • 序列化的作用,以及Android两种序列化的区别

  • 差值器

  • 估值器

  • Android中数据存储方式

    image

(二)Android源码相关分析

  • Android动画框架实现原理

  • Android各个版本API的区别

  • Requestlayout,onlayout,onDraw,DrawChild区别与联系

  • invalidate和postInvalidate的区别及使用

  • Activity-Window-View三者的差别

  • 谈谈对Volley的理解

  • 如何优化自定义view

  • 低版本SDK如何实现高版本api?

  • 描述一次网络请求的流程

  • HttpUrlConnection 和 okhttp关系

  • Bitmap对象的理解

  • looper架构

  • ActivityThread,AMS,WMS的工作原理

  • 自定义view如何考虑机型适配

  • 自定义view的事件

  • AstncTask+HttpClient 与 AsyncHttpClient有什么区别?

  • LaunchMode应用场景

  • AsyncTask 如何使用?

  • SpareArray原理

  • 请介绍下ContentProvider 是如何实现数据共享的?

  • AndroidService与Activity之间通信的几种方式

  • IntentService原理及作用是什么?

  • 说说Activity、Intent、Service 是什么关系

  • ApplicationContext和ActivityContext的区别

  • SP是进程同步的吗?有什么方法做到同步?

  • 谈谈多线程在Android中的使用

  • 进程和 Application 的生命周期

  • 封装View的时候怎么知道view的大小

  • RecycleView原理

  • AndroidManifest的作用与理解

    image

    (三)常见的一些原理性问题

  • Handler机制和底层实现

  • Handler、Thread和HandlerThread的差别

  • handler发消息给子线程,looper怎么启动?

  • 关于Handler,在任何地方new Handler 都是什么线程下?

  • ThreadLocal原理,实现及如何保证Local属性?

  • 请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系

  • 请描述一下View事件传递分发机制

  • Touch事件传递流程

  • 事件分发中的onTouch 和onTouchEvent 有什么区别,又该如何使用?

  • View和ViewGroup分别有哪些事件分发相关的回调方法

  • View刷新机制

  • View绘制流程

  • 自定义控件原理

  • 自定义view如何提供获取View属性的接口?

  • Android代码中实现WAP方式联网

  • AsyncTask机制

  • AsyncTask原理及不足

  • 如何取消AsyncTask?

  • 为什么不能在子线程更新UI?

  • ANR产生的原因是什么?

  • ANR定位和修正

  • oom是什么?

  • 什么情况导致oom?

  • 有什么解决方法可以避免OOM?

  • Oom 是否可以try catch?为什么?

  • 内存泄漏是什么?

  • 什么情况导致内存泄漏?

  • 如何防止线程的内存泄漏?

  • 内存泄露场的解决方法

  • 内存泄漏和内存溢出区别?

  • LruCache默认缓存大小

  • ContentProvider的权限管理(解答:读写分离,权限控制-精确到表级,URL控制)

  • 如何通过广播拦截和abort一条短信?

  • 广播是否可以请求网络?

  • 广播引起anr的时间限制是多少?

  • 计算一个view的嵌套层级

  • Activity栈

  • Android线程有没有上限?

  • 线程池有没有上限?

  • Android为什么引入Parcelable?

  • 有没有尝试简化Parcelable的使用?

    image

二、Java面试题

熟练掌握java是很关键的,大公司不仅仅要求你会使用几个api,更多的是要你熟悉源码实现原理,甚至要你知道有哪些不足,怎么改进,还有一些java有关的一些算法,设计模式等等。 (一、)Java

  • HashMap 和 HashTable 以及 CurrentHashMap 的区别。

  • synchronized 和 volatile 、reentrantlock 、CAS 的区别。

  • JVM 类加载机制、垃圾回收算法对比、Java 虚拟机结构等。

  • Java 的四大引用

  • Java 的泛型,<? super T> 和 <? extends T> 的区别。

  • Java 线程有哪些状态,有哪些锁,各种锁的区别。

  • final 、finally、finalize 区别。

  • 接口和抽象类的区别。

  • sleep 、wait、yield 的区别,wait 的线程如何唤醒它?

    image

三、高级开发技术面试题

一、图片

1、图片库对比 2、LRUCache原理 3、图片加载原理 4、自己去实现图片库,怎么做? 5、Glide源码解析 6、Glide使用什么缓存? 7、Glide内存缓存如何控制大小?

image

二、网络和安全机制

1.网络框架对比和源码分析
2.自己去设计网络请求框架,怎么做?
3.网络请求缓存处理,okhttp如何处理网络缓存的
4.从网络加载一个10M的图片,说下注意事项
5.TCP的3次握手和四次挥手
6.TCP与UDP的区别
7.TCP与UDP的应用
8.HTTP协议
9.HTTP1.0与2.0的区别
10.HTTP报文结构
11.HTTP与HTTPS的区别以及如何实现安全性
12.如何验证证书的合法性?
13.https中哪里用了对称加密,哪里用了非对称加密,对加密算法(如RSA)等是否有了解?
14.client如何确定自己发送的消息被server收到?
15.谈谈你对WebSocket的理解
16.WebSocket与socket的区别
17.谈谈你对安卓签名的理解。
18.请解释安卓为啥要加签名机制?
19.视频加密传输
20.App 是如何沙箱化,为什么要这么做?
21.权限管理系统(底层的权限是如何进行 grant 的)?

image

三、数据库

1.sqlite升级,增加字段的语句
2.数据库框架对比和源码分析
3.数据库的优化
4.数据库数据迁移问题

image

四、插件化、模块化、组件化、热修复、增量更新、Gradle

1.对热修复和插件化的理解 2.插件化原理分析 3.模块化实现(好处,原因) 4.热修复、插件化 5.项目组件化的理解 6.描述清点击 Android Studio 的 build 按钮后发生了什么

image

五、架构设计和设计模式

1.谈谈你对Android设计模式的理解 2.MVC MVP MVVM原理和区别 3.你所知道的设计模式有哪些? 4.项目中常用的设计模式 5.手写生产者/消费者模式 6.写出观察者模式的代码 7.适配器模式,装饰者模式,外观模式的异同? 8.用到的一些开源框架,介绍一个看过源码的,内部实现过程。 9.谈谈对RxJava的理解 10.Rxjava发送事件步骤 11.RxJava的作用,与平时使用的异步操作来比的优缺点 12.说说EventBus作用,实现方式,代替EventBus的方式 13.从0设计一款App整体架构,如何去做? 14.说一款你认为当前比较火的应用并设计(比如:直播APP,P2P金融,小视频等) 15.谈谈对java状态机理解 16.Fragment如果在Adapter中使用应该如何解耦? 17.Binder机制及底层实现 18.对于应用更新这块是如何做的?(解答:灰度,强制更新,分区域更新)? 19.实现一个Json解析器(可以通过正则提高速度) 20.统计启动时长,标准

image

六、性能优化

1.启动 app 黑白屏优化
2.稳定——内存优化
3.流畅——卡顿优化
4.节省——耗电优化
5.安装包——APK 瘦身
6.冷启动与热启动
7.内存泄漏的场景和解决办法
8. Bitmap 优化
9.LRU 的原理
10.webview 优化
11.如何避免 OOM?
...

image

七、Android Framework

1.Android 系统架构
2.View 的事件分发机制?滑动冲突怎么解决?
3.View 的绘制流程?
4.跨进程通信
5.Android 系统启动流程是什么?
6.启动一个程序,可以主界面点击图标进入,也可 以从一个程序中 跳转过去,二者有什么区别?
7.AMS 家族重要术语解释
8.用到的一些开源框架,介绍一个看过源码的,内部实现过程。
...

image

八、Android优秀三方库源码

1.网络底层框架:OkHttp 实现原理 2.网络封装框架:Retrofifit 实现原理 3.响应式编程框架:RxJava 实现原理 4.图片加载框架:Glide 实现原理 5.事件总线框架:EventBus 实现原理 6.内存泄漏检测框架:LeakCanary 实现原理 7.依赖注入框架:ButterKnife 实现原理 8.依赖全局管理框架:Dagger2 实现原理 9.数据库框架:GreenDao 实现原理

image

学习分享

①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包——————可以点击我的【腾讯文档】免费下载,最后觉得有帮助、有需要的朋友可以点个赞

7.依赖注入框架:ButterKnife 实现原理
8.依赖全局管理框架:Dagger2 实现原理
9.数据库框架:GreenDao 实现原理

[外链图片转存中…(img-sPrd0VEC-1622272455333)]

学习分享

①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包——————可以点击我的【腾讯文档】免费下载,最后觉得有帮助、有需要的朋友可以点个赞

[外链图片转存中…(img-WFKIQIvj-1622272455334)]

[外链图片转存中…(img-F6qVGhfX-1622272455334)]

[外链图片转存中…(img-2P7HNLgd-1622272455335)]

2021京东最新Android面试真题解析,深夜思考

2021京东最新Android面试真题解析,深夜思考

前言

每年,毕业季后面接踵而来的就是就业季,各位准备得怎么样了?准备Android面试的朋友记得收藏点赞哦,不是准备这个方向的面试也可以转发给你这个专业的朋友,助他一臂之力。

二:作为一名Android移动互联网架构师需要掌握的技术?

为了帮助大家能够在短时间内突破自身的瓶颈,我在这里也为大家整理一份腾讯T3级Android高级技术大纲和一门免费的Android移动互联网高级课程,文末附有学习资料

架构师专题.png

三:如何系统化学习?

1.学习Android底层开发知识,掌握前言技术,紧跟潮流掌握人工智能的AI时代,突破native层瓶颈,如:

手写斗鱼视频直播
Opencv详解
QQ音视频通话核心
爱奇艺音视频实战
人工智能
智能家居实战
抖音小视频实战

面向人群:面向底层方向发展,突破native层瓶颈的想和同行拉开差距,保持核心技术优势,没有任何C基础编程语言的人群

2.学习Android9.0最新技术,刘海屏适配技术,淘宝实战屏幕技术,如:

华为内部大型项目屏幕适配实战
UI绘制流程和原理
自定义控件实践等

面向人群:对于原生UI自定义控件想要更加深入了解.对Google开发的Android系统框架想深入学习.企业上追求原生性能的体验的人群

3.熟练掌握性能优化

奔溃优化:应用奔溃你改如何
卡顿优化:如何优化应用卡顿
网络优化:开发工程师必备的网络优化基础,大数据下网络如何监控
UI优化:如何避免重复渲染丶以及UI卡顿
内存优化丶启动优化丶储存优化丶耗电和安装包优化

面向人群:想进入大型BATJ公司的人群,对项目的运行效率不是特别满意的人群

4.掌握混合式开发技能

  • 4.1 Weex篇

ES6语法详解
weex环境打在和开发部署(window,mac)
weex与android交互原理

  • 4.2ReactNative篇

JSX语法详解
Android原生空间与React组件区别和转换原理
ReactJS组件生命周期和原生通信原理

  • 4.3美团电商实战用ReactNative打造美团APP

打造美团首页头部Banner
Listview实现多布局展示页面
ltem样式和类型扩展
美团购物中心结构搭建和订单详情页面

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

《379页Android开发面试宝典》

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

1)]

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

[外链图片转存中…(img-BDGzIqDj-1620452735543)]

因为文件太多,全部展示会影响篇幅,暂时就先列举这些部分截图,大家可以**点击这里**自行领取。

今天关于太赞了!2021京东最新Android面试真题解析,已拿offer京东安卓面试题的介绍到此结束,谢谢您的阅读,有关2017-2020历年字节跳动Android面试真题解析、2020Android面试重难点之Handler机制,含字节、京东、腾讯经典面试真题解析!、2021京东最新Android面试真题解析,文末领取面试资料、2021京东最新Android面试真题解析,深夜思考等更多相关知识的信息可以在本站进行查询。

本文标签: