1. 谈谈代码混淆的步骤?
-
开启混淆 :在模块的
build.gradle中设置minifyEnabled true,并指定混淆文件:groovyproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' -
编写混淆规则 (
proguard-rules.pro):- 保留四大组件、自定义 View、实体类(用于 JSON 反序列化需保留无参构造器和字段名)。
- 保留通过反射、注解、JNI、序列化访问的类或方法。
- 引入第三方库专属混淆规则(通常库文档会提供
-keep指令)。
-
测试:对混淆后的包进行充分测试,避免
ClassNotFoundException或NoSuchMethodError。 -
打包 :混淆器自动进行代码压缩、类名/方法名短化、配合
shrinkResources true压缩资源、移除无效代码。 -
保存映射文件 :每次发布保存
build/outputs/mapping/下的mapping.txt,记录了混淆前的类名、方法名、字段和行号的对应关系,用于线上堆栈还原(崩溃日志)。 -
作用:减小 APK 体积、加固防破解、删除无用代码。
2. 谈谈怎么给 apk 瘦身?
- 代码混淆:开启 ProGuard / R8,移除无用代码。
- 资源压缩 :
shrinkResources true,移除未引用资源。 - 图片优化:使用 WebP、矢量图(VectorDrawable)、降低 PNG 质量。
- 避免重复库:使用
gradle的exclude去重。 - 动态下载:部分模块插件化或 AAB(Android App Bundle)按需分发。
- 移除不必要的语言资源:
resConfigs "zh"。
3. 谈谈如何对网络请求进行优化?
- 减少请求次数:批量接口、合并请求。
- 数据压缩:Gzip 压缩请求体 / 响应体。
- 使用缓存 :HTTP 缓存(
Cache-Control)、本地磁盘缓存。 - 连接复用 :使用 HTTP/2、
OkHttp连接池。 - 弱网优化:设置合理超时、重试策略、断点续传。
- DNS 优化:HttpDNS 防劫持、减少 DNS 解析时间。
4. 谈谈 App 的电量优化?
- 减少网络请求:合并请求、使用 GCM/FCM 推送。
- 后台任务 :避免长时间后台运行,使用
JobScheduler进行批量处理。 - 减少唤醒次数:使用
AlarmManager时选非精确(setInexactRepeating),使用JobScheduler/WorkManager。 - 定位优化:按需请求位置,使用被动定位(
requestLocationUpdates合理设置间隔)。 - Sensor 使用:及时注销传感器监听。
- WakeLock:及时释放,使用
acquire/release配对。
5. 谈谈你是如何优化 App 启动过程的?
- 异步初始化 :
Application中的第三方库延迟或异步初始化,使用IdleHandler。 - 减少主线程任务:将非立即需要的初始化移到子线程。
- 视觉优化 :设置主题页(
windowBackground)避免白屏。 - 优化布局:首屏布局简单,避免嵌套过深。
- 预加载 :
WebView预创建、数据预取。 - 减小 Dex 体积:开启混淆、MultiDex 优化。
6. 谈谈如何对 WebView 进行优化?
- 预初始化:提前创建 WebView 并复用。
- 加载优化 :开启硬件加速、合理设置缓存策略(
setCacheMode网页缓存、资源缓存、离线缓存)。 - 资源拦截:本地替换 JS/CSS,使用
shouldInterceptRequest。 - 进程独立:将 WebView 置于单独进程,避免内存泄漏影响主进程。
- 销毁时:
onDestroy中removeAllViews、destroy()。 - JS 注入优化:延迟注入或按需注入。
7. Android Native Crash 问题如何分析定位?
- 获取日志 :
logcat抓取 crash 日志,关注Fatal signal 11 (SIGSEGV)等。 - tombstone 文件 :
/data/tombstones/下有崩溃堆栈。 - addr2line :用
ndk-stack或addr2line解析 so 文件中的地址。 - Breakpad / Google Breakpad:捕获 Native 崩溃生成 minidump 文件分析。
- 常见原因:空指针、内存越界、释放后使用、栈溢出、动态库加载失败。
8. 谈谈你对 Android 性能优化方面的了解?
- 内存优化 :避免内存泄漏(静态引用、Handler、匿名内部类)、对象复用、使用
LruCache及时回收 Bitmap。- LruCache:内存缓存工具类,基于 LRU(Least Recently Used,最近最少使用)算法,优先淘汰最近就少使用的对象
- 启动优化 :减少
Application和首屏onCreate任务、异步加载、优化主题页。 - 卡顿优化 :避免主线程耗时操作、使用
TraceView/Systrace分析。 - 网络优化:合并请求、数据压缩、使用缓存、HttpDNS。
- 安装包瘦身:混淆、资源压缩、WebP、移除无用资源。
- 绘制优化 :
onDraw中避免创建对象、频繁调用invalidate。 - 布局优化 :减少层级(
ConstraintLayout)、避免过度绘制、使用<merge>/<ViewStub>。
9 简要说说 LruCache 的原理?
- LRU 全称:最近最少使用算法,优先淘汰长时间未使用的数据。
- 底层: 基于 LinkedHashMap,开启访问有序模式。
- 核心机制:
- 每次 get/put 访问元素,会将当前元素移至队列头部;
- 长期不访问的元素会逐步挤到队列尾部;
- 缓存达到设定阈值时,自动移除尾部最少使用元素。
- 优点: 可控内存上限、自动淘汰、防止缓存无限膨胀、减少 OOM。
- 适用: 图片内存缓存、高频本地缓存场景。
一句话记忆:LruCache 基于 LinkedHashMap 的访问顺序,自动淘汰最久未使用的条目。
10 为什么推荐用 SparseArray 代替 HashMap?
- Key 类型 :
SparseArray的 Key 为int基本类型,无需自动装箱(int→Integer),节省内存和 CPU。 - 内存结构 :底层采用数组存储 ,内存占用更小;
- HashMap 是数组 + 链表 / 红黑树,结构冗余、内存开销大。
- 查找效率 :
SparseArray使用二分查找 (O(log n)),小数据量下速度更快。HashMap使用哈希表(O(1) 平均)。数据量较小时,二分查找开销可接受且更省内存。
- 延迟删除机制: 删除时先标记,不立即移动数组,减少拷贝开销。
- 适用场景 :数据量小(几百以内) 且 Key 为 int(如映射 View ID 到 View)。
一句话记忆:SparseArray 省内存、免装箱,适合 Key 为 int 的小数据量场景。
11. 谈谈 Android 中内存优化的方式?
- 避免内存泄漏 :静态内部类 + 弱引用(非静态内部类、匿名类持有外部引用)、及时注销监听、
onDestroy中清空 Handler。 - Bitmap 优化 :
inSampleSize缩放、inBitmap复用、RGB_565格式。 - 对象池 :复用对象(如
Message.obtain)。 - 使用数据缓存 :
LruCache、DiskLruCache。 - 使用
SparseArray/ArrayMap替代HashMap节省内存。
12. 哪些情况下会导致 OOM 问题?
- 加载过大图片:未缩放直接加载到内存。
- 内存泄漏:Activity 未释放,大量对象无法回收。
- 创建大量对象 :循环中频繁
new对象。 - 内存碎片:频繁分配释放导致大块连续内存不足。
- 长生命周期集合:静态
List/Map不断添加数据。 - 超大数组:一次性加载大量数据到内存。
- Android 版本差异:低版本单个进程内存上限更小。
13. 自定义 Handler 时如何有效地避免内存泄漏问题?
-
原因:非静态内部类 Handler 隐式持有外部 Activity 引用,延迟消息导致 Activity 无法回收。
-
解决方案:
- 使用静态内部类 + 弱引用持有 Activity。
- 在
onStop/onDestroy中调用handler.removeCallbacksAndMessages(null)移除所有消息。
-
示例:
javastatic class MyHandler extends Handler { WeakReference<Activity> ref; MyHandler(Activity activity) { ref = new WeakReference<>(activity); } @Override void handleMessage(Message msg) { Activity act = ref.get(); if (act != null) { /* 处理 */ } } }
14. Bitmap 的内存优化方法有哪些/如何避免 Bitmap 的 OOM 问题?
- 按需加载: 图片压缩(
inSampleSize降低宽高)、缩略图、局部加载、大图分块加载(只解码显示区域); - 合理格式: 选用高效格式 WebP、HEIF,减少内存占用;
- 解码配置 :
inPreferredConfig用RGB_565(16位)代替ARGB_8888(32位)降低内存。 - 使用缓存:三级缓存(内存缓存、磁盘缓存、网络);控制缓存上限;(Glide/Coil )
- 复用内存 :
inBitmap复用已分配的内存,避免重新分配; - 及时回收 :
recycle()释放 native 内存(Android 8.0 后内部自动管理,但建议仍调用);
15. 什么是 ANR?触发条件?如何避免?
- ANR:Application Not Responding,应用无响应,主线程阻塞超时(Activity 5 秒、BroadcastReceiver 10 秒、Service 20 秒)。
- 触发条件 :
- 输入事件(按键/触摸)5 秒内未响应
- 广播接收者(BroadcastReceiver)10 秒内未执行完
- 服务(Service)20 秒内未启动完成
- ContentProvider 发布超时
- 常见场景 :
- 主线程执行网络请求、IO 读写、大文件操作
- 主线程执行复杂计算(循环、加密)
- 主线程执行数据库大量操作
- 线程死锁导致主线程等待
- 避免方法 :
- 不在主线程做耗时操作(网络、IO、复杂计算),使用子线程处理耗时任务(
Thread、协程); - 避免在主线程中使用锁;
- 主线程只做 UI 刷新,轻量逻辑;
- 优化布局,避免过度绘制、频繁的
requestLayout; - 使用
HandlerThread、IntentService处理后台任务。 - 使用
StrictMode检测主线程中的潜在阻塞操作。
- 不在主线程做耗时操作(网络、IO、复杂计算),使用子线程处理耗时任务(
16. 如何计算一张图片所占的内存空间大小?
图片内存大小 = 总像素个数 × 单个像素所占字节
-
总像素 = 图片宽 × 图片高
-
像素位深举例:
- ARGB_8888:每个像素 4 字节
- RGB_565:每个像素 2 字节
- ARGB_4444:每个像素 2 字节
补充要点:
- 计算的是加载进内存的像素内存,不是磁盘文件大小;
inSampleSize采样压缩后,宽高同时缩小倍数,内存成倍降低;- Android 图片内存为 Native 内存,容易引发 OOM。
17. WebP 和 SVG 特点 & Android 使用方案
WebP 特点:
- 高效压缩,体积远小于 JPG/PNG,无损 / 有损都支持;
- 支持透明、动图,画质相近体积更小;
- 安卓高版本原生完美兼容,减少图片包体积。
使用场景: 图标、商品图、背景图、常规位图,替代 PNG/JPG。
SVG 特点:
- 矢量图,放大缩小不失真;
- 体积极小,纯路径绘制,无像素概念;
- 不适合复杂大图、色彩丰富图片。
使用场景: 纯色图标、按钮 ICON、简单矢量标识。
开发使用:
- WebP:直接放置资源目录,系统原生加载、Glide 完美支持;
- SVG:AndroidStudio 导入矢量图,使用
VectorDrawable渲染。
18. 对于 GIF 图片加载有什么思路和建议?
- 使用成熟框架: 优先 Glide、Coil 原生支持 GIF 解析播放,不手写解码。
- 格式替代: 简单动图优先用 Lottie 矢量动画,替代笨重 GIF,性能更好。
- 内存限制: 限制 GIF 分辨率、帧率,避免逐帧大图占用大量内存。
- 资源回收: 页面销毁及时释放 GIF 解码器、停止动画、清空引用。
- 按需播放: 页面不可见、切后台时暂停 GIF、减少耗电与内存占用。
- 避免频繁循环: 非必要场景限制播放次数,降低 CPU 消耗。
19. MVP 中如何处理 Presenter 防止内存泄漏?
- View 层弱引用: Presenter 持有 View 时,使用
WeakReference包裹,避免 Activity/Fragment 被强引用。 - 生命周期解绑: 页面销毁
onDestroy/onDestroyView中,主动解绑 View,置空引用。 - 终止异步任务: Presenter 中网络请求、协程、回调,页面销毁时统一取消、终止。
- 杜绝静态持有: Presenter 不要使用静态变量持有页面、View 实例。
- 接口隔离: 通过 View 接口通信,减少强耦合,方便统一回收销毁。
总结:Presenter 弱引用持有 View,页面生命周期及时解绑、清空异步任务,杜绝长引用绑定。