当前位置: 首页 > news >正文

Android第六次面试总结之Java设计模式篇(一)

一、单例模式在 Android 面试中的核心考点

1. Android 中如何安全实现单例?需注意哪些坑?(字节跳动、美团面试真题)

解答
Android 中实现单例需重点关注 Context 泄漏线程安全 和 反射 / 序列化攻击

  • 推荐实现:静态内部类(线程安全 + 避免内存泄漏)
    public class AppManager {  private Context context;  // 静态内部类持有实例(类加载时初始化,线程安全)  private static class Holder {  static final AppManager INSTANCE = new AppManager();  }  private AppManager() {  // 使用 Application Context,避免持有 Activity Context 导致泄漏  context = MyApplication.getAppContext();  }  public static AppManager getInstance() {  return Holder.INSTANCE;  }  
    }  
    
  • 注意事项
    • Context 选择:单例若需持有 Context,必须使用 Application Context(通过 getApplicationContext() 或自定义 Application 类获取),避免传入 Activity Context 导致内存泄漏(Activity 销毁后单例仍持有其引用)。
    • 反序列化安全:若单例需实现 Serializable,需添加 readResolve() 方法返回已有实例;或直接使用 枚举单例(Android 中枚举序列化安全,且代码简洁)。
2. 单例在 Android 中的典型应用场景(阿里、腾讯面试真题)

解答

  • 全局管理器:如网络请求管理器(Retrofit 单例)、图片加载器(Glide 内部单例)、数据库管理类(Room Database 单例)。
  • 状态管理:全局用户信息、配置参数(如 App 主题、语言设置)。
  • 系统服务代理:封装 SensorManagerNotificationManager 等系统服务,提供统一访问入口。
3. 反模式:为什么不推荐在 Android 中使用双重检查锁(DCL)?

解答

  • DCL 需配合 volatile 关键字防止指令重排序,但 Android 早期版本(如 Java 1.4 前)的 JVM 对 volatile 支持不完整,可能导致实例未完全初始化就被访问。
  • 静态内部类或枚举单例实现更简单且线程安全,无需手动处理同步,是 Android 中的首选方案。

二、工厂模式在 Android 开发中的实战场景

1. LayoutInflater 如何体现工厂模式?如何自定义 View 工厂?(字节跳动面试真题)

解答

  • 系统级应用:Android 的 LayoutInflater 是典型的 工厂方法模式,通过 inflate() 方法根据布局文件创建 View 实例,子类(如 AppCompatDelegate)可自定义 View 创建逻辑(如兼容旧版控件)。
  • 自定义 View 工厂(示例:根据类型创建不同的 ViewHolder):
    public interface ViewHolderFactory {  ViewHolder createViewHolder(View itemView, int viewType);  
    }  
    // 实现类  
    public class DefaultViewHolderFactory implements ViewHolderFactory {  @Override  public ViewHolder createViewHolder(View itemView, int viewType) {  if (viewType == TYPE_TEXT) {  return new TextViewHolder(itemView);  } else if (viewType == TYPE_IMAGE) {  return new ImageViewHolder(itemView);  }  throw new IllegalArgumentException("Unknown view type");  }  
    }  
    
  • 优势:解耦 View 创建逻辑,方便扩展(如新增 ViewType 时无需修改适配器核心代码)。
2. 对比简单工厂 vs 工厂方法:何时选择哪种?

Android 场景举例

  • 简单工厂:适合轻量化场景,如根据类型创建不同的动画对象(AnimationFactory.createAnimation (type)),新增类型需修改工厂类。
  • 工厂方法:适合复杂场景或需遵循开闭原则,如 RecyclerView 的 ViewHolder 创建(通过 onCreateViewHolder 由子类实现)。
3. ThreadFactory 为什么是线程池的 “灵魂组件”?(字节跳动、美团面试真题)

源码级解析
线程池(如ThreadPoolExecutor)通过ThreadFactory创建线程,核心作用:

  • 解耦线程创建逻辑:将 “如何创建线程”(如命名、优先级、守护线程)与 “如何管理线程”(如任务队列、拒绝策略)分离。
  • 统一线程属性:确保线程池内所有线程具备相同的基础配置(如业务线程设置setName("Biz-Thread-%d"),便于日志追踪)。

