Android Jetpack原理相关面试题分享(二)

127 阅读9分钟

一、LiveData和RxJava有什么区别?

LiveData 和 RxJava 是 Android 开发中常用的两种数据观察和响应式编程工具,但它们的设计目标、使用场景和特性有显著区别。以下是它们的对比:

1. 设计目标

  • LiveData:

    • 专为 Android 设计,主要用于在 UI 层观察数据变化。
    • 与 Android 生命周期紧密集成,确保数据更新只在 UI 处于活跃状态时触发,避免内存泄漏。
    • 简单易用,适合处理 UI 相关的数据流。
  • RxJava:

    • 是一个通用的响应式编程库,适用于任何 Java 项目,不限于 Android。
    • 提供了强大的数据流操作符(如 mapfilterflatMap 等),适合处理复杂的异步任务和数据流。
    • 需要手动管理生命周期,否则可能导致内存泄漏。

2. 生命周期感知

  • LiveData:

    • 自动感知生命周期,确保观察者只在 STARTED 或 RESUMED 状态下接收数据更新。
    • 无需手动处理生命周期,避免内存泄漏。
  • RxJava:

    • 不直接支持生命周期感知,需要借助 RxLifecycle 或 AutoDispose 等第三方库来管理生命周期。
    • 如果不处理生命周期,可能导致内存泄漏。

3. 数据流处理能力

  • LiveData:

    • 功能较为简单,主要用于观察单一数据源的变化。
    • 不支持复杂的数据流操作(如线程切换、数据转换等)。
    • 适合简单的 UI 数据绑定。
  • RxJava:

    • 提供了丰富的操作符(如 mapfilterflatMapzip 等),可以轻松处理复杂的数据流。
    • 支持线程切换(如 subscribeOnobserveOn),方便处理异步任务。
    • 适合处理复杂的业务逻辑和数据流。

4. 线程管理

  • LiveData:

    • 默认在主线程中触发数据更新。
    • 如果需要在后台线程更新数据,可以使用 postValue 方法。
  • RxJava:

    • 提供了强大的线程调度功能,可以通过 subscribeOn 和 observeOn 灵活切换线程。
    • 适合处理多线程任务。

5. 使用场景

  • LiveData:

    • 适合简单的 UI 数据绑定,例如从 ViewModel 向 UI 层传递数据。
    • 适合与 ViewModel 结合使用,实现 MVVM 架构。
  • RxJava:

    • 适合处理复杂的异步任务和数据流,例如网络请求、数据库操作、事件总线等。
    • 适合需要大量数据转换和组合的场景。

6. 学习曲线

  • LiveData:

    • 学习曲线较低,API 简单易用。
    • 适合初学者或不需要复杂数据流处理的场景。
  • RxJava:

    • 学习曲线较高,需要掌握响应式编程的概念和大量操作符。
    • 适合有经验的开发者或需要处理复杂逻辑的场景。

7. 性能

  • LiveData:

    • 轻量级,性能开销较小。
    • 适合简单的 UI 数据更新。
  • RxJava:

    • 功能强大,但性能开销相对较大,尤其是在处理复杂数据流时。
    • 需要合理使用操作符和线程调度,避免性能问题。

8. 代码示例

LiveData 示例:

// ViewModel
MutableLiveData<String> liveData = new MutableLiveData<>();

// 更新数据
liveData.setValue("Hello, LiveData!");

// 观察数据
liveData.observe(this, new Observer<String>() {
    @Override
    public void onChanged(String value) {
        // 更新 UI
        textView.setText(value);
    }
});

RxJava 示例:

// 创建 Observable
Observable<String> observable = Observable.just("Hello, RxJava!");

// 订阅并观察数据
observable.subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe(new Observer<String>() {
              @Override
              public void onNext(String value) {
                  // 更新 UI
                  textView.setText(value);
              }

              @Override
              public void onError(Throwable e) {
                  // 处理错误
              }

              @Override
              public void onComplete() {
                  // 完成
              }
          });

总结

特性LiveDataRxJava
生命周期感知需要第三方库支持
数据流处理能力简单强大
线程管理有限灵活
使用场景简单 UI 数据绑定复杂异步任务和数据流
学习曲线
性能轻量级较重
  • 如果只是简单的 UI 数据绑定,推荐使用 LiveData
  • 如果需要处理复杂的异步任务或数据流,推荐使用 RxJava

二、如何避免LiveData粘性事件?

LiveData 的“粘性事件”是指当一个新的观察者开始观察 LiveData 时,它会立即接收到 LiveData 中最后一条数据,即使这条数据是在观察者注册之前更新的。这种行为在某些场景下可能会导致问题(例如重复处理数据或触发不必要的 UI 更新)。以下是避免 LiveData 粘性事件的几种方法:

1. 使用 SingleLiveEvent

SingleLiveEvent 是一个自定义的 LiveData 实现,确保每个事件只被观察一次,避免粘性事件。

