2022 Android 常见面试题整理(02-11)

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:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@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<Runnable> 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容