自定义 ThreadFactory 实战(面试必考代码)

public class NamedThreadFactory implements ThreadFactory {  private final String namePrefix;  private final boolean daemon;  private final int priority;  public NamedThreadFactory(String namePrefix, boolean daemon, int priority) {  this.namePrefix = namePrefix;  this.daemon = daemon;  this.priority = Math.min(Math.max(priority, Thread.MIN_PRIORITY), Thread.MAX_PRIORITY);  }  @Override  public Thread newThread(Runnable r) {  Thread thread = new Thread(r, namePrefix + "-" + counter.incrementAndGet());  thread.setDaemon(daemon); // 设置守护线程(如后台日志线程)  thread.setPriority(priority);  // 关键:为线程设置未捕获异常处理器,避免静默崩溃  thread.setUncaughtExceptionHandler((t, e) -> {  Log.e("ThreadFactory", "Thread " + t.getName() + " crashed: " + e.getMessage());  });  return thread;  }  private final AtomicInteger counter = new AtomicInteger(1);  
}  
4. 线程池工厂模式的 5 大面试考点

① 为什么线程池不直接使用 new Thread (),而是通过工厂?

  • 答案
    • 统一管控线程属性(命名规则、优先级),避免 “野线程”(无意义的 Thread-0/1/2),提升调试效率(通过线程名快速定位问题线程)。
    • 支持扩展(如创建守护线程、设置安全上下文AccessControlContext)。

② FixedThreadPool 为什么被弃用?与工厂模式的关系?

  • 源码对比(JDK 8):
    // 旧版FixedThreadPool(硬编码工厂,无自定义能力)  
    public static ExecutorService newFixedThreadPool(int nThreads) {  return new ThreadPoolExecutor(nThreads, nThreads,  0L, TimeUnit.MILLISECONDS,  new LinkedBlockingQueue<Runnable>(),  new DefaultThreadFactory()); // 匿名工厂,线程名无业务含义  
    }  
    // 推荐做法:自定义工厂  
    ExecutorService pool = new ThreadPoolExecutor(  4, 8, 30, SECONDS,  new ArrayBlockingQueue<>(100),  new NamedThreadFactory("Order-Processor", false, Thread.NORM_PRIORITY)  
    );  
    
  • 弃用原因:默认工厂创建的线程名无业务意义,且LinkedBlockingQueue可能导致 OOM(队列无界),自定义工厂 + 合理参数是现代开发标配。

③ 守护线程在工厂中的应用场景

  • 场景:后台日志收集线程、心跳检测线程,设置thread.setDaemon(true),确保程序退出时随主线程终止,避免资源泄漏。

④ 工厂模式如何配合线程池拒绝策略?

