Android
本人于2022年2月份被裁员,题库是旧的。加 * 是今年被问次数较多的题,希望读者着重看一下。
(如有时间,我整理下被面试到新问题。但是我没答案,想要答案问村长去)
「本人没什么流量,此文章本用于自用。答案是从其他作者的文章摘取,如有侵权需删除此贴,请及时通知。因不常驻,收到消息时效较慢,望见谅」
1.Android 基础
1、什么是ANR?如何避免它?*
答:在Android 上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择让程序继续运行,但是,他们在使用你的应用程序时,并不希望每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样,系统不会显示ANR 给用户。不同的组件发生ANR 的时间不一样,主线程(Activity、Service)是5 秒,BroadCastReceiver 是10 秒。解决方案:将所有耗时操作,比如访问网络,Socket 通信,查询大量SQL 语句,复杂逻辑计算等都放在子线程中去,然后通过handler.sendMessage、runonUITread、AsyncTask 等方式更新UI。无论如何都要确保用户界面操作的流畅度。如果耗时操作需要让用户等待,那么可以在界面上显示进度条
2、事件*
绘制流程
ViewRoot
-> performTraversal() 触发开始 View 的绘制
-> performMeasure() 遍历 View 的 measure 测量尺寸
-> performLayout() 遍历 View 的 layout 确定位置
-> performDraw() 遍历 View 的 draw 绘制
-> View/ViewGroup measure()
-> View/ViewGroup onMeasure()
-> View/ViewGroup layout() ->
View/ViewGroup onLayout() ->
View/ViewGroup draw() ->
View/ViewGroup onDraw() 看下invalidate方法,有带4个参数的,和不带参数有什么区别;requestLayout触发measure和layout,如何实现局部重新测量,避免全局重新测量问题。
touch 事件分发机制*
一个 MotionEvent 产生后,按 Activity -> Window -> decorView -> View 顺序传递,View 传递过程就是事件分发,主要依赖三个方法:
dispatchTouchEvent:用于分发事件,只要接受到点击事件就会被调用,返回结果表示是否消耗了当前事件
onInterceptTouchEvent:用于判断是否拦截事件,当 ViewGroup 确定要拦截事件后,该事件序列都不会再触发调用此 ViewGroup 的 onIntercept
onTouchEvent:用于处理事件,返回结果表示是否处理了当前事件,未处理则传递给父容器处理
细节: 一个事件序列只能被一个 View 拦截且消耗 View 没有 onIntercept 方法,直接调用 onTouchEvent 处理 OnTouchListener 优先级比 OnTouchEvent 高,onClickListener 优先级最低 requestDisallowInterceptTouchEvent 可以屏蔽父容器 onIntercept 方法的调用
Handler *
Handler 实属必备知识了,而且相对来说,代码比较简单,这里不做赘述,但有几个问题是今年常问的,希望各位带着问题再读一遍源码
- Looper 对象的创建,由谁创建,怎么创建的?
- Looper.loop() 会不会导致ANR?
- MessageQueue 是队列结构么?
- Handler 发送延时消息是怎么实现的?
- MessageQueue 没有消息了会怎样?
- 知道 IdleHandler 是做什么的么?
- Handler 构造有个 async 做什么的?
3、 GC
垃圾回收相关算法
引用计数算法
可达性分析算法
GC Roots选择作为Root的对象*
虚拟机栈中引用的对象
比如:各个线程被调用的方法中使用到的参数、局部变量等。
本地方法栈内JNI(通常说的本地方法)引用的对象
方法区中类静态属性引用的对象
比如:Java类的引用类型静态变量
方法区中常量引用的对象
比如:字符串常量池(String Table)里的引用
所有被同步锁synchronized持有的对象
Java虚拟机内部的引用。
基本数据类型对应的Class对象,一些常驻的异常对象(如:NullPointerException、OutOfMemoryError),系统类加载器。
反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
清除阶段
清除阶段:标记-清除算法
缺点:
标记清除算法的效率不算高
在进行GC的时候,需要停止整个应用程序,用户体验较差
这种方式清理出来的空闲内存是不连续的,产生内碎片,需要维护一个空闲列表
清除阶段:标记-复制算法*
应用场景:
在新生代,对常规应用的垃圾回收,一次通常可以回收70% - 99% 的内存空间。回收性价比很高。所以现在的商业虚拟机都是用这种收集算法回收新生代。
优点:
没有标记和清除过程,实现简单,运行高效
复制过去以后保证空间的连续性,不会出现“碎片”问题。
缺点:
此算法的缺点也是很明显的,就是需要两倍的内存空间。
对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小
清除阶段:标记-压缩(整理)算法
优点:
消除了标记-清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可。
消除了复制算法当中,内存减半的高额代价。
缺点:
从效率上来说,标记-整理算法要低于复制算法。
移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址
移动过程中,需要全程暂停用户应用程序。即:STW
分代收集算法
年轻代(Young Gen)
区域相对老年代较小,对象生命周期短、存活率低,回收频繁
老年代(Tenured Gen)
区域较大,对象生命周期长、存活率高,回收不及年轻代频繁
增量收集算法
增量收集算法通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作
分区算法
分代算法将按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同小区间。
每一个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间。
4.请介绍下ContentProvider 是如何实现数据共享的*
这里需要知道 Binder 机制,需自行查找Binder机制详解(面试官可能问到不同方向)*
Binder+共享内存
2.网络基础
HTTP
网络协议模型
应用层:负责处理特定的应用程序细节 HTTP、FTP、DNS
传输层:为两台主机提供端到端的基础通信 TCP、UDP
网络层:控制分组传输、路由选择等 IP
链路层:操作系统设备驱动程序、网卡相关接口
TCP 和 UDP 区别
TCP 连接;可靠;有序;面向字节流;速度慢;较重量;全双工;适用于文件传输、浏览器等
UDP 无连接;不可靠;无序;面向报文;速度快;轻量;适用于即时通讯、视频通话等
POST 和 GET 区别
Get 参数放在 url 中;Post 参数放在 request Body 中
Get 可能不安全,因为参数放在 url 中
HTTP 和 HTTPS* (下面答案不做参考,自行搜索,HTTPS 有问到请求流程 以及对称加密和非对称加密在哪里使用)
HTTP 是超文本传输协议,明文传输;HTTPS 使用 SSL 协议对 HTTP 传输数据进行了加密
HTTP 默认 80 端口;HTTPS 默认 443 端口
优点:安全 缺点:费时、SSL 证书收费,加密能力还是有限的,但是比 HTTP 安全些
3. Java 基础
Java设计模式遵循的六大原则* (可能需要你根据你的项目经历说,去之前想好)
单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。
依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
迪米特法则(Law of Demeter, LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。
静态代理与动态代理的区别和使用场景
静态代理与动态代理的区别在于代理类生成的时间不同,如果需要对多个类进行代理,并且代理的功能都是一样的,用静态代理重复编写代理类就非常的麻烦,可以用动态代理动态的生成代理类
线程池*
需要背构造的传参
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:
* {@code corePoolSize < 0}
* {@code keepAliveTime < 0}
* {@code maximumPoolSize <= 0}
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用new 线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率
① newSingleThreadExecutor
//单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务
② newFixedThreadExecutor(n)
//固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行
③ newCacheThreadExecutor(推荐使用)
//可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
④ newScheduleThreadExecutor
//大小无限制的线程池,支持定时和周期性的执行线程
4.Android 性能优化*
原生
Activity 启动模式*
standard 标准模式
singleTop 栈顶复用模式, 推送点击消息界面
singleTask 栈内复用模式, 首页
singleInstance 单例模式,单独位于一个任务栈中 拨打电话界面
Window 、 WindowManager、WMS、SurfaceFlinger
Window:抽象概念不是实际存在的,而是以 View 的形式存在,通过 PhoneWindow 实现
WindowManager:外界访问 Window 的入口,内部与 WMS 交互是个 IPC 过程
WMS:管理窗口 Surface 的布局和次序,作为系统级服务单独运行在一个进程
SurfaceFlinger:将 WMS 维护的窗口按一定次序混合后显示到屏幕上
Serializable、Parcelable *
Serializable :Java 序列化方式,适用于存储和网络传输,serialVersionUID 用于确定反序列化和类版本是否一致,不一致时反序列化回失败
Parcelable :Android 序列化方式,适用于组件通信数据传递,性能高,因为不像 Serializable 一样有大量反射操作,频繁 GC (只答这样是不够的,还是没有具体说出来问啥快,自行找一下答案吧)
Android 系统启动流程
按电源键 -> 加载引导程序 BootLoader 到 RAM -> 执行 BootLoader 程序启动内核 -> 启动 init 进程 -> 启动 Zygote 和各种守护进程 ->
启动 System Server 服务进程开启 AMS、WMS 等 -> 启动 Launcher 应用进程
App 启动流程*(这个很敷衍了,自行找一下答案吧)
Launcher 中点击一个应用图标 -> 通过 AMS 查找应用进程,若不存在就通过 Zygote 进程 fork
进程保活*(问这个的是沙叉)
进程优先级:1.前台进程 ;2.可见进程;3.服务进程;4.后台进程;5.空进程
进程被 kill 场景:1.切到后台内存不足时被杀;2.切到后台厂商省电机制杀死;3.用户主动清理
保活方式:
1.Activity 提权:挂一个 1像素 Activity 将进程优先级提高到前台进程
2.Service 提权:启动一个前台服务(API>18会有正在运行通知栏)
3.广播拉活
4.Service 拉活
5.JobScheduler 定时任务拉活
6.双进程拉活
网络优化及检测
速度:1.GZIP 压缩(okHttp 自动支持);2.Protocol Buffer 替代 json;3.优化图片/文件流量;4.IP 直连省去 DNS 解析时间
成功率:1.失败重试策略;
流量:1.GZIP 压缩(okHttp 自动支持);2.Protocol Buffer 替代 json;3.优化图片/文件流量;5.文件下载断点续传 ;6.缓存
协议层的优化,比如更优的 http 版本等
监控:Charles 抓包、Network Monitor 监控流量
UI卡顿优化*
减少布局层级及控件复杂度,避免过度绘制
使用 include、merge、viewstub
优化绘制过程,避免在 Draw 中频繁创建对象、做耗时操作
5.Android 模块化&热修复&热更新&打包&混淆&压缩
VM
Dalvik 和 ART
Dalvik 谷歌设计专用于 Android 平台的 Java 虚拟机,可直接运行 .dex 文件,适合内存和处理速度有限的系统 JVM 指令集是基于栈的;Dalvik 指令集是基于寄存器的,代码执行效率更优
ART Dalvik 每次运行都要将字节码转换成机器码;ART 在应用安装时就会转换成机器码,执行速度更快 ART 存储机器码占用空间更大,空间换时间
APK 打包流程
1.aapt 打包资源文件生成 R.java 文件;aidl 生成 java 文件
2.将 java 文件编译为 class 文件
3.将工程及第三方的 class 文件转换成 dex 文件
4.将 dex 文件、so、编译过的资源、原始资源等打包成 apk 文件
5.签名
6.资源文件对齐,减少运行时内存
App 安装过程
首先要解压 APK,资源、so等放到应用目录
Dalvik 会将 dex 处理成 ODEX ;ART 会将 dex 处理成 OAT;
OAT 包含 dex 和安装时编译的机器码
各种源码解析
根据项目中使用到的框架,直接去找相应的详解,记住使用了哪些设计模式
被问到的有 HashMap* ,OkHttp* ,Retrofit* ,RxJava* 主要看你项目中使用到的和在你简历中出现的。
少见的有 LinkedHashMap ,ArrayMap 或 SparseArray。
Kotlin
todo
Flutter
todo