金九银十,每年9、10月份各大互联网公司都会周期性地发生人事变动,无论是刚进入社会的职场菜鸟,还是准备跳槽的老手,都想在这个时候获得新的工作,或迎来晋升涨薪的最佳机会。

不同于往年的是今年的互联网寒冬好像更冷一点,形式更严峻了一些,不少公司都在裁员,可能在求职中有一大部分人经历了被裁。即使在这种情况下,9、10月份仍然会有一波离职、求职潮。

作为求职者来说,面试就是一道坎,会有很多人会恐惧面试,即使是工作很多年的程序员,可能仍存在面试的焦虑。

针对「金九银十」的面试跳槽季,今天小编给大家分享的这套中大厂面试题集,包含了往年和最新的面试题目和注意事项大全,在阅读时,建议不要先看答案,而是自己先思考一番,然后再看参考答案,才能达到最好的效果。

一、Java异常机制中,异常Exception与错误Error区别

在Java中存在一个Throwable可抛出类,Throwable有两个重要的子类,一个是Error,另一个则是Exception。

“金九银十”的秋招季,请收下这套互联网中大厂Android面试题大全(含答案解析)_初始化

Error是程序不能处理的错误,表示程序中较严重问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError等等。这些错误发生时,JVM一般会选择线程终止。这些错误是不可查的,它们在程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。

Exception是程序可以处理的异常。而Exception又分为运行时异常(RuntimeException)与非运行时异常。

  • 运行异常
    运行时异常,又称不受检查异常 。所谓的不受检查,指的是Java编译检查时不会告诉我们有这个异常,需要在运行时候才会暴露出来,比如下标越界,空指针异常等。
  • 非运行时异常
    RuntimeException之外的异常我们统称为非运行时异常,比如IOException、SQLException,是必须进行处理的异常(检查异常),如果不处理(throw到上层或者try-catch),程序就不能编译通过。

二、谈谈你对 Activity.runOnUiThread 的理解?

一般是用来将一个runnable绑定到主线程,在runOnUiThread源码里面会判断当前runnable是否是主线程,如果是直接run,如果不是,通过一个默认的空构造函数handler将runnable post 到looper里面,创建构造函数handler,会默认绑定一个主线程的looper对象

三、子线程能否更新UI?为什么?

子线程是不能直接更新UI的 注意这句话,是不能直接更新,不是不能更新(极端情况下可更新)绘制过程要保持同步(否则页面不流畅),而我们的主线程负责绘制ui,极端情况就是,在Activity的onResume(含)之前的生命周期中子线程都可以进行更新ui,也就是onCreate,onStart和onResume,此时主线程的绘制还没开始。

四、 谈谈 Handler 机制和原理?

首先在UI线程我们创建了一个Handler实例对象,无论是匿名内部类还是自定义类生成的Handler实例对象,我们都需要对handleMessage方法进行重写,在handleMessage方法中我们可以通过参数msg来写接受消息过后UIi线程的逻辑处理,接着我们创建子线程,在子线程中需要更新UI的时候,新建一个Message对象,并且将消息的数据记录在这个消息对象Message的内部,比如arg1,arg2,obj等,然后通过前面的Handler实例对象调用sendMessge方法把这个Message实例对象发送出去,之后这个消息会被存放于MessageQueue中等待被处理,此时MessageQueue的管家Looper正在不停的把MessageQueue存在的消息取出来,通过回调dispatchMessage方法将消息传递给Handler的handleMessage方法,最终前面提到的消息会被Looper 从MessageQueue中取出来传递给handleMessage方法。

五、Zygote 为什么不采用 Binder 机制进行 IPC 通信?

Binder机制中存在Binder线程池,是多线程的,如果Zygote采用Binder的话就存在上面说的fork()与多线程的问题了。其实严格来说,Binder机制不一定要多线程,所谓的Binder线程只不过是在循环读取Binder驱动的消息而已,只注册一个Binder线程也是可以工作的,比如service manager 就是这样的。实际上Zygote尽管没有采取Binder机制,它也不是单线程的,但它在fork()前主动停止了其他线程,fork()后重新启动了。

六、Binder有什么优势

性能方面

  • 共享内存 0次数据拷贝
  • Binder 1次数据拷贝
  • Socket/管道/消息队列 2次数据拷贝