  • 当线程池拒绝任务(如AbortPolicy抛出异常),可通过工厂为线程添加钩子函数,监控拒绝事件:
    // 在newThread中设置钩子  
    thread.setUncaughtExceptionHandler((t, e) -> {  if (e instanceof RejectedExecutionException) {  metrics.trackRejectedTask(t.getName()); // 统计拒绝次数  }  
    });  
    

三、建造者模式在 Android 中的高频考点

1. OkHttpClient.Builder:网络配置的 “瑞士军刀”(字节跳动、腾讯面试真题)

核心配置项源码解析

public final class OkHttpClient.Builder {  // 必选参数(无默认值,构建时校验)  private int connectTimeout = 10_000; // 10秒  private int readTimeout = 10_000;  private int writeTimeout = 10_000;  // 可选参数(有默认实现,支持链式覆盖)  private List<Interceptor> interceptors = new ArrayList<>(); // 应用拦截器(用户自定义)  private List<Interceptor> networkInterceptors = new ArrayList<>(); // 网络拦截器(OkHttp内部)  private ConnectionPool connectionPool = new ConnectionPool(); // 连接池(默认保持5分钟)  // 链式方法本质:返回this,支持连续调用  public Builder connectTimeout(int timeout, TimeUnit unit) {  this.connectTimeout = unit.toMillis(timeout);  return this;  }  // 构建核心:校验参数 + 不可变对象创建  public OkHttpClient build() {  return new OkHttpClient(this); // 将Builder状态复制到OkHttpClient实例  }  
}  

面试高频问题:
① 应用拦截器 vs 网络拦截器(顺序决定行为)

  • 执行顺序:应用拦截器(用户自定义)→ 重试 & 重定向拦截器 → 桥接拦截器 → 网络拦截器 → 连接池拦截器 → 最后是实际网络请求。
  • 典型场景
    • 应用拦截器:添加公共 Header(如 Token)、日志打印(不关心重定向)。
    • 网络拦截器:处理响应体压缩(Gzip)、监控真实网络请求耗时(排除重试逻辑)。

② 连接池为什么默认保持 5 分钟?建造者如何自定义

// 自定义连接池(长连接场景)  
OkHttpClient client = new OkHttpClient.Builder()  .connectionPool(new ConnectionPool(10, 30, TimeUnit.MINUTES)) // 保持10个空闲连接,30分钟  .build();  
  • 原理:通过建造者设置ConnectionPool,避免频繁创建 TCP 连接(三次握手开销),提升 HTTPS 请求性能。
2. Retrofit.Builder:从接口到网络请求的 “转换器工厂”(阿里、美团面试真题)

核心扩展点源码解析

public final class Retrofit {  private final String baseUrl;  private final List<Converter.Factory> converterFactories; // 数据转换器(如Gson/Jackson)  private final List<CallAdapter.Factory> callAdapterFactories; // 回调适配器(如RxJava、Kotlin Coroutines)  private final OkHttpClient client; // 依赖OkHttp的建造者配置  // 建造者核心:链式添加工厂  public Builder addConverterFactory(Converter.Factory factory) {  converterFactories.add(factory);  return this;  }  public Builder client(OkHttpClient client) {  this.client = client; // 可复用外部配置好的OkHttpClient(如已添加日志拦截器)  return this;  }  
}  

面试高频问题:
① 为什么 Retrofit 需要 Converter.Factory?自定义转换器如何实现?

  • 答案
    Retrofit 通过工厂模式解耦 “网络字节流” 与 “业务对象” 的转换逻辑,例如:
    // 自定义Gson转换器(支持LocalDateTime序列化)  
    public class CustomGsonConverterFactory extends Converter.Factory {  private final Gson gson;  public static CustomGsonConverterFactory create() {  return create(new GsonBuilder()  .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())  .create());  }  // 重写requestBodyConverter和responseBodyConverter方法  
    }  
    // 使用时通过建造者添加  
    Retrofit retrofit = new Retrofit.Builder()  .addConverterFactory(CustomGsonConverterFactory.create())  .build();  
    

② Retrofit 为什么推荐复用 OkHttpClient 实例?与建造者模式的关系?

  • 性能原因:OkHttpClient 内部的连接池、DNS 缓存等是重量级资源,重复创建会导致性能下降。
  • 建造者实践
    // 全局单例OkHttpClient(通过建造者配置)  
    private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient.Builder()  .connectTimeout(15, SECONDS)  .addInterceptor(new LoggingInterceptor())  .build();  // Retrofit复用该实例  
    private static final Retrofit RETROFIT = new Retrofit.Builder()  .baseUrl("https://api.example.com")  .client(OK_HTTP_CLIENT)  .addConverterFactory(GsonConverterFactory.create())  .build();  
    
3. OkHttp vs Retrofit 建造者模式对比(面试必考点)
特性OkHttpClient.BuilderRetrofit.Builder
核心职责配置网络底层(连接、拦截器、证书、连接池)配置接口转换(BaseUrl、数据转换器、回调适配)
不可变对象构建后OkHttpClient所有属性不可变同理,Retrofit实例构建后不可变
依赖关系独立配置,可被 Retrofit.Builder 引用必须依赖 OkHttpClient(或使用默认实例)
链式调用核心网络参数的 “物理层” 配置(如超时、代理)业务层抽象(如将接口方法转为 HTTP 请求)
面试陷阱忘记设置readTimeout导致 Socket 永久阻塞未添加数据转换器导致ClassCastException
Android 场景如何区分?

解答

  • 工厂模式:适合 “一键创建” 简单对象,如 LayoutInflater.inflate() 直接生成 View。
  • 建造者模式:适合 “分步配置” 复杂对象,如配置一个带有拦截器、超时时间、缓存策略的 OkHttpClient(需多个可选参数组合)。

四、Android 面试高频综合题:设计模式与性能 / 内存优化结合

1. 单例持有 Activity Context 为什么会导致内存泄漏?如何避免?(阿里、美团面试真题)

解答

  • 原理:单例是全局静态实例,若持有非静态的 Activity Context,当 Activity 销毁后,单例的强引用会阻止 Activity 被回收,导致内存泄漏。
  • 解决方案
    • 单例中使用 Application Context(生命周期与 App 一致)。
    • 若必须持有 Activity Context,可使用 弱引用(但需注意空指针问题,非推荐方案)。
2. 线程池工厂模式如何避免 “幽灵线程”?(字节跳动面试真题)
  • 问题场景:线程异常终止后,线程池通过工厂创建新线程,但未记录历史,导致调试困难。
  • 解决方案
    // 在ThreadFactory中添加线程创建序号和业务标签  
    public Thread newThread(Runnable r) {  Thread thread = new Thread(r, String.format("%s-%d", namePrefix, counter.getAndIncrement()));  // 关键:设置线程上下文(如MDC,用于日志关联)  MDC.put("thread_id", thread.getName());  return thread;  
    }  
    
3. OkHttp 建造者如何优化 HTTPS 性能?(阿里面试真题)
  • 实战配置
    OkHttpClient client = new OkHttpClient.Builder()  // 启用TLS 1.3(比1.2快50%握手时间)  .sslSocketFactory(sslContext.getSocketFactory(), trustManager)  .protocols(Arrays.asList(Protocol.TLS_1_3, Protocol.TLS_1_2))  // 配置连接池(长连接场景)  .connectionPool(new ConnectionPool(50, 5, TimeUnit.MINUTES))  // 启用HTTP/2(需服务端支持)  .addInterceptor(new OkHttp3.Http2CleartextInterceptor())  .build();  
    
4. Retrofit 建造者如何处理多环境配置(开发 / 测试 / 生产)?
  • 工厂方法 + 建造者组合模式
    public class RetrofitFactory {  private static final Map<String, Retrofit> retrofitMap = new HashMap<>();  public static Retrofit getRetrofit(String env) {  if (!retrofitMap.containsKey(env)) {  OkHttpClient client = new OkHttpClient.Builder()  .addInterceptor(new EnvInterceptor(env)) // 根据环境切换BaseUrl  .build();  Retrofit retrofit = new Retrofit.Builder()  .client(client)  .baseUrl(getBaseUrlByEnv(env))  .addConverterFactory(GsonConverterFactory.create())  .build();  retrofitMap.put(env, retrofit);  }  return retrofitMap.get(env);  }  
    }  
    

相关文章:

  • 使用VMware Workstation pro 17.5.1在Windows上安装Ubuntu 24.04.2的 详细步骤
  • 结合Hutool 突增突降检测的算法
  • javascript Map 和对象使用
  • 安卓基础(点击按钮动态添加视图到容器)
  • 单片机-STM32部分:5、STM32CubeMX实现HAL点灯
  • Leetcode Hot 100字母异位词分词
  • Vue 项目中使用 EJS 模板动态注入环境变量
  • 哪些岗位需要考取城市客运安全员证?
  • SCINet 训练代码修改
  • cmake qt 项目编译(win)
  • npm下载插件无法更新package.json和package-lock.json文件的解决办法
  • clickhouse - 重新建表覆盖旧表-解决分区时间错误问题-197001
  • AI内容检测的技术优势与应用场景
  • Java注解
  • Linux开发工具【上】
  • win11共享打印机主机设置
  • 使用 Python 监控系统资源
  • LeetCode 解题思路 45(分割等和子集、最长有效括号)
  • 程序员学商务英语之Shipment Claim 运输和索赔
  • LeetCode 每日一题 2025/4/28-2025/5/4
  • 8小时《大师与玛格丽特》:长度可以是特点,但不是价值标准
  • 韩国法院将李在明所涉案件重审日期延至大选后
  • 正荣地产:公司控股股东已获委任联合清盘人
  • 多个“网约摩托车”平台上线,工人日报:安全与监管不能掉队
  • 我国科研团队发布第四代量子计算测控系统
  • 秦洪看盘|涌现新逻辑,A股放量回升