杰瑞科技汇

java android面试题

目录

  1. Java 基础
  2. Android 基础
  3. Android 进阶与核心组件
  4. 性能优化
  5. 架构与设计模式
  6. 数据存储与网络
  7. 多线程与并发
  8. 项目与软技能
  9. 热点技术与未来趋势

Java 基础

这部分是面试的敲门砖,考察你对语言核心特性的理解深度。

java android面试题-图1
(图片来源网络,侵删)

面向对象

  • 什么是面向对象编程?
    • 考察点:基本概念,回答时应包含封装、继承、多态。
  • 封装、继承、多态是什么,并举例说明。
    • 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。private 修饰成员变量,通过 publicgetter/setter 方法访问。
    • 继承:子类继承父类的非私有属性和方法,实现代码复用。Dog extends Animal
    • 多态:同一操作作用于不同的对象,可以产生不同的执行结果,实现方式:重写 和接口实现。Animal a = new Dog(); a.eat(); 会调用 Dogeat() 方法。
  • final, finally, finalize 的区别。
    • final:关键字,可以修饰类(不可被继承)、方法(不可被重写)、变量(不可被修改)。
    • finally:关键字,用于 try-catch 语句块中,表示无论是否发生异常,代码块都会执行,通常用于资源释放。
    • finalize:方法,是 Object 类的一个方法,当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。不推荐使用,因为它不可靠,且在 Android 中可能导致内存抖动。
  • equals() 的区别。
    • 对于基本数据类型,比较的是值是否相等;对于引用数据类型,比较的是两个对象的内存地址是否相等。
    • equals():是 Object 类的方法,默认行为也是比较内存地址,但很多类(如 String, Integer)重写了 equals() 方法,用于比较内容是否相等。
  • 重写 和重载 的区别。
    • 重写:发生在父子类中,方法名、参数列表、返回值类型必须相同(返回值可以是子类),访问修饰符不能更严格,遵循“运行时多态”。
    • 重载:发生在同一个类中,方法名相同,参数列表不同(参数个数、类型、顺序不同),与返回值类型无关,遵循“编译时多态”。

集合框架

  • ArrayListLinkedList 的区别。
    • 底层数据结构ArrayList 是动态数组,LinkedList 是双向链表。
    • 随机访问ArrayList 通过索引访问很快 O(1)LinkedList 需要从头或尾遍历 O(n)
    • 增删操作ArrayList 在中间增删慢(需要移动元素)O(n),在末尾增删很快(可能需要扩容)O(1) (均摊)。LinkedList 在任意位置增删很快 O(1),只需修改前后节点的指针。
    • 内存占用LinkedList 每个节点需要额外存储前后节点的引用,内存占用更高。
  • **HashMap 的工作原理。**
    • 底层数据结构:数组 + 链表/红黑树 (JDK 1.8 之后)。
    • 核心流程
      1. 计算 keyhashCode()
      2. 通过 (n - 1) & hash 计算出在数组中的索引位置。
      3. 如果该位置为空,直接存放。
      4. 如果该位置不为空,发生哈希冲突,会先比较 keyhashCode()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 的生命周期,以及 FragmentActivity 的生命周期关系。
    • Fragment 生命周期onAttach() -> onCreate() -> onCreateView() -> onActivityCreated() -> onStart() -> onResume() -> onPause() -> onStop() -> onDestroyView() -> onDestroy() -> onDetach()
    • 关系:Fragment 的生命周期依赖于其所在的 Activity,当 ActivityonResume() 调用后,FragmentonResume() 才会被调用。
  • 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 的最终宽高,从 ViewRootmeasure() 开始,递归调用 onMeasure()
    • Layout (布局)layout() 方法,确定 View 在父容器中的最终位置和尺寸,递归调用 onLayout()
    • Draw (绘制)draw() 方法,将 View 的内容绘制到屏幕上,递归调用 onDraw()
  • ViewViewGroup 的区别,ViewGroup 如何测量子 View
    • View 是 UI 的基本单元,而 ViewGroup 是一个可以包含子 View 的容器。
    • ViewGroup 通过 measureChildren() 方法遍历所有子 View,并调用 measure() 方法来测量它们。ViewGrouponMeasure() 方法通常会先测量所有子 View,然后根据子 View 的尺寸和布局规则,计算出自己的尺寸。
  • Android 中的布局优化。
    • 减少布局层级:使用 <include>, <merge>, <ViewStub>
    • 避免过度绘制:使用 GPU 过度绘制工具分析,移除不必要的背景,使用 clipRect 裁剪。
    • 使用 ConstraintLayout:减少布局嵌套,提升性能。
    • 复用 View:在 ListViewRecyclerView 中使用 ViewHolder 模式。

Android 进阶与核心组件

这部分是区分中高级开发者的关键,考察对系统机制和现代开发框架的理解。

Handler 机制

  • 请描述一下 Handler 的工作原理。
    • 核心组成Message, MessageQueue, Looper, Handler
    • 工作流程
      1. Handler 发送 MessageMessageQueue
      2. Looper 在一个无限循环中,不断从 MessageQueue 中取出 Message
      3. Looper 将取出的 Message 交给创建该 MessageHandlerdispatchMessage() 方法处理。
      4. HandlerhandleMessage() 方法被调用。
    • 线程关系:每个线程(除了主线程)默认没有 Looper,必须在子线程中调用 Looper.prepare()Looper.loop() 来创建消息循环。Handler 在哪个线程创建,就和哪个线程的 Looper 关联。
  • 为什么主线程可以创建 Handler 而不需要手动 prepare()
    • 因为在 Activity 启动时,系统已经为主线程(UI 线程)自动创建了 LooperMessageQueue
  • Handler 可能会导致哪些问题?如何解决?
    • 内存泄漏:当 Handler 持有 Activity 或 Context 的引用时,Activity 销毁而 Handler 的消息队列中还有未处理的延迟消息,会导致 Activity 无法被回收,从而造成内存泄漏。
    • 解决方案
      1. 使用 static 修饰 Handler 内部类,这样它就不会持有外部类的引用。
      2. ActivityonDestroy()onStop() 中,调用 handler.removeCallbacksAndMessages(null) 清空所有消息。
      3. 使用 WeakReference 引用 Activity。

Intent 与 IPC

  • Intent 的作用,显式 Intent 和隐式 Intent 的区别。
    • 作用:用于组件间通信,可以启动 Activity, Service, 发送 Broadcast
    • 显式 Intent:明确指定了要启动的组件的 ComponentName(包名+类名),通常用于应用内部组件跳转。
    • 隐式 Intent:没有指定具体组件,而是通过 action, category, data 等信息来声明一种通用操作,系统会找到能匹配此 Intent 的组件,通常用于调用系统应用(如打开网页、拨打电话)或应用间通信。
  • SerializableParcelable 的区别,为什么 Parcelable 在 Android 中更推荐?
    • 实现方式Serializable 是 Java 接口,通过序列化实现,使用简单但会产生大量临时对象,性能较差。Parcelable 是 Android 特有接口,通过将数据写入 Parcel 对象实现,性能高。
    • 性能Parcelable 的性能远高于 Serializable,因为它避免了大量的 I/O 操作。
    • 使用场景Serializable 适合序列化到存储或网络传输。Parcelable 主要用于在 Android 组件间(如 Intent, Bundle)传递数据。

多线程

  • AsyncTask 的工作原理,它的缺点是什么?
    • 原理:一个轻量级的异步类,内部使用 ThreadPoolExecutorHandler,在 doInBackground() 中执行耗时任务,然后通过 Handler 将结果发送到主线程,并调用 onPostExecute()
    • 缺点
      1. 生命周期问题AsyncTask 的任务执行时间超过了 Activity 的生命周期,可能会导致内存泄漏或 onPostExecute() 在已销毁的 Activity 上执行。
      2. 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 检测主线程的耗时操作。
  • 如何进行内存泄漏分析?
    • Android Profiler:实时监控内存分配和回收情况,观察内存曲线是否正常。
    • LeakCanary:Square 开源的内存泄漏检测库,在 Activity/Fragment 销毁后,会自动在后台检测是否存在内存泄漏,并通过通知告知开发者。
    • MAT (Memory Analyzer Tool):强大的内存分析工具,可以生成 hprof 文件,通过 Leak Suspects 报告快速定位内存泄漏。
  • 启动优化有哪些方法?
    • 冷启动优化
      1. 优化 Application 初始化:避免在 onCreate() 中做耗时操作,可以延迟初始化或使用异步初始化。
      2. 优化布局加载:使用 <merge>, <ViewStub>,减少 LayoutInflater 的时间。
      3. 优化第三方库初始化:按需初始化,避免一启动就加载所有 SDK。
      4. 子线程初始化:将一些非 UI 相关的耗时任务放到子线程中执行。
    • 测量工具adb shell am start -W [packageName]/[activityName]
  • UI 卡顿优化有哪些方法?
    • 避免在主线程进行耗时操作:网络请求、文件读写、复杂计算等。
    • 优化布局:减少布局层级,使用 ConstraintLayout
    • 优化自定义 View:在 onDraw() 中避免创建对象,避免复杂的 onDraw() 逻辑。
    • 使用 RecyclerView 代替 ListViewRecyclerViewViewHolder 模式极大地提升了列表滚动性能。
    • 避免过度绘制:使用 GPU 过度绘制工具分析并优化。

架构与设计模式

考察你的代码组织能力和工程化思想。

java android面试题-图2
(图片来源网络,侵删)

MVVM 架构

  • 请解释一下 MVVM 架构。
    • Model:数据层,负责数据的获取、存储和业务逻辑,可以是数据模型、网络请求、数据库等。
    • View:UI 层,负责界面的展示和用户交互,在 Android 中是 Activity, Fragment, Layout 等,它观察 ViewModel 的变化并更新 UI。
    • ViewModel:视图模型,作为 ViewModel 之间的桥梁,它持有 View 所需的数据和状态,并处理 View 的用户交互逻辑,它不持有 View 的引用,View 被销毁时,ViewModel 依然存在,有助于配置更改时数据不丢失。
  • MVVM 和 MVP 的区别。
    • 通信方式:MVP 中,ViewPresenter 通过接口直接引用和调用,MVVM 中,View 通过 Data BindingLiveData 等 观察 ViewModel 的变化,是数据驱动的,耦合更低。
    • 代码量:MVVM 由于使用 Data Binding,可以减少大量 findViewByIdsetXXX 的样板代码。
    • 测试性:两者都易于进行单元测试,因为 PresenterViewModel 都不持有 View 的引用。

其他设计模式

  • 单例模式:确保一个类只有一个实例,并提供一个全局访问点。
    • 实现方式:饿汉式、懒汉式(双重检查锁)、静态内部类、枚举,在 Android 中,Application 对象就是一个单例。
  • 工厂模式:定义一个创建对象的接口,让子类决定实例化哪一个类,将实例的创建延迟到子类。
  • 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
    • Android 中的体现LiveDataEventBusRxJavaBroadcastReceiver

数据存储与网络

  • Android 中有哪些数据存储方式?
    • SharedPreferences:轻量级键值对存储,适合保存简单配置信息,底层是 XML 文件。
    • 文件存储:将数据存储在设备的文件系统中,可以是内部存储或外部存储。
    • SQLite 数据库:轻量级的关系型数据库,适合存储结构化数据。
    • ContentProvider:用于在不同应用间共享数据。
    • 网络存储:将数据存储在服务器上。
  • OkHttp 的工作原理。
    • 核心Dispatcher (分发器,管理线程池), Interceptor (拦截器)。
    • 流程
      1. 构建 Request 对象。
      2. 通过 OkHttpClient 发起请求。
      3. 请求经过一系列拦截器(应用拦截器 -> 网络拦截器 -> 网络请求)。
      4. 得到 Response 后,再经过一系列拦截器返回给用户。
    • 拦截器:是 OkHttp 的精髓,可以实现对请求和响应的统一处理,如添加通用 Header、日志打印、缓存处理等。
  • Retrofit 的工作原理。
    • 本质:一个基于 OkHttp 的 RESTful 网络请求框架,它将网络请求接口化。
    • 原理:动态代理 + 注解解析。
      1. 通过 @GET, @POST 等注解定义接口。
      2. Retrofit 在创建时,会使用动态代理(Proxy.newProxyInstance)创建接口的代理对象。
      3. 当调用接口方法时,代理对象会拦截该方法,解析方法上的注解(如路径、请求方法、参数等)。
      4. 将解析后的信息封装成一个 Request 对象,然后交给 OkHttp 去执行。
      5. 使用 Converter (如 Gson) 将 OkHttp 返回的 Response Body 转换成我们需要的 Java 对象。

多线程与并发

  • Thread, Runnable, Callable, Future 的区别。
    • Thread:线程类,是执行任务的载体。
    • Runnable:任务接口,定义了任务要执行的内容,没有返回值,不能抛出受检异常。
    • Callable:任务接口,与 Runnable 类似,但有返回值,可以抛出受检异常。
    • Future:表示异步计算的结果,可以用来检查任务是否完成,获取任务结果,或取消任务。
  • CountDownLatchCyclicBarrier 的区别。
    • CountDownLatch:一个或多个线程,等待其他多个线程完成某项操作之后,才能继续执行,它是一次性的。
    • CyclicBarrier:多个线程互相等待,直到所有线程都到达某个屏障点,然后再一起继续执行,它是可循环使用的。

项目与软技能

  • 请介绍一下你做过的最有挑战性的项目,你在其中扮演的角色,遇到了什么困难,以及如何解决的?
    • 考察点:技术广度、深度、解决问题的能力、沟通协作能力。
    • 回答技巧:使用 STAR 法则(Situation-情境, Task-任务, Action-行动, Result-结果)来清晰地阐述,重点突出你的技术思考和贡献。
  • 你平时通过哪些途径学习新技术?
    • 考察点:学习能力和主动性。
    • 回答技巧:阅读官方文档、技术博客(Medium, Medium, 掘金)、开源项目源码、技术书籍、参加技术分享会等。
  • 你有什么问题想问我们吗?
    • 考察点:你对公司和岗位的兴趣程度。
    • 回答技巧:不要问薪资福利等敏感问题,可以问:
      • 团队的技术栈是怎样的?未来有什么技术规划?
      • 这个岗位的主要挑战是什么?
      • 我入职后会参与什么样的项目?

热点技术与未来趋势

  • Kotlin vs Java,你更倾向于哪个?为什么?
    • Kotlin 优势:空安全、扩展函数、协程、数据类、密封类等,代码更简洁、安全、表达力强,是 Google 官方推荐的开发语言。
    • Java 优势:生态极其成熟,库和资源丰富,在大型企业级项目中仍有大量应用。
    • 回答建议:表达对 Kotlin 的喜爱,并说明其在 Android 开发中的优势,同时尊重 Java 的历史地位。
  • 协程 是什么?它解决了什么问题?
    • 是什么:Kotlin 提供的一种并发编程方案,它可以在单线程上挂起和恢复函数,以实现异步代码的同步写法。
    • 解决了什么问题
      1. 回调地狱:将嵌套的回调代码写成线性的、易于理解的代码。
      2. 线程管理:简化了线程切换的复杂性,开发者无需手动管理线程池。
      3. 结构化并发:提供了更强大的作用域控制,可以确保协程在作用域结束时被取消,避免资源泄漏。
  • Jetpack Compose 是什么?它有什么优势?
    • 是什么:Android 官方推出的现代化 UI 工具包,用于构建原生 UI,它采用声明式 UI 编程范式。
    • 优势
      1. 更少的代码:消除了 findViewById 和大量样板代码。
      2. 更快的开发:提供强大的工具支持,如实时预览、状态管理。
      3. 更易于维护:状态和 UI 的关系更加清晰,不易出错。
      4. 性能更好:基于 Compose Compiler 生成高效的代码,避免不必要的重绘。

面试准备建议

  1. 理论与实践结合:不仅要会背答案,更要理解其背后的原理。HashMap 的工作原理,最好能自己动手画一遍图。
  2. 准备项目经验:对简历上的每一个项目都要了如指掌,能够清晰地讲出技术选型、难点、解决方案和最终成果。
  3. 刷 LeetCode:对于初级和中级岗位,刷一些简单的算法题(如数组、链表、字符串)是很有必要的。
  4. 模拟面试:找朋友或同事进行模拟面试,锻炼表达能力和临场反应。
  5. 了解目标公司:针对目标公司的业务和技术栈,重点复习相关的知识点,电商公司可能更关注列表性能和图片加载,社交公司可能更关注消息推送和长连接。

祝你面试顺利,拿到心仪的 Offer!

java android面试题-图3
(图片来源网络,侵删)
分享:
扫描分享到社交APP
上一篇
下一篇