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

一、适配器模式(Adapter Pattern)

1. ListView vs RecyclerView 的 Adapter 核心区别?为什么 RecyclerView 需要 ViewHolder?

解答

  • 核心区别

    特性 ListView.Adapter(如 ArrayAdapter) RecyclerView.Adapter
    ViewHolder 机制 无,直接通过 getView 重复创建 View(性能差) 强制使用 ViewHolder 缓存 View,避免重复 inflate
    数据更新 只能全局刷新(notifyDataSetChanged 支持局部刷新(notifyItemChanged 等细粒度方法)
    布局管理器 内置固定布局(垂直 / 水平) 可自定义(LinearLayoutManager/GridLayoutManager)
  • ViewHolder 必要性

    RecyclerView 通过 ViewHolder 缓存列表项的视图组件,避免每次滑动都调用 LayoutInflater.inflate,将列表滑动性能从 O(n) 提升至接近 O(1)

    java 复制代码
    // RecyclerView 适配器必须实现 ViewHolder  
    class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {  
        public static class ViewHolder extends RecyclerView.ViewHolder {  
            TextView textView;  
            ViewHolder(View itemView) { super(itemView); textView = itemView.findViewById(R.id.tv); }  
        }  
    }  
2. RecyclerView 适配器如何实现多类型布局?对比 ListView 有何优势?

解答

  • 多类型布局实现
    通过重写getItemType返回不同类型标识,在onCreateViewHolder中创建对应 ViewHolder。

    java 复制代码
    @Override  
    public int getItemType(int position) {  
        return dataList.get(position).getType(); // 根据数据类型返回不同标识  
    }  
    
    @NonNull  
    @Override  
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {  
        if (viewType == TYPE_ITEM1) {  
            return new Item1ViewHolder(inflate(R.layout.item1, parent));  
        } else {  
            return new Item2ViewHolder(inflate(R.layout.item2, parent));  
        }  
    }  
  • 对比 ListView 优势

    • ViewHolder 复用机制:避免重复创建 View,性能提升显著。
    • 局部刷新 :支持notifyItemChanged等细粒度更新,减少 UI 线程负担。
    • 布局灵活性 :通过LayoutManager实现线性、网格、瀑布流等多种布局。

真题陷阱

getItemType返回固定值,RecyclerView 会如何处理?

  • 答案:所有 Item 使用同一 ViewHolder,无法实现多类型布局。
3. 如何优化 RecyclerView 适配器的滑动性能?

解答

  • ViewHolder 复用 :避免在onCreateViewHolder频繁创建 View,RecyclerView 已内置此机制。
  • 减少布局层级 :使用merge标签或约束布局,避免过度嵌套。
  • 预加载与缓存
    • 设置setItemViewCacheSize增加缓存空间。
    • 开启setHasFixedSize(true)(若 Item 高度固定)。
  • 异步加载 :在onBindViewHolder中避免耗时操作,如网络请求或复杂计算。

真题陷阱

为什么setHasFixedSize(true)能提升性能?

  • 答案 :告知 RecyclerView 无需重新测量 Item 尺寸,减少requestLayout调用。

二、观察者模式(Observer Pattern)

1. LiveData 如何实现生命周期感知?粘性事件的原理是什么?

解答

  • 生命周期感知原理
    LiveData 通过LifecycleOwner获取生命周期状态,仅在STARTEDRESUMED时通知观察者。关键源码:

    java 复制代码
    public void observe(LifecycleOwner owner, Observer<? super T> observer) {  
        owner.getLifecycle().addObserver(wrapper); // 注册生命周期观察者  
    }  
    
    class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {  
        @Override  
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {  
            if (event == Lifecycle.Event.ON_DESTROY) {  
                removeObserver(observer); // 自动移除观察者  
            }  
        }  
    }  
  • 粘性事件原理
    LiveData 缓存最新数据,新注册的观察者会立即收到当前数据。可通过MediatorLiveData或自定义LiveData去除粘性。

真题陷阱

若在onStop后数据更新,Activity 恢复后是否会收到通知?

  • 答案 :会。LiveData 在 Activity 重新回到STARTED状态时重新通知最新数据。
2. LiveData 的setValuepostValue有何区别?为何前者必须在主线程?(字节跳动 / 腾讯 2024 面试真题)

解答

方法 线程安全 调用时机 原理
setValue 非线程安全 必须在主线程 直接调用dispatchingValue触发通知,未通过队列保证顺序。
postValue 线程安全 任意线程 将数据封装为Runnable通过Handler发送到主线程,保证顺序性。
  • 主线程限制原因
    setValue未通过队列调度,若在子线程调用可能导致数据竞争或通知顺序混乱。

真题陷阱

在子线程中调用setValue会发生什么?

  • 答案 :抛出IllegalStateException,提示必须在主线程调用。
3. 对比 LiveData 与 EventBus,各自的优缺点?

解答

特性 LiveData EventBus
生命周期感知 支持(自动移除观察者) 不支持(需手动移除,否则内存泄漏)
线程调度 仅主线程更新(setValue)/ 支持子线程(postValue) 支持自定义线程(@Subscribe (threadMode = ...))
数据类型 强类型(泛型约束) 弱类型(任意对象)
适用场景 页面内数据共享、组件间通信(同作用域) 跨组件复杂通信(如跨 Activity/Fragment)
  • 最佳实践
    页面内数据绑定优先用 LiveData,跨组件通信可用 MutableLiveData + ViewModel,复杂场景(如事件总线)再考虑 EventBus。

三、责任链模式(Chain of Responsibility Pattern)

1. OkHttp 拦截器链如何实现责任链模式?自定义拦截器要注意什么?

解答

  • 核心原理

    OkHttp 的拦截器(Interceptor)按顺序组成链条,每个拦截器可处理请求 / 响应,或调用 chain.proceed(request) 将请求传递给下一个拦截器。

    java 复制代码
    // 自定义日志拦截器  
    public class LoggingInterceptor implements Interceptor {  
        @Override  
        public Response intercept(Chain chain) throws IOException {  
            Request request = chain.request();  
            Log.d("OkHttp", "Request: " + request.url());  
            Response response = chain.proceed(request); // 传递给下一个拦截器  
            Log.d("OkHttp", "Response: " + response.code());  
            return response;  
        }  
    }  
  • 拦截器顺序影响

    • 应用拦截器addInterceptor):先于网络拦截器执行,且不关心重定向(适合添加公共 Header)。
    • 网络拦截器addNetworkInterceptor):在重试 / 重定向之后执行,可获取真实网络请求结果(适合处理 Gzip 响应)。
  • 面试陷阱

    若拦截器未调用 chain.proceed(request),会导致链条中断,后续拦截器无法执行(类似 Android 事件分发的 onTouchEvent 返回 true)。

2. Android 事件分发是否用到责任链模式?举例说明。

解答

  • 事件分发链条

    触摸事件从 Activity.dispatchTouchEvent 开始,依次经过 ViewGroup(如 LinearLayout)的 dispatchTouchEventonInterceptTouchEvent,最后到 ViewdispatchTouchEventonTouchEvent

    • 每个节点(Activity/ViewGroup/View)可决定自己处理事件或传递给子节点(责任链核心:节点处理或转发请求)。
  • 示例代码(ViewGroup 拦截事件):

    java 复制代码
    public class CustomViewGroup extends ViewGroup {  
        @Override  
        public boolean onInterceptTouchEvent(MotionEvent ev) {  
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {  
                return true; // 拦截事件,不再传递给子 View  
            }  
            return super.onInterceptTouchEvent(ev);  
        }  
    }  
3. 责任链模式的优缺点?适用场景有哪些?

解答

  • 优点
    • 解耦请求发送者与处理者,处理逻辑可动态添加 / 删除(如 OkHttp 灵活配置拦截器)。
    • 符合开闭原则,新增处理逻辑无需修改原有代码(只需添加新拦截器)。
  • 缺点
    • 调试困难:事件传递路径不明确,需逐层打印日志定位问题。
    • 可能导致处理链过长,影响性能(需控制拦截器数量)。
  • Android 适用场景
    • 网络请求处理(OkHttp 拦截器)。
    • 事件分发(触摸事件、按键事件处理)。
    • 复杂业务流程(如订单状态校验链:库存检查→价格校验→支付权限校验)。

四、综合面试题:设计模式对比与选型

1. 适配器模式与装饰器模式的区别?(阿里 / 华为 2024 面试真题)

解答

特性 适配器模式 装饰器模式
目的 转换接口,使不兼容的类协同工作 动态扩展对象功能,不改变原有接口
类关系 适配器持有目标类或继承目标类 装饰器持有被装饰对象,实现相同接口
Android 案例 RecyclerView.Adapter 适配数据到视图 TextAppearance为 TextView 添加样式

真题陷阱

如何区分两者?

  • 答案:适配器改变接口,装饰器增强功能。例如,将 List 适配为 ListView 是适配器,给 Button 添加点击动画是装饰器。
2. 观察者模式中,如何避免 "内存泄漏" 和 "重复通知"?

解答

  • 内存泄漏
    • LiveData 依赖 LifecycleOwner,自动移除观察者;传统观察者需在宿主销毁时调用 subject.removeObserver(observer)
    • 真题陷阱:若观察者被 Activity 持有,而被观察者是单例,会导致内存泄漏吗?

答案:会。单例持有观察者的强引用,Activity 无法被回收。

  • 重复通知
    • LiveData 通过 mVersion 版本号控制,相同版本数据不会重复通知(onChanged 仅在数据变化时调用)。
    • 自定义观察者可添加去重逻辑(如比较新旧数据)。
相关推荐
诸葛小猿19 分钟前
Pdf转Word案例(java)
java·pdf·word·格式转换
yuren_xia24 分钟前
Spring MVC中跨域问题处理
java·spring·mvc
计算机毕设定制辅导-无忧学长32 分钟前
ActiveMQ 源码剖析:消息存储与通信协议实现(二)
java·activemq·java-activemq
一个憨憨coder1 小时前
Spring 如何解决循环依赖问题?
java·后端·spring
音视频牛哥1 小时前
把Android设备变成“国标摄像头”:GB28181移动终端实战接入指南
android·音视频·大牛直播sdk·gb28181安卓端·gb28181对接·gb28181平台对接·gb28181监控
钢铁男儿1 小时前
深入解析C#参数传递:值参数 vs 引用参数
java·开发语言·c#
学渣676561 小时前
.idea和__pycache__文件夹分别是什么意思
java·ide·intellij-idea
purrrew1 小时前
【Java ee 初阶】多线程(9)上
java·java-ee
tangweiguo030519871 小时前
Jetpack Compose 响应式布局实战:BoxWithConstraints 完全指南
android
深色風信子2 小时前
Eclipse 插件开发 5 编辑器
java·eclipse·编辑器