一、基础语法和语言特性‑Java 相关
1. 请解释 Java 中的多态性,并举一个实际的例子
多态:同一个行为,不同对象表现出不同的实现结果;核心三要素:继承 / 实现、方法重写、父类引用指向子类对象。分为编译期多态(方法重载)、运行时多态(方法重写)。
示例:
java
运行
//父类
abstract class Animal{
public abstract void shout();
}
//子类狗
class Dog extends Animal{
@Override
public void shout() {
System.out.println("汪汪汪");
}
}
//子类猫
class Cat extends Animal{
@Override
public void shout() {
System.out.println("喵喵喵");
}
}
public class Test{
public static void main(String[] args) {
//父类引用指向子类对象,多态
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.shout(); //汪汪汪
a2.shout(); //喵喵喵
}
}
2. 简述 Java 中接口和抽象类的区别
表格
3. 说说 Java 的垃圾回收机制,以及如何优化内存使用
GC 核心思想:JVM 自动识别不再被引用的对象,回收堆内存,不需要手动释放;只回收堆内存。
判断对象存活:可达性分析算法,从 GC Roots(栈引用、静态变量、本地方法引用等)出发,不可到达的对象判定为垃圾。
垃圾回收算法:标记‑清除、复制算法、标记‑整理;分代回收:年轻代 (Eden、Survivor)、老年代。
GC 类型:Minor GC(年轻代)、Major GC/Full GC(老年代,STW,耗时高)。
内存优化手段
避免创建大量短期对象,循环内不要频繁 new 对象;
及时置空不再使用的对象引用,解除长生命周期对象对短生命周期对象的引用;
集合使用后及时清理,避免内存泄漏;
合理使用缓存,避免无上限缓存;
避免非必要自动装箱拆箱;
大对象谨慎创建,大图片做好压缩;
尽量减少 Full GC,减少 STW 时间。
二、Kotlin 相关
1. Kotlin 中的空安全机制是如何实现的?
Kotlin 从语法层面杜绝空指针异常,区分可空类型和非空类型
非空类型:
var name:String,不能赋值 null,编译期校验直接报错;可空类型:
var name:String?,允许 null,所有调用会强制要求处理空,否则编译报错。
空处理操作符:
?.安全调用:对象不为 null 才执行调用,null 直接返回 nullname?.length!!非空断言:强制认为不为 null,如果是 null 直接抛出 NPE,不推荐滥用?:埃尔维斯运算符:为空返回默认值val len = name?.length ?: 0
底层:编译阶段做语法检查,生成 Java 字节码时增加空判断逻辑,把空指针风险从运行时提前到编译期。
2. 解释 Kotlin 中的扩展函数和扩展属性
扩展:不修改原有类源码,给已有类增加函数 / 属性,静态实现,不会真正修改原类。
扩展函数
kotlin
//给String新增一个扩展函数
fun String.printLog(){
println("字符串:$this") //this代表调用者对象
}
//调用
"test".printLog()
底层是静态方法,接收对象作为第一个参数;不能访问原类私有成员。
扩展属性
没有实际的字段,只有 getter/setter,不能新增成员变量:
kotlin
val String.firstChar:Char
get() = this[0]
fun main(){
println("abc".firstChar)
}
注意:扩展是静态解析,看的是声明时的变量类型,不是运行时对象类型;子类重写无效。
三、Android 系统和框架‑四大组件
1. 详细描述 Activity 的生命周期,以及在各个阶段可能会做的操作
完整生命周期:onCreate → onStart → onResume → onPause → onStop → onDestroy
还有onRestart:Activity 从 stop 回到前台时调用。
onCreate():Activity 创建,只执行一次。初始化 View、绑定数据、初始化成员变量、setContentView。
onStart():可见,但还不能交互。UI 可见,还没到前台。
onResume():前台可见,可交互。获取焦点;开启动画、注册需要前台运行的监听。
onPause():失去焦点,部分可见(弹窗盖住)。保存临时数据;停止动画、释放独占资源;不能做耗时操作。
onStop():完全不可见。释放大量资源,取消网络请求,注销广播。
onRestart():stop 之后重新回到 onStart,页面从后台切回来触发。
onDestroy():销毁,释放所有资源,解绑监听,避免内存泄漏。
配置变更(屏幕旋转)会销毁重建:onPause‑onStop‑onDestroy,再 onCreate‑onStart‑onResume。
2. 如何实现 Service 的远程调用?
跨进程通信,使用 AIDL(Android Interface Definition Language)
步骤:
创建
.aidl接口文件,定义跨进程调用方法;make 项目生成自动生成的 Binder 接口类;
Service 内部实现 AIDL 的 Stub (Binder) 对象,在 onBind 返回这个 Binder;
Service 配置
android:process=":remote"开启独立进程;客户端通过
bindService绑定服务,拿到 IBinder,转成 AIDL 接口;客户端调用接口方法,实现跨进程调用。
注意:跨进程不能直接传对象,对象需要实现 Parcelable;AIDL 调用是阻塞,不要在主线程调用。也可以用 Messenger 基于 Binder 实现简单跨进程。
3. BroadcastReceiver 有几种注册方式,它们的区别是什么?
两种注册:静态注册(清单文件注册)、动态注册(代码注册)
静态注册:AndroidManifest.xml 中
<receiver>标签声明。
APP 未启动,系统发送广播时可以自动拉起应用;
Android8.0 之后大部分隐式静态广播被限制,需要显式 Intent。
动态注册:代码
registerReceiver()注册,unregisterReceiver()解注册。
跟随 Context 生命周期,Activity 销毁必须注销,否则内存泄漏;
只有应用进程存活才接收广播;
接收广播优先级可控,适合 APP 运行时监听。
区别总结:
表格
4. ContentProvider 的作用和使用场景有哪些?
ContentProvider:四大组件之一,统一对外提供数据访问接口,实现跨应用数据共享,封装底层数据库。
底层基于 Binder。外部 APP 通过 ContentResolver 访问。
使用场景:
应用之间共享数据,例如系统通讯录、相册;
本应用不同进程之间共享数据库数据;
封装数据库操作,对外提供统一 URI 访问接口;
配合 ContentObserver 监听数据变化。
URI 作为访问标识,格式
content://authority/path/id。
四、视图和布局
1. 说说 Android 中常见的布局方式,以及它们的优缺点
LinearLayout 线性布局:子 View 横向 / 纵向排列
优点:简单,适合线性列表;缺点:嵌套多会性能差,权重 layout_weight 会多次测量。
RelativeLayout 相对布局:子 View 相对其他控件 / 父容器位置摆放
优点:可以实现复杂相对位置;缺点:测量两次,性能较差,现在不推荐。
FrameLayout 帧布局:子控件层叠摆放,默认左上角
优点:简单,测量快;适合 Fragment 容器、轮播;缺点:只能层叠,复杂布局不好写。
ConstraintLayout 约束布局(推荐)
优点:扁平化布局,减少嵌套,性能好;支持百分比、链条、导向线;适配能力强;
缺点:布局 xml 可读性差,复杂约束调试麻烦。
TableLayout 表格布局:表格行列,现在很少用。
最佳实践:尽量使用 ConstraintLayout,减少布局嵌套层级。
2. 如何实现自定义 View,需要注意哪些方面?
实现方式
继承 View:完全自定义绘制,重写
onMeasure()测量、onLayout()布局、onDraw()绘制;继承已有控件(TextView 等):扩展原有控件能力;
组合 View:继承 ViewGroup,xml 组合多个控件,封装为一个自定义控件。
重点注意事项
重写 onMeasure:处理 wrap_content,不处理 wrap_content 会 match_parent 效果;处理 padding;
onDraw 不要创建对象,避免频繁 GC;
处理 padding;如果是 ViewGroup 处理 margin;
支持自定义属性,在 xml 中配置;
处理手势事件:onTouchEvent,注意事件冲突;
内存:onDraw 不要 new 对象;动画、监听在页面销毁时释放;
兼容不同分辨率;
硬件加速:部分绘图 API 不支持硬件加速;
避免在 onDraw 中做耗时操作。
五、异步处理
1. 介绍 Android 中实现异步任务的几种方式,如 AsyncTask、Handler、RxJava 等
Handler+Message:Android 原生。子线程发消息,主线程 Handler 处理消息;底层 MessageQueue、Looper。适合主线程和子线程通信。
AsyncTask:封装线程池 + Handler,简化异步;Android11 废弃;缺陷:生命周期耦合,容易内存泄漏,多任务并发问题。
Thread / Runnable:原生 Java 线程;需要自己 Handler 切换主线程;管理线程麻烦。
ThreadPoolExecutor 线程池:手动管理线程,推荐,可控并发数,避免频繁创建销毁线程。
Coroutine 协程 (Kotlin):轻量级,suspend 挂起,Dispatchers 切换线程,Android 主流。
RxJava/RxAndroid:响应式编程,链式调用,线程切换;处理复杂异步流;需要管理 Disposable 防止内存泄漏。
WorkManager:适合延迟、可保证执行的后台任务。
2. 如何避免 Handler 造成的内存泄漏问题?
泄漏原因:非静态内部 Handler 类持有外部 Activity 引用;消息队列 Message 持有 Handler 引用;消息延迟执行,Activity 销毁消息还未处理,Activity 无法回收。
解决方案:
使用静态 Handler,外部使用弱引用 WeakReference 持有 Activity;
Activity 销毁时,调用
handler.removeCallbacksAndMessages(null)清空所有消息、回调;尽量避免使用匿名内部 Handler;
使用协程、RxJava 等替代,生命周期自动取消。
六、性能优化
内存优化
1. 如何检测 Android 应用中的内存泄漏?
Android Studio Profiler 内存分析工具,抓取 Heap Dump,分析 hprof 文件;
LeakCanary:第三方库,自动检测内存泄漏,输出泄漏链路,开发调试首选;
adb shell dumpsys meminfo 查看内存状态;
MAT 工具分析 hprof 文件。
2. 列举一些常见的内存泄漏场景和解决方法
非静态内部类 / 匿名内部类持有 Activity:Handler、Runnable;改用静态类 + WeakReference;销毁清空消息。
静态变量持有 Activity/Fragment 引用:不要把页面实例赋值给 static;页面销毁置 null。
注册监听、广播没有反注册:广播、传感器、回调,onDestroy 注销。
单例持有 Activity 上下文:单例尽量使用 Application Context,不要传 Activity。
集合没有清理引用:List、HashMap 存放对象,退出页面不清空。
WebView 泄漏:销毁时销毁 webview,移除 view,调用 destroy。
图片大内存,图片没有及时回收。
布局优化
1. 怎样优化 Android 布局的加载速度?
减少布局嵌套层级,优先 ConstraintLayout,避免多重 LinearLayout 嵌套;
使用
<include>复用布局,<ViewStub>懒加载不立即渲染;减少过度绘制,去掉多余背景;
避免在 onCreate 做 inflate 大量布局;
减少自定义 View 中 onMeasure、onDraw 耗时操作;
使用 merge 标签消除多余父布局节点;
RecyclerView 优化,复用 ViewHolder,减少 inflate。
2. 解释 ViewStub 和 include 标签的作用和使用场景
<include>:布局复用,把公共布局抽出来,include 引入。
注意:会复制父布局属性,如果要覆盖 layout 属性,必须同时重写 layout_width 和 layout_height。
<ViewStub>:惰性加载占位布局。初始化不会解析渲染,占用内存很小;调用viewStub.inflate()才真正加载布局。使用场景:页面中默认不需要显示的布局,比如空状态、错误页面,需要的时候才加载,减少初始化开销。
<merge>:配合 include,消除多余父布局,减少层级。
电量优化
谈谈 Android 应用中可能会导致电量消耗过大的原因和优化策略
耗电根源:CPU 唤醒、网络频繁请求、GPS 定位、屏幕、频繁唤醒锁。
原因
频繁网络请求,短时间大量 http;频繁轮询;
GPS 频繁开启定位;
WakeLock 唤醒锁,CPU 持续唤醒;
后台频繁启动服务,后台频繁线程、定时器;
动画、频繁 UI 刷新;后台频繁唤醒 APP。
优化策略
网络:合并网络请求,批量上传;减少轮询,优先推送;弱网优化;
定位:不需要的时候关闭定位,降低定位精度;
谨慎使用 WakeLock,用完立刻释放;
后台任务优先 WorkManager,系统统一调度,批量执行任务,对齐唤醒;
减少后台服务,避免定时无限循环;
减少不必要动画,退出页面停止动画;
遵循 Android 后台限制,避免后台频繁唤醒。
七、项目经验和问题解决
1. 请详细描述你参与过的一个 Android 项目,包括项目的功能、架构和遇到的挑战。
面试口述参考模板(你可以替换成自己项目)
项目:XX 短视频 APP,主要功能:首页视频流、个人中心、消息、登录、播放视频,图片加载,网络请求。
架构:MVVM 架构,Jetpack 组件:ViewModel、LiveData、Room;网络 Retrofit+OkHttp;协程做异步;图片 Glide;RecyclerView 实现视频列表。
模块拆分:base 基础库、网络模块、UI 组件、业务模块。
遇到挑战:
RecyclerView 滑动卡顿,视频预加载,滑动时频繁创建播放器;解决方案:播放器复用池,滑动过程暂停播放,可见 item 才初始化播放器;
内存泄漏问题,引入 LeakCanary 检测,修复 Handler、回调未注销的泄漏;
适配不同 Android 版本,Android13 权限变更,相册、通知权限适配。
2. 在项目中,你是如何进行版本管理的,使用了哪些工具?
版本控制使用 Git;代码仓库 Gitee/GitLab/GitHub。
分支策略:master/main 主干;develop 开发分支;feature 分支开发新功能;hotfix 分支线上 bug 修复;release 版本分支;
提交规范:commit 信息规范,描述改动;
CodeReview:提交 Merge Request,同事评审代码;
版本打包:Gradle 打包;Jenkins 持续集成,自动化构建 APK;
TAG 打标签标记线上版本。
3. 当应用出现 ANR(Application Not Responding)时,你会如何进行排查和解决?
ANR 原因:主线程阻塞超过阈值;输入事件 5s 无响应;Broadcast10s;Service20s。
排查步骤:
查看日志:
/data/anr/traces.txt,系统发生 ANR 时输出的堆栈,看主线程卡在什么地方;看主线程是否做耗时操作:网络请求、数据库大查询、大文件 IO、复杂计算;
查看锁等待,主线程等待子线程锁;
使用 Systrace 工具,追踪主线程执行情况,定位卡顿点。
解决:
所有耗时操作移动到子线程;网络、数据库、IO 不要放主线程;
避免主线程锁等待;
优化数据库操作,批量操作;
避免 onCreate/onResume 做大量耗时逻辑。
4. 遇到应用崩溃的情况,你会采取哪些步骤来定位和修复问题?
获取崩溃堆栈日志:Logcat;线上收集崩溃日志(Bugly、Firebase),看异常类型、异常栈;
复现:尽可能复现崩溃,复现路径;
分析异常类型:空指针、数组越界、权限、内存 OOM、Android 版本适配、并发异常;
定位代码行,分析触发条件;
修复代码,增加判空,捕获异常,处理权限;
测试验证,回归;
OOM 崩溃:用 Profiler/LeakCanary 排查内存泄漏、大图片;
原生 crash:看 tombstone 日志,native 层崩溃。
八、新技术和趋势 Jetpack
1. 介绍一些 Jetpack 组件,如 ViewModel、LiveData、Room 等,以及它们的作用。
ViewModel:存储页面数据,屏幕旋转配置变更不会销毁;生命周期独立于 Activity;解决屏幕旋转数据丢失;不要持有 Context,用 AndroidViewModel。
LiveData:可观察的数据持有者,感知生命周期;只有页面处于活跃状态 (STARTED/RESUMED) 才回调;自动解绑,避免内存泄漏。常配合 ViewModel 使用。
Room:SQLite 的 ORM 封装库;注解生成数据库代码;简化数据库操作;支持协程;避免手写 SQL 大量模板代码。
DataBinding / ViewBinding:ViewBinding 简化控件 id 绑定;DataBinding 支持数据绑定,MVVM。
WorkManager:后台任务调度;适合需要保证执行的后台任务;兼容低版本,系统会智能调度。
Navigation:页面导航框架,统一管理 Fragment 跳转,管理回退栈。
Lifecycle:生命周期感知组件;可以监听 Activity/Fragment 生命周期,不用写大量生命周期回调。
2. 如何在项目中使用 Jetpack 进行开发?
build.gradle 引入各个 Jetpack 组件依赖;
架构选用 MVVM;UI 层 Activity/Fragment;ViewModel 存放业务数据;
ViewModel 持有 LiveData,UI 观察 LiveData 数据变化更新界面;
使用 Lifecycle 感知生命周期;
ViewBinding 替换 findViewById;
Room 处理本地数据库;
WorkManager 处理后台任务;
Navigation 统一管理 Fragment 跳转。
核心:分离 UI 与业务逻辑,ViewModel 负责数据,View 只负责 UI 渲染。
Android12 及以上新特性,说说 Android12 引入的一些新特性,如隐私保护、界面设计等
界面 Material You 设计:Material3,动态取色,系统根据壁纸生成主题颜色;组件圆角、动画全新改版。
隐私保护
精确位置和大概位置:APP 申请定位,用户可以选择粗略定位;
附近设备权限:蓝牙扫描不再直接申请蓝牙权限,使用附近设备权限;
隐私指示器:状态栏出现圆点,APP 使用相机麦克风,状态栏提示用户;
一次性权限:权限可以只授予这一次,下次打开重新申请;
应用行为:后台弹窗限制,后台 APP 不能随意弹出 activity;
图片选择器 Photo Picker,系统相册选择器,不需要申请相册读写权限;
应用休眠:长时间不使用 APP,撤销权限;
网络:Wi‑Fi 扫描权限变更。
窗口、折叠屏适配支持。