实现代码:

public class SingleLiveEvent<T> extends MutableLiveData<T> {
    private final AtomicBoolean mPending = new AtomicBoolean(false);

    @Override
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        super.observe(owner, t -> {
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t);
            }
        });
    }

    @Override
    public void setValue(T value) {
        mPending.set(true);
        super.setValue(value);
    }

    public void call() {
        setValue(null);
    }
}

使用方式:

// ViewModel
private SingleLiveEvent<String> singleLiveEvent = new SingleLiveEvent<>();

public SingleLiveEvent<String> getSingleLiveEvent() {
    return singleLiveEvent;
}

// 触发事件
singleLiveEvent.setValue("Hello, SingleLiveEvent!");

// 观察事件
viewModel.getSingleLiveEvent().observe(this, value -> {
    // 处理事件
});

2. 使用 Event 包装类

通过将数据包装在一个 Event 类中,可以标记数据是否已经被处理,从而避免重复触发。

实现代码:

public class Event<T> {
    private T content;
    private boolean hasBeenHandled = false;

    public Event(T content) {
        this.content = content;
    }

    public T getContentIfNotHandled() {
        if (hasBeenHandled) {
            return null;
        } else {
            hasBeenHandled = true;
            return content;
        }
    }

    public T peekContent() {
        return content;
    }
}

使用方式:

// ViewModel
private MutableLiveData<Event<String>> liveData = new MutableLiveData<>();

public MutableLiveData<Event<String>> getLiveData() {
    return liveData;
}

// 触发事件
liveData.setValue(new Event<>("Hello, Event!"));

// 观察事件
viewModel.getLiveData().observe(this, event -> {
    String content = event.getContentIfNotHandled();
    if (content != null) {
        // 处理事件
    }
});

3. 使用 MediatorLiveData

MediatorLiveData 可以监听其他 LiveData 的变化,并在需要时过滤掉粘性事件。

实现代码:

// ViewModel
private MutableLiveData<String> sourceLiveData = new MutableLiveData<>();
private MediatorLiveData<String> mediatorLiveData = new MediatorLiveData<>();

public MediatorLiveData<String> getMediatorLiveData() {
    return mediatorLiveData;
}

public void triggerEvent(String value) {
    sourceLiveData.setValue(value);
}

{
    mediatorLiveData.addSource(sourceLiveData, value -> {
        mediatorLiveData.setValue(value);
        mediatorLiveData.removeSource(sourceLiveData); // 移除源以避免粘性事件
    });
}

// 观察事件
viewModel.getMediatorLiveData().observe(this, value -> {
    // 处理事件
});

4. 使用 Kotlin 的 SharedFlow(推荐)

如果你使用 Kotlin,可以使用 SharedFlow 替代 LiveData,它天然支持非粘性事件。

实现代码:

// ViewModel
private val _events = MutableSharedFlow<String>()
val events: SharedFlow<String> = _events

fun triggerEvent(value: String) {
    viewModelScope.launch {
        _events.emit(value)
    }
}

// 观察事件
viewModel.events.collect { value ->
    // 处理事件
}

5. 手动清除数据

在观察者处理完数据后,手动清除 LiveData 中的数据,避免重复触发。

// ViewModel
private MutableLiveData<String> liveData = new MutableLiveData<>();

public MutableLiveData<String> getLiveData() {
    return liveData;
}

public void clearLiveData() {
    liveData.setValue(null);
}

// 观察事件
viewModel.getLiveData().observe(this, value -> {
    if (value != null) {
        // 处理事件
        viewModel.clearLiveData(); // 清除数据
    }
});

总结

方法优点缺点
SingleLiveEvent简单易用,适合一次性事件只能处理单个观察者
Event 包装类灵活,适合多种场景需要手动调用 getContentIfNotHandled
MediatorLiveData可以监听多个 LiveData实现稍复杂
SharedFlow(Kotlin)天然支持非粘性事件,功能强大仅适用于 Kotlin
手动清除数据简单直接需要手动管理,容易遗漏

根据具体场景选择合适的方法,推荐优先使用 Event 包装类或 SharedFlow如果使用 Kotlin)。

三、ViewModel如何穿透生命周期?

ViewModel 是 Android 架构组件的一部分,它的设计目的是为了在配置更改(如屏幕旋转)时保留数据,从而避免重新加载数据或重新初始化 UI 状态。然而,ViewModel 的生命周期是与 Activity 或 Fragment 绑定的,当 Activity 或 Fragment 被销毁时(例如用户按返回键退出),ViewModel 也会被清除。如果需要在 Activity 或 Fragment 销毁后仍然保留数据,可以通过以下方法实现“穿透生命周期”的效果。

1. 使用 SavedStateHandle

SavedStateHandle 是 ViewModel 的一个扩展功能,允许在 Activity 或 Fragment 被系统销毁并重建时(例如配置更改或进程被杀死后恢复)保留数据。

实现代码:

class MyViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    // 保存和恢复数据
    var myData: String
        get() = savedStateHandle.get<String>("myData") ?: ""
        set(value) = savedStateHandle.set("myData", value)
}

在 Activity 或 Fragment 中使用:

class MyActivity : AppCompatActivity() {
    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProvider(this).get(MyViewModel::class.java)

        // 使用 viewModel.myData
    }
}

优点:

  • 支持进程被杀死后恢复数据。
  • 简单易用,适合保存少量数据。

缺点:

  • 只能保存简单的数据类型(如 StringInt 等),不适合保存复杂对象。

2. 使用持久化存储

如果需要在 Activity 或 Fragment 完全销毁后仍然保留数据,可以将数据保存到持久化存储中,例如 SharedPreferences、数据库或文件。

使用 SharedPreferences 示例:

class MyViewModel(application: Application) : AndroidViewModel(application) {
    private val sharedPreferences = application.getSharedPreferences("my_prefs", Context.MODE_PRIVATE)

    fun saveData(data: String) {
        sharedPreferences.edit().putString("myData", data).apply()
    }

    fun loadData(): String {
        return sharedPreferences.getString("myData", "") ?: ""
    }
}

在 Activity 或 Fragment 中使用:

class MyActivity : AppCompatActivity() {
    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProvider(this).get(MyViewModel::class.java)

        // 保存数据
        viewModel.saveData("Hello, World!")

        // 加载数据
        val data = viewModel.loadData()
    }
}

优点:

  • 数据持久化,即使应用被杀死也能保留。
  • 适合保存大量数据或复杂对象。

缺点:

  • 需要手动管理数据的存储和加载。
  • 性能开销较大,不适合频繁读写。

3. 使用单例模式

通过单例模式将数据保存在内存中,即使 ViewModel 被销毁,数据仍然保留。

实现代码:

object DataHolder {
    var myData: String? = null
}

class MyViewModel : ViewModel() {
    fun saveData(data: String) {
        DataHolder.myData = data
    }

    fun loadData(): String? {
        return DataHolder.myData
    }
}

在 Activity 或 Fragment 中使用:

class MyActivity : AppCompatActivity() {
    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProvider(this).get(MyViewModel::class.java)

        // 保存数据
        viewModel.saveData("Hello, World!")

        // 加载数据
        val data = viewModel.loadData()
    }
}

优点:

  • 数据保存在内存中,访问速度快。
  • 简单易用。

缺点:

  • 数据不会持久化,应用进程被杀死后数据丢失。
  • 可能导致内存泄漏,需要谨慎使用。

4. 使用 Application 类

通过自定义 Application 类保存全局数据,即使 Activity 或 Fragment 被销毁,数据仍然保留。

实现代码:

class MyApplication : Application() {
    var myData: String? = null
}

class MyViewModel(application: Application) : AndroidViewModel(application) {
    private val app = application as MyApplication

    fun saveData(data: String) {
        app.myData = data
    }

    fun loadData(): String? {
        return app.myData
    }
}

在 Activity 或 Fragment 中使用:

class MyActivity : AppCompatActivity() {
    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProvider(this).get(MyViewModel::class.java)

        // 保存数据
        viewModel.saveData("Hello, World!")

        // 加载数据
        val data = viewModel.loadData()
    }
}

优点:

  • 数据保存在内存中,访问速度快。
  • 适合保存全局数据。

缺点:

  • 数据不会持久化,应用进程被杀死后数据丢失。
  • 可能导致内存泄漏,需要谨慎使用。

5. 使用 Navigation 组件

如果是在 Fragment 之间共享数据,可以使用 Navigation 组件的 SavedStateHandle 或 ViewModel 共享数据。

实现代码:

val navController = findNavController()
val viewModel = navController.getViewModel<MyViewModel>()

优点:

  • 适合 Fragment 之间的数据共享。
  • 与 Navigation 组件无缝集成。

缺点:

  • 仅限于 Fragment 之间使用。

总结

方法优点缺点
SavedStateHandle支持配置更改和进程恢复只能保存简单数据
持久化存储数据持久化,适合大量数据性能开销较大
单例模式简单易用,访问速度快数据不持久化,可能导致内存泄漏
Application 类适合保存全局数据数据不持久化,可能导致内存泄漏
Navigation 组件适合 Fragment 之间共享数据仅限于 Fragment 使用

根据具体需求选择合适的方法:

  • 如果需要支持配置更改和进程恢复,使用 SavedStateHandle
  • 如果需要持久化数据,使用持久化存储。
  • 如果需要全局共享数据,使用单例模式或 Application 类。

更多分享

  1. Android Jetpack相关面试题分享(一)
  2. Android 架构以及优化相关面试题分享
  3. Android Kotlin协程相关面试题分享
  4. Android 互联网大厂,高频重点面试题集分享(一)