稳定性方面

  • Binder:基于C/S架构,客户端 (Client) 有什么需求就丢给服务端 (Server) 去完成,架构清晰、职责明确又相互独立,自然稳定性更好
  • 共享内存:虽然无需拷贝,但是控制复杂,难以使用
  • 从稳定性的角度讲,Binder机制是优于内存共享的。

安全性方面

  • 传统的IPC没有任何安全措施,安全依赖上层协议来确保。
  • 传统的IPC方法无法获得对方可靠的进程用户ID/进程UI (UID/PID) ,从而无法鉴别对方身份。
  • 传统的IPC只能由用户在数据包中填入UID/PID,容易被恶意程序利用。
  • 传统的IPC访问接入点是开放的,无法阻止恶意程序通过猜测接收方地址获得连接。
  • Binder既支持实名Binder,又支持匿名Binder,安全性高。

七、Binder是如何做到一次拷贝的

主要是因为Linux是使用的虚拟内存寻址方式,它有如下特性:

  • 用户空间的虚拟内存地址是映射到物理内存中的。
  • 对虚拟内存的读写实际上是对物理内存的读写,这个过程就是内存映射。
  • 这个内存映射过程是通过系统调用mmap()来实现的。
  • Binder借助了内存映射的方法,在内核空间的接收方用户控件的数据缓存区之间做了一层内存映射,就相当于直接拷贝到了接收方用户空间的数据缓存区,从而减少了一次数据拷贝。

八、Widget和element和RenderObject之间的关系

首先我详细说下当时的情景,面试官问我Widget和Element之间是不是一对多的关系,如果是增加一个Widget之后,这个关系又是什么。 这部分还是没有很好地答案,现在只是一个猜想,如果添加了一个widget,Element树遍历后面所有的Element看类型是否发生改变,有的话再重建RenderObject。Element和Widget之间应该还是一对一的关系,因为每个Widget的context都是独一无二的。

九、Flutter 是什么?

Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。

十、Flutter 特性有哪些?

  1. 快速开发(毫秒级热重载)
  2. 绚丽UI(内建漂亮的质感设计Material Design和Cupertino Widget和丰富平滑的动画效果和平台感知)
  3. 响应式(Reactive,用强大而灵活的API解决2D、动画、手势、效果等难题)
  4. 原生访问功能
  5. 堪比原生性能

十一、Flutter 中的生命周期

initState()表示当前 State 将和一个 BuildContext 产生关联,但是此时BuildContext 没有完全装载完成,如果你需要在该方法中获取 BuildContext ,可以 new Future.delayed(const Duration(seconds: 0, (){//context}); 一下。

didChangeDependencies()在 initState()之后调用,当 State 对象的依赖关系发生变化时,该方法被调用,初始化时也会调用。deactivate()当 State 被暂时从视图树中移除时,会调用这个方法,同时页面切换时,也会调用。dispose() Widget 销毁了,在调用这个方法之前,总会先调用 deactivate()。didUpdateWidge 当 widget 状态发生变化时,会调用。

通过 StreamBuilder 和 FutureBuilder 我们可以快速使用 Stream 和 Future 快速构建我们的异步控件,Flutter 中 runApp 启动入口其实是一个 WidgetsFlutterBinding ,它主要是通过BindingBase 的子类 GestureBinding 、ServicesBinding 、SchedulerBinding 、PaintingBinding 、SemanticsBinding 、 RendererBinding 、WidgetsBinding 等,通过 mixins 的组合而成的。 Flutter 中的 Dart 的线程是以事件循环和消息队列的形式存在,包含两个任务队列,一个是 microtask 内部队列,一个是event 外部队列,而 microtask 的优先级又高于event。因为 microtask 的优先级又高于 event,同时会阻塞event 队列,所以如果microtask 太多就可能会对触摸、绘制等外部事件造成阻塞卡顿哦。

Flutter 中存在四大线程,分别为 UI Runner、GPU Runner、IO Runner, Platform Runner (原生主线程),同时在 Flutter 中可以通过 isolate 或者compute 执行真正的跨线程异步操作。

十二、Stream 两种订阅模式?

Stream有两种订阅模式:单订阅(single) 和 多订阅(broadcast)。 单订阅就是只能有一个订阅者,而广播是可以有多个订阅者。这就有点类似于消息服务(Message Service)的处理模式。单订阅类似于点对点,在订阅者出现之前会持有数据,在订阅者出现之后就才转交给它。而广播类似于发布订阅模式,可以同时有多个订阅者,当有数据时就会传递给所有的订阅者,而不管当前是否已有订阅者存在。 Stream 默认处于单订阅模式,所以同一个 stream 上的 listen 和其它大多数方法只能调用一次,调用第二次就会报错。但 Stream 可以通过 transform() 方法(返回另一个 Stream)进行连续调用。通过Stream.asBroadcastStream() 可以将一个单订阅模式的 Stream 转换成一个多订阅模式的 Stream,isBroadcast 属性可以判断当前 Stream 所处的模式。

十三、简述下Handler机制的总体原理

  1. Looper 准备和开启轮循: Looper#prepare()初始化线程独有的 Looper 以及 MessageQueue Looper#loop()开启死循环读取 MessageQueue 中下一个满足执行时间的 Message 尚无 Message 的话,调用 Native 侧的 pollOnce()进入无限等待存在 Message,但执行时间 when 尚未满足的话,调用 pollOnce()时传入剩余时长参数进入有限等待
  2. Message 发送、入队和出队: Native 侧如果处于无限等待的话:任意线程向 Handler 发送 Message 或 Runnable 后,Message 将按照 when 条件的先后,被插 入 Handler 持有的 Looper 实例所对应的 MessageQueue 中适当的位置。 MessageQueue 发现有合适的 Message 插入后将调用 Native 侧的 wake() 唤醒无限等待的线程。这将促使 MessageQueue 的读取继续进入下一次循环,此刻 Queue 中已有满足条件的 Message 则 出队返回给 Looper Native 侧如果处于有限等待的话:在等待指定时长后 epoll_wait 将返回。线程继续读取 MessageQueue, 此 刻因为时长条件将满足将其出队 Looper 处理Message 的实现:
  3. Looper 得到 Message 后回调 Message 的 callback 属性即 Runnable,或依据 target 属性即 Handler,去执行 Handler 的回调。存在 mCallback属性的话回调 Handler$Callback 反之,回调 handleMessage()

十四、 Looper 存在哪?如何可以保证线程独有?

Looper实例被管理在静态属性sThreadLocal中ThreadLocal内部通过ThreadLocalMap持有Looper,key为ThreadLocal实例本身,value即为Looper实例每个Thread都有一个自己的ThreadLocalMap,这样可以保证每个线程对应一个独立的 Looper实例,进而保证 myLooper()可以获得线程独有的 Looper 彩蛋:一个 App 拥有几个 Looper 实例?几个 ThreadLocal 实例?几个 MessageQueue 实例?几个Message 实例?几个Handler实例一个线程只有一个 Looper实例一个 Looper 实例只对应着一个 MessageQueue实例一个MessageQueue 实例可对应多个 Message 实例,其从 Message 静态池里获取,存在50的上限一个线程可以拥有多个 Handler 实例,其Handler 只是发送和执行任务逻辑的入口和出口 ThreadLocal 实例是静态的,整个进程共用一个实例。每个 Looper 存放的 ThreadLocalMap 均弱引用它作为 key

十五、如何理解 ThreadLocal 的作用?

首先要明确并非不是用来切换线程的,只是为了让每个线程方便程获取自己的 Looper 实例,见 Looper#myLooper()后续可供 Handler 初始化时指定其所属的 Looper 线程也可用来线程判断自己是否是主线程

十六、ActivityManagerService是什么?什么时候初始化的?有什么作用?

ActivityManagerService 主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块类似。ActivityManagerService进行初始化的时机很明确,就是在 SystemServer进程开启的时候,就会初始化ActivityManagerService。(系统启动流程)如果打开一个App的话,需要AMS去通知zygote进程,所有的Activity的生命周期AMS来控制

由于文章篇幅有限,不能将完整的面试题全部展示出来,有需要完整面试题和答案解析的朋友,可以点击下方课程链接详细了解!!!

https://edu.51cto.com/course/32703.html

“金九银十”的秋招季,请收下这套互联网中大厂Android面试题大全(含答案解析)_UI_02

最后

面试是一个挑战和机遇并存的过程,不论结果如何,都当做一次宝贵的经验。