目录
- Java 基础
- Android 基础
- Android 进阶与核心组件
- 性能优化
- 架构与设计模式
- 数据存储与网络
- 多线程与并发
- 项目与软技能
- 热点技术与未来趋势
Java 基础
这部分是面试的敲门砖,考察你对语言核心特性的理解深度。

面向对象
- 什么是面向对象编程?
- 考察点:基本概念,回答时应包含封装、继承、多态。
- 封装、继承、多态是什么,并举例说明。
- 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。
private修饰成员变量,通过public的getter/setter方法访问。 - 继承:子类继承父类的非私有属性和方法,实现代码复用。
Dog extends Animal。 - 多态:同一操作作用于不同的对象,可以产生不同的执行结果,实现方式:重写 和接口实现。
Animal a = new Dog(); a.eat();会调用Dog的eat()方法。
- 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。
final,finally,finalize的区别。final:关键字,可以修饰类(不可被继承)、方法(不可被重写)、变量(不可被修改)。finally:关键字,用于try-catch语句块中,表示无论是否发生异常,代码块都会执行,通常用于资源释放。finalize:方法,是Object类的一个方法,当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。不推荐使用,因为它不可靠,且在 Android 中可能导致内存抖动。
- 和
equals()的区别。- 对于基本数据类型,比较的是值是否相等;对于引用数据类型,比较的是两个对象的内存地址是否相等。
equals():是Object类的方法,默认行为也是比较内存地址,但很多类(如String,Integer)重写了equals()方法,用于比较内容是否相等。
- 重写 和重载 的区别。
- 重写:发生在父子类中,方法名、参数列表、返回值类型必须相同(返回值可以是子类),访问修饰符不能更严格,遵循“运行时多态”。
- 重载:发生在同一个类中,方法名相同,参数列表不同(参数个数、类型、顺序不同),与返回值类型无关,遵循“编译时多态”。
集合框架
ArrayList和LinkedList的区别。- 底层数据结构:
ArrayList是动态数组,LinkedList是双向链表。 - 随机访问:
ArrayList通过索引访问很快O(1),LinkedList需要从头或尾遍历O(n)。 - 增删操作:
ArrayList在中间增删慢(需要移动元素)O(n),在末尾增删很快(可能需要扩容)O(1)(均摊)。LinkedList在任意位置增删很快O(1),只需修改前后节点的指针。 - 内存占用:
LinkedList每个节点需要额外存储前后节点的引用,内存占用更高。
- 底层数据结构:
**HashMap的工作原理。**- 底层数据结构:数组 + 链表/红黑树 (JDK 1.8 之后)。
- 核心流程:
- 计算
key的hashCode()。 - 通过
(n - 1) & hash计算出在数组中的索引位置。 - 如果该位置为空,直接存放。
- 如果该位置不为空,发生哈希冲突,会先比较
key的hashCode()和equals(),如果相同则覆盖;如果不同,则以链表或红黑树的形式挂载在该位置。
- 计算
- 为什么是
2的幂次方容量? 为了让(n - 1) & hash这一步能均匀地分布元素,减少哈希冲突。 - 为什么在链表长度超过
8且数组长度超过64时,链表会转化为红黑树? 为了解决哈希冲突严重时,链表过长导致查询效率降低(从O(n)优化到O(log n))。
ConcurrentHashMap是如何保证线程安全的?- JDK 1.7:分段锁,将数据分成多个段,每个段有自己的锁,一个段锁住时,不影响其他段的读写。
- JDK 1.8:CAS +
synchronized,放弃分段锁,改为数组 + 链表/红黑树的结构,当发生哈希冲突时,使用synchronized锁住链表或红黑树的头节点,锁的粒度更小,在初始化和扩容时使用 CAS 操作保证原子性。
JVM 内存模型与垃圾回收
- 简述 JVM 内存区域(运行时数据区)。
- 堆:存放对象实例和数组,是垃圾回收的主要区域。
- 虚拟机栈:存储局部变量表、操作数栈、动态链接、方法出口等,线程私有,生命周期与线程相同。
- 本地方法栈:为虚拟机使用到的 Native 方法服务。
- 方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存数据,在 JDK 1.8+ 中被元空间 取代。
- 程序计数器:当前线程所执行的字节码行号指示器,线程私有。
- 什么是 GC Roots?哪些对象可以作为 GC Roots?
- GC Roots:是一组必须存活的对象,垃圾回收器通过从这些对象出发,查找所有可达对象,不可达的则被回收。
- GC Roots 包括:
- 虚拟机栈中引用的对象(如方法参数、局部变量)。
- 静态变量引用的对象。
- JNI(即 Native 方法)引用的对象。
- 常见的垃圾回收算法有哪些?
- 标记-清除:标记所有需要回收的对象,然后统一回收,缺点是效率不高,会产生大量内存碎片。
- 复制算法:将内存分为两块,每次只使用其中一块,当这块用完后,将存活对象复制到另一块,然后清空这一块,缺点是内存空间减半。
- 标记-整理:标记所有需要回收的对象,让所有存活对象都向内存空间一端移动,然后直接清理掉端边界以外的内存,结合了前两者的优点。
- 分代收集:根据对象存活周期的不同,将内存划分为新生代和老年代,新生代采用复制算法,老年代采用标记-清除或标记-整理算法,这是目前主流的 GC 策略。
Android 基础
这部分考察你对 Android 平台基本组件和生命周期的理解。
四大组件
- Activity 的生命周期,
onStart()和onResume()的区别。- 生命周期:
onCreate()->onStart()->onResume()->onPause()->onStop()->onDestroy()。 onStart():Activity 即将对用户可见,但还没有获取焦点,Activity 还在后台。onResume():Activity 已经可见并且获取了焦点,处于前台,可以与用户交互。
- 生命周期:
Fragment的生命周期,以及Fragment和Activity的生命周期关系。- Fragment 生命周期:
onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume()->onPause()->onStop()->onDestroyView()->onDestroy()->onDetach()。 - 关系:Fragment 的生命周期依赖于其所在的 Activity,当
Activity的onResume()调用后,Fragment的onResume()才会被调用。
- Fragment 生命周期:
Service的两种启动方式及其区别。startService():- 服务由
startService()启动,与启动它的组件没有绑定关系。 - 即使启动它的组件被销毁,服务依然在后台运行。
- 必须通过
stopService()或stopSelf()来停止服务。
- 服务由
bindService():- 服务由
bindService()启动,与启动它的组件绑定。 - 当绑定组件被销毁时,服务会自动被解绑并销毁。
- 可以通过
IBinder接口与 Service 进行通信。
- 服务由
BroadcastReceiver的两种注册方式及其区别。- 静态注册:在
AndroidManifest.xml中注册,即使应用没有运行,也能接收广播(如开机广播)。注意:Android 8.0 (API 26) 后,对隐式广播的静态注册做了严格限制。 - 动态注册:在代码中通过
Context.registerReceiver()注册,当应用进程不在运行时,无法接收广播,组件销毁时必须调用unregisterReceiver()解除注册,否则会引发内存泄漏。
- 静态注册:在
布局与 UI
View的测量、布局、绘制流程。- Measure (测量):
measure()方法,确定 View 的最终宽高,从ViewRoot的measure()开始,递归调用onMeasure()。 - Layout (布局):
layout()方法,确定 View 在父容器中的最终位置和尺寸,递归调用onLayout()。 - Draw (绘制):
draw()方法,将 View 的内容绘制到屏幕上,递归调用onDraw()。
- Measure (测量):
View和ViewGroup的区别,ViewGroup如何测量子View?View是 UI 的基本单元,而ViewGroup是一个可以包含子View的容器。ViewGroup通过measureChildren()方法遍历所有子 View,并调用measure()方法来测量它们。ViewGroup的onMeasure()方法通常会先测量所有子 View,然后根据子 View 的尺寸和布局规则,计算出自己的尺寸。
Android中的布局优化。- 减少布局层级:使用
<include>,<merge>,<ViewStub>- 避免过度绘制:使用
GPU过度绘制工具分析,移除不必要的背景,使用clipRect裁剪。- 使用
ConstraintLayout:减少布局嵌套,提升性能。- 复用
View:在ListView或RecyclerView中使用ViewHolder模式。 - 避免过度绘制:使用
- 减少布局层级:使用
Android 进阶与核心组件
这部分是区分中高级开发者的关键,考察对系统机制和现代开发框架的理解。
Handler 机制
- 请描述一下
Handler的工作原理。- 核心组成:
Message,MessageQueue,Looper,Handler。 - 工作流程:
Handler发送Message到MessageQueue。Looper在一个无限循环中,不断从MessageQueue中取出Message。Looper将取出的Message交给创建该Message的Handler的dispatchMessage()方法处理。Handler的handleMessage()方法被调用。
- 线程关系:每个线程(除了主线程)默认没有
Looper,必须在子线程中调用Looper.prepare()和Looper.loop()来创建消息循环。Handler在哪个线程创建,就和哪个线程的Looper关联。
- 核心组成:
- 为什么主线程可以创建
Handler而不需要手动prepare()?- 因为在 Activity 启动时,系统已经为主线程(UI 线程)自动创建了
Looper和MessageQueue。
- 因为在 Activity 启动时,系统已经为主线程(UI 线程)自动创建了
Handler可能会导致哪些问题?如何解决?- 内存泄漏:当
Handler持有 Activity 或 Context 的引用时,Activity 销毁而Handler的消息队列中还有未处理的延迟消息,会导致 Activity 无法被回收,从而造成内存泄漏。 - 解决方案:
- 使用
static修饰Handler内部类,这样它就不会持有外部类的引用。 - 在
Activity的onDestroy()或onStop()中,调用handler.removeCallbacksAndMessages(null)清空所有消息。 - 使用
WeakReference引用 Activity。
- 使用
- 内存泄漏:当
Intent 与 IPC
Intent的作用,显式Intent和隐式Intent的区别。- 作用:用于组件间通信,可以启动
Activity,Service, 发送Broadcast。 - 显式 Intent:明确指定了要启动的组件的
ComponentName(包名+类名),通常用于应用内部组件跳转。 - 隐式 Intent:没有指定具体组件,而是通过
action,category,data等信息来声明一种通用操作,系统会找到能匹配此 Intent 的组件,通常用于调用系统应用(如打开网页、拨打电话)或应用间通信。
- 作用:用于组件间通信,可以启动
Serializable和Parcelable的区别,为什么Parcelable在 Android 中更推荐?- 实现方式:
Serializable是 Java 接口,通过序列化实现,使用简单但会产生大量临时对象,性能较差。Parcelable是 Android 特有接口,通过将数据写入Parcel对象实现,性能高。 - 性能:
Parcelable的性能远高于Serializable,因为它避免了大量的 I/O 操作。 - 使用场景:
Serializable适合序列化到存储或网络传输。Parcelable主要用于在 Android 组件间(如Intent,Bundle)传递数据。
- 实现方式:
多线程
AsyncTask的工作原理,它的缺点是什么?- 原理:一个轻量级的异步类,内部使用
ThreadPoolExecutor和Handler,在doInBackground()中执行耗时任务,然后通过Handler将结果发送到主线程,并调用onPostExecute()。 - 缺点:
- 生命周期问题:
AsyncTask的任务执行时间超过了Activity的生命周期,可能会导致内存泄漏或onPostExecute()在已销毁的 Activity 上执行。 - API 废弃:从 Android 11 (API 30) 开始,
AsyncTask被标记为@Deprecated,不推荐在新代码中使用。
- 生命周期问题:
- 原理:一个轻量级的异步类,内部使用
ThreadPoolExecutor的核心参数。corePoolSize: 核心线程数。maximumPoolSize: 最大线程数。keepAliveTime: 非核心线程的空闲存活时间。unit:keepAliveTime的时间单位。workQueue: 任务队列,用于存放等待执行的任务。threadFactory: 线程工厂,用于创建线程。handler: 拒绝策略,当线程数和队列都满了时,对新任务的处理方式。
volatile关键字的作用。- 保证可见性:当一个线程修改了
volatile变量,新值会立刻同步到主内存,并且其他线程读取时会从主内存读取,保证了线程间的可见性。 - 禁止指令重排序:通过插入内存屏障,禁止
volatile变量前后的指令进行重排序优化。 - 不保证原子性:
volatile int count = 0; count++;这一步不是原子的,仍然可能并发问题。
- 保证可见性:当一个线程修改了
性能优化
这是高级工程师的核心能力,也是面试的重中之重。
- 如何进行 ANR 分析?
- ANR 日志:在
/data/anr/traces.txt文件中查看 ANR 发生时的线程堆栈,重点关注主线程(main)是否在执行耗时操作(如 I/O, 网络请求,复杂的计算)。 StrictMode:在开发阶段使用StrictMode检测主线程的耗时操作。
- ANR 日志:在
- 如何进行内存泄漏分析?
Android Profiler:实时监控内存分配和回收情况,观察内存曲线是否正常。LeakCanary:Square 开源的内存泄漏检测库,在 Activity/Fragment 销毁后,会自动在后台检测是否存在内存泄漏,并通过通知告知开发者。- MAT (Memory Analyzer Tool):强大的内存分析工具,可以生成
hprof文件,通过Leak Suspects报告快速定位内存泄漏。
- 启动优化有哪些方法?
- 冷启动优化:
- 优化 Application 初始化:避免在
onCreate()中做耗时操作,可以延迟初始化或使用异步初始化。 - 优化布局加载:使用
<merge>,<ViewStub>,减少LayoutInflater的时间。 - 优化第三方库初始化:按需初始化,避免一启动就加载所有 SDK。
- 子线程初始化:将一些非 UI 相关的耗时任务放到子线程中执行。
- 优化 Application 初始化:避免在
- 测量工具:
adb shell am start -W [packageName]/[activityName]。
- 冷启动优化:
- UI 卡顿优化有哪些方法?
- 避免在主线程进行耗时操作:网络请求、文件读写、复杂计算等。
- 优化布局:减少布局层级,使用
ConstraintLayout。 - 优化自定义 View:在
onDraw()中避免创建对象,避免复杂的onDraw()逻辑。 - 使用
RecyclerView代替ListView:RecyclerView的ViewHolder模式极大地提升了列表滚动性能。 - 避免过度绘制:使用
GPU过度绘制工具分析并优化。
架构与设计模式
考察你的代码组织能力和工程化思想。

MVVM 架构
- 请解释一下 MVVM 架构。
- Model:数据层,负责数据的获取、存储和业务逻辑,可以是数据模型、网络请求、数据库等。
- View:UI 层,负责界面的展示和用户交互,在 Android 中是
Activity,Fragment,Layout等,它观察ViewModel的变化并更新 UI。 - ViewModel:视图模型,作为
View和Model之间的桥梁,它持有View所需的数据和状态,并处理View的用户交互逻辑,它不持有View的引用,View被销毁时,ViewModel依然存在,有助于配置更改时数据不丢失。
- MVVM 和 MVP 的区别。
- 通信方式:MVP 中,
View和Presenter通过接口直接引用和调用,MVVM 中,View通过Data Binding或LiveData等 观察ViewModel的变化,是数据驱动的,耦合更低。 - 代码量:MVVM 由于使用
Data Binding,可以减少大量findViewById和setXXX的样板代码。 - 测试性:两者都易于进行单元测试,因为
Presenter和ViewModel都不持有View的引用。
- 通信方式:MVP 中,
其他设计模式
- 单例模式:确保一个类只有一个实例,并提供一个全局访问点。
- 实现方式:饿汉式、懒汉式(双重检查锁)、静态内部类、枚举,在 Android 中,
Application对象就是一个单例。
- 实现方式:饿汉式、懒汉式(双重检查锁)、静态内部类、枚举,在 Android 中,
- 工厂模式:定义一个创建对象的接口,让子类决定实例化哪一个类,将实例的创建延迟到子类。
- 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
- Android 中的体现:
LiveData、EventBus、RxJava、BroadcastReceiver。
- Android 中的体现:
数据存储与网络
- Android 中有哪些数据存储方式?
- SharedPreferences:轻量级键值对存储,适合保存简单配置信息,底层是 XML 文件。
- 文件存储:将数据存储在设备的文件系统中,可以是内部存储或外部存储。
- SQLite 数据库:轻量级的关系型数据库,适合存储结构化数据。
- ContentProvider:用于在不同应用间共享数据。
- 网络存储:将数据存储在服务器上。
OkHttp的工作原理。- 核心:
Dispatcher(分发器,管理线程池),Interceptor(拦截器)。 - 流程:
- 构建
Request对象。 - 通过
OkHttpClient发起请求。 - 请求经过一系列拦截器(应用拦截器 -> 网络拦截器 -> 网络请求)。
- 得到
Response后,再经过一系列拦截器返回给用户。
- 构建
- 拦截器:是
OkHttp的精髓,可以实现对请求和响应的统一处理,如添加通用 Header、日志打印、缓存处理等。
- 核心:
Retrofit的工作原理。- 本质:一个基于
OkHttp的 RESTful 网络请求框架,它将网络请求接口化。 - 原理:动态代理 + 注解解析。
- 通过
@GET,@POST等注解定义接口。 Retrofit在创建时,会使用动态代理(Proxy.newProxyInstance)创建接口的代理对象。- 当调用接口方法时,代理对象会拦截该方法,解析方法上的注解(如路径、请求方法、参数等)。
- 将解析后的信息封装成一个
Request对象,然后交给OkHttp去执行。 - 使用
Converter(如 Gson) 将OkHttp返回的ResponseBody 转换成我们需要的 Java 对象。
- 通过
- 本质:一个基于
多线程与并发
Thread,Runnable,Callable,Future的区别。Thread:线程类,是执行任务的载体。Runnable:任务接口,定义了任务要执行的内容,没有返回值,不能抛出受检异常。Callable:任务接口,与Runnable类似,但有返回值,可以抛出受检异常。Future:表示异步计算的结果,可以用来检查任务是否完成,获取任务结果,或取消任务。
CountDownLatch和CyclicBarrier的区别。CountDownLatch:一个或多个线程,等待其他多个线程完成某项操作之后,才能继续执行,它是一次性的。CyclicBarrier:多个线程互相等待,直到所有线程都到达某个屏障点,然后再一起继续执行,它是可循环使用的。
项目与软技能
- 请介绍一下你做过的最有挑战性的项目,你在其中扮演的角色,遇到了什么困难,以及如何解决的?
- 考察点:技术广度、深度、解决问题的能力、沟通协作能力。
- 回答技巧:使用 STAR 法则(Situation-情境, Task-任务, Action-行动, Result-结果)来清晰地阐述,重点突出你的技术思考和贡献。
- 你平时通过哪些途径学习新技术?
- 考察点:学习能力和主动性。
- 回答技巧:阅读官方文档、技术博客(Medium, Medium, 掘金)、开源项目源码、技术书籍、参加技术分享会等。
- 你有什么问题想问我们吗?
- 考察点:你对公司和岗位的兴趣程度。
- 回答技巧:不要问薪资福利等敏感问题,可以问:
- 团队的技术栈是怎样的?未来有什么技术规划?
- 这个岗位的主要挑战是什么?
- 我入职后会参与什么样的项目?
热点技术与未来趋势
- Kotlin vs Java,你更倾向于哪个?为什么?
- Kotlin 优势:空安全、扩展函数、协程、数据类、密封类等,代码更简洁、安全、表达力强,是 Google 官方推荐的开发语言。
- Java 优势:生态极其成熟,库和资源丰富,在大型企业级项目中仍有大量应用。
- 回答建议:表达对 Kotlin 的喜爱,并说明其在 Android 开发中的优势,同时尊重 Java 的历史地位。
- 协程 是什么?它解决了什么问题?
- 是什么:Kotlin 提供的一种并发编程方案,它可以在单线程上挂起和恢复函数,以实现异步代码的同步写法。
- 解决了什么问题:
- 回调地狱:将嵌套的回调代码写成线性的、易于理解的代码。
- 线程管理:简化了线程切换的复杂性,开发者无需手动管理线程池。
- 结构化并发:提供了更强大的作用域控制,可以确保协程在作用域结束时被取消,避免资源泄漏。
- Jetpack Compose 是什么?它有什么优势?
- 是什么:Android 官方推出的现代化 UI 工具包,用于构建原生 UI,它采用声明式 UI 编程范式。
- 优势:
- 更少的代码:消除了
findViewById和大量样板代码。 - 更快的开发:提供强大的工具支持,如实时预览、状态管理。
- 更易于维护:状态和 UI 的关系更加清晰,不易出错。
- 性能更好:基于
Compose Compiler生成高效的代码,避免不必要的重绘。
- 更少的代码:消除了
面试准备建议
- 理论与实践结合:不仅要会背答案,更要理解其背后的原理。
HashMap的工作原理,最好能自己动手画一遍图。 - 准备项目经验:对简历上的每一个项目都要了如指掌,能够清晰地讲出技术选型、难点、解决方案和最终成果。
- 刷 LeetCode:对于初级和中级岗位,刷一些简单的算法题(如数组、链表、字符串)是很有必要的。
- 模拟面试:找朋友或同事进行模拟面试,锻炼表达能力和临场反应。
- 了解目标公司:针对目标公司的业务和技术栈,重点复习相关的知识点,电商公司可能更关注列表性能和图片加载,社交公司可能更关注消息推送和长连接。
祝你面试顺利,拿到心仪的 Offer!

