4 面字节跳动拿到 Offer,灵魂拷问
前言
Android 系统发布十多年以来,关于 Android 的 UI 的适配一直是开发环节中最重要的问题,但是我看到还是有很多小伙伴对 Android 适配方案不了解。刚好,近期准备对糗事百科 Android 客户端设计一套 UI 尺寸适配方案,可以和小伙伴们详细的聊一聊这个问题。
Android 适配最核心的问题有两个,其一,就是适配的效率,即把设计图转化为 App 界面的过程是否高效,其二如何保证实现 UI 界面在不同尺寸和分辨率的手机中 UI 的一致性。这两个问题都很重要,一个是保证我们开发的高效,一个是保证我们适配的成效;今天我们就这两个核心的问题来聊一聊 Android 的适配方案。
ANR 面试题
1、什么是 ANR
Application Not Responding,页面无响应的对话框
2、发生 ANR 的条件
应用程序的响应性是由 ActivityManager 和 WindowManager 系统服务监视的,当 ANR 发生条件满足时,就会弹出 ANR 的对话框
Activity 超过 5 秒无响应
BroadcastReceiver 超过 10 秒无响应
Service 超过 20 秒无响应
3、造成 ANR 的主要原因
主线程被 IO 操作阻塞
Activity 的所有生命周期回调都是执行在主线程的
Service 默认执行在主线程中
BoardcastReceiver 的回调 onReceive()执行在主线程中
AsyncTask 的回调除了 doInBackground,其他都是在主线程中
没有使用子线程 Looper 的 Handler 的 handlerMessage,post(Runnable)都是执行在主线程中
4、如何解决 ANR
使用 AsyncTask 处理耗时 IO 操作
使用 Thread 或 HandlerThread 提供优先级
使用 Handler 处理工作线程的耗时操作
Activity 的 onCreate 和 onResume 回调尽量避免耗时操作
OOM 面试题
1、什么是 OOM
OOM 指 Out of memory(内存溢出),当前占用内存加上我们申请的内存资源超过了 Dalvik 虚拟机的最大内存限制就会抛出 Out of memory 异常
2、OOM 相关概念
内存溢出:指程序在申请内存时,没有足够的空间供其使用
内存泄漏:指程序分配出去的内存不再使用,无法进行回收
内存抖动:指程序短时间内大量创建对象,然后回收的现象
3、解决 OOM
Bitmap 相关
图片压缩
加载缩略图
在滚动时不加载图片
回收 Bitmap
使用 inBitmap 属性
捕获异常
其他相关
listview 重用 convertView、使用 lru
避免 onDraw 方法执行对象的创建
谨慎使用多进程
Bitmap 面试题
1、recycle
在安卓 3.0 以前 Bitmap 是存放在堆中的,我们只要回收堆内存即可
在安卓 3.0 以后 Bitmap 是存放在内存中的,我们需要回收 native 层和 Java 层的内存
官方建议我们 3.0 以后使用 recycle 方法进行回收,该方法也可以不主动调用,因为垃圾回收器会自动收集不可用的 Bitmap 对象进行回收
recycle 方法会判断 Bitmap 在不可用的情况下,将发送指令到垃圾回收器,让其回收 native 层和 Java 层的内存,则 Bitmap 进入 dead 状态
recycle 方法是不可逆的,如果再次调用 getPixels()等方法,则获取不到想要的结果
2、LruCache 原理
LruCache 是个泛型类,内部采用 LinkedHashMap 来实现缓存机制,它提供 get 方法和 put 方法来获取缓存和添加缓存,其最重要的方法 trimToSize 是用来移除最少使用的缓存和使用最久的缓存,并添加最新的缓存到队列中
UI 卡顿面试题
1、UI 卡顿原理
View 的绘制帧数保持 60fps 是最佳,这要求每帧的绘制时间不超过 16ms(1000/60),如果安卓不能在 16ms 内完成界面的渲染,那么就会出现卡顿现象
2、UI 卡顿的原因分析
在 UI 线程中做轻微的耗时操作,导致 UI 线程卡顿
布局 Layout 过于复杂,无法在 16ms 内完成渲染
同一时间动画执行的次数过多,导致 CPU 和 GPU 负载过重
overDraw,导致像素在同一帧的时间内被绘制多次,使 CPU 和 GPU 负载过重
View 频繁的触发 measure、layout,导致 measure、layout 累计耗时过多和整个 View 频繁的重新渲染
频繁的触发 GC 操作导致线程暂停,会使得安卓系统在 16ms 内无法完成绘制
冗余资源及逻辑等导致加载和执行缓慢
ANR
3、UI 卡顿的优化
布局优化
使用 include、ViewStub、merge
不要出现过于嵌套和冗余的布局
使用自定义 View 取代复杂的 View
ListView 优化
复用 convertView
滑动不加载
背景和图片优化
缩略图
图片压缩
避免 ANR
不要在 UI 线程中做耗时操作
内存泄漏面试题
1、Java 内存泄漏引起的主要原因
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏
2、Java 内存分配策略
静态存储区:又称方法区,主要存储全局变量和静态变量,在整个程序运行期间都存在
栈区:方法体的局部变量会在栈区创建空间,并在方法执行结束后会自动释放变量的空间和内存
堆区:保存动态产生的数据,如:new 出来的对象和数组,在不使用的时候由 Java 回收器自动回收
3、Android 解决内存泄漏的例子
单例造成的内存泄漏:在单例中,使用 context.getApplicationContext()作为单例的 context
匿名内部类造成的内存泄漏:由于非静态内部类持有匿名外部类的引用,必须将内部类设置为 static
Handler 造成的内存泄漏:使用 static 的 Handler 内部类,同时在实现内部类中持有 Context 的弱引用
避免使用 static 变量:由于 static 变量会跟 Activity 生命周期一致,当 Activity 退出后台被后台回收时,static 变量是不安全,所以也要管理好 static 变量的生命周期
资源未关闭造成的内存泄漏:比如 Socket、Broadcast、Cursor、Bitmap、ListView 等,使用完后要关闭
AsyncTask 造成的内存泄漏:由于非静态内部类持有匿名内部类的引用而造成内存泄漏,可以通过 AsyncTask 内部持有外部 Activity 的弱引用同时改为静态内部类或在 onDestroy()中执行 AsyncTask.cancel()进行修复
内存管理面试题
1、Android 内存管理机制
分配机制
管理机制
2、内存管理机制的特点
更少的占用内存
在合适的时候,合理的释放系统资源
在系统内存紧张的时候,能释放掉大部分不重要的资源
能合理的在特殊生命周期中,保存或还原重要数据
3、内存优化方法
Service 完成任务后应停止它,或用 IntentService(因为可以自动停止服务)代替 Service
在 UI 不可见的时候,释放其 UI 资源
在系统内存紧张的时候,尽可能多的释放非重要资源
避免滥用 Bitmap 导致内存浪费
避免使用依赖注入框架
使用针对内存优化过的数据容器
使用 ZIP 对齐的 APK
使用多进程
冷启动和热启动面试题
1、什么是冷启动和热启动
冷启动:在启动应用前,系统中没有该应用的任何进程信息
热启动:在启动应用时,在已有的进程上启动应用(用户使用返回键退出应用,然后马上又重新启动应用)
2、冷启动和热启动的区别
冷启动:创建 Application 后再创建和初始化 MainActivity
热启动:创建和初始化 MainActivity 即可
3、冷启动时间的计算
这个时间值从应用启动(创建进程)开始计算,到完成视图的第一次绘制为止
4、冷启动流程
Zygote 进程中 fork 创建出一个新的进程
创建和初始化 Application 类、创建 MainActivity
inflate 布局、当 onCreate/onStart/onResume 方法都走完
contentView 的 measure/layout/draw 显示在界面上
总结:Application 构造方法->attachBaseContext()->onCreate()->Activity 构造方法->onCreate()->配置主题中背景等属性->onStart()->onResume()->测量布局绘制显示在界面上
5、冷启动优化
减少第一个界面 onCreate()方法的工作量
不要让 Application 参与业务的操作
不要在 Application 进行耗时操作
不要以静态变量的方式在 Application 中保存数据
减少布局的复杂性和深度
不要在 mainThread 中加载资源
通过懒加载方式初始化第三方 SDK
其他优化面试题
1、Android 不用静态变量存储数据
静态变量等数据由于进程已经被杀死而被初始化
使用其他数据传输方式:文件/sp/contentProvider
2、SharePreference 安全问题
不能跨进程同步
文件不宜过大
3、内存对象序列化
Serializeble:是 java 的序列化方式,Serializeble 在序列化的时候会产生大量的临时对象,从而引起频繁的 GC
Parcelable:是 Android 的序列化方式,且性能比 Serializeble 高,Parcelable 不能使用在要将数据存储在硬盘上的情况
4、避免在 UI 线程中做繁重的操作
架构模式面试题
插件化面试题
1、插件化解决的问题
动态加载 APK(反射、类加载器)
资源加载(反射、AssetManager、独立资源、分段资源)
代码加载(反射获取生命周期)
2、类加载器(Java 中字节码添加到虚拟机中)
DexClassLoader:能够加载未安装的 jar/apk/dex,主要用于动态加载和代码热更新
PathClassLoader:只能加载系统中已经安装过的 apk
热更新面试题
1、热更新主要流程
线上检查到 Crash
拉出 Bugfix 分支修复 Crash 问题
jenkins 构建和补丁生成
app 通过推送或主动拉取补丁文件
将 Bugfix 代码合到 master 上
2、热更新主流框架
Dexposed
AndFix
Nuwa
Tinker
3、热更新的原理
在 ClassLoader 创建一个 dexElements 数组
将修复好的 dex 文件存放在 dexElements 数组的最前面
ClassLoader 会遍历 dexElements 数组,找到最前面的 dex 文件优先加载
进程保活面试题
1、进程的优先级
空进程
后台进程
服务进程
可见进程
前台进程
2、Android 进程回收策略
Low memory Killer(定时执行):通过一些比较复杂的评分机制,对进程进行打分,然后将分数高的进程判定为 bad 进程,杀死并释放内存
OOM_ODJ:判别进程的优先级
3、Android 保活方案
利用系统广播拉活
利用系统 Service 机制拉活
利用 Native 进程拉活
利用 JobScheduler 机制拉活
利用账号同步机制拉活
Lint 面试题
1、什么是 Android Lint
Android Lint 是一个静态代码分析工具,它能够对你的 Android 项目中潜在的 Bug、可优化的代码、安全性、性能、可用性、可访问性、国际化等进行检查
2、Lint 工作流程
3、配置 Lint
创建 Lint.xml 到根目录下,自定义 Lint 安全等级等
在 Java 文件中可以使用 @suppressLint(“NewApi”)来忽视 Lint 的报错
在 xml 文件中可以使用 tool:ignore(“UnusedResources”)来忽视 Lint 的报错
自定义 Lint 检查,可以创建类,继承 Detector 和实现 JavaPsiScanner
Kotlin 面试题
1、什么是 Kotlin
Kotlin 是一种基于 JVM 的编程语言
对 Java 的一种拓展,比 Java 更简洁
Kotlin 支持函数式编程
Kotlin 类和 Java 类可以相互调用
2、Kotlin 环境搭建
直接在 Plugin 中下载 Kotlin 插件即可
系统会自动配置到 Kotlin 环境
题外话
我们见过很多技术 leader 在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了 7、8 年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。
其实 30 岁到 40 岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。
不断奔跑,你就知道学习的意义所在!
注意:我们之前因为秋招收集的二十套一二线互联网公司 Android 面试真题(含 BAT、小米、华为、美团、滴滴)和我自己整理 Android 复习笔记(包含 Android 基础知识点、Android 扩展知识点、Android 源码解析、设计模式汇总、Gradle 知识点、常见算法题汇总。)
以上进阶 Android 高级工程师系统学习资料可以免费分享给大家,需要完整版的朋友点这里可以看到全部内容。
评论