android LiveData

LiveData是数据holder类,并支持数据可被监听(观察)。和传统的观察者模式中的被观察者不一样,LiveData是一个生命周期感知组件,因此观察者可以指定某一个LifeCycle给LiveData,并对数据进行监听。

如果观察者的生命周期处于STARTED or RESUMED状态,LiveData认为观察者处于活动状态。

public class LocationLiveData extends LiveData<Location> {
    private LocationManager locationManager;

    private SimpleLocationListener listener = new SimpleLocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            setValue(location);
        }
    };

    public LocationLiveData(Context context) {
        locationManager = (LocationManager) context.getSystemService(
                Context.LOCATION_SERVICE);
    }

    @Override
    protected void onActive() {
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
    }

    @Override
    protected void onInactive() {
        locationManager.removeUpdates(listener);
    }
}

在上面这个例子实现中有三个重要的部分:

onActive()
当LiveData有一个活动的观察者时,就会调用这个方法。这意味着我们需要开始观察设备的位置更新。

onInactive()
当LiveData没有任何活动的观察者时,就会调用这个方法。由于没有观察人员在监听,所以没有理由与LocationManager服务保持联系。这一点很重要,因为保持连接会消耗大量的电池,而没有任何好处。

setValue()
调用该方法将更新LiveData实例的值,并通知活动的观察者关于更改的信息。

接着我们就能像下面这样使用LocationLiveData了。

public class MyFragment extends LifecycleFragment {
    public void onActivityCreated (Bundle savedInstanceState) {
        LiveData<Location> myLocationListener = ...;
        Util.checkUserStatus(result -> {
            if (result) {
                myLocationListener.addObserver(this, location -> {
                    // update UI
                });
            }
        });
    }
}

注意上面的addObserver方法,我们将LifeCycleOwner作为第一个参数传递了进去,这样做意味着这个观察者应该被绑定到这个生命周期

如果LifeCycle不在Started或者RESUMED这两个状态,那么观察者将无法接受到数据更新的回调,即使数据发生了变化。

如果LifeCycle销毁了,即生命周期结束,观察者将被自动从LiveData中移除。

既然LocationLiveData是生命周期感知的,我们可以在多个activity、fragment之间共享它,让它可以被多个Activity或者Fragment公用:

public class LocationLiveData extends LiveData<Location> {
    private static LocationLiveData sInstance;
    private LocationManager locationManager;

    @MainThread
    public static LocationLiveData get(Context context) {
        if (sInstance == null) {
            sInstance = new LocationLiveData(context.getApplicationContext());
        }
        return sInstance;
    }

    private SimpleLocationListener listener = new SimpleLocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            setValue(location);
        }
    };

    private LocationLiveData(Context context) {
        locationManager = (LocationManager) context.getSystemService(
                Context.LOCATION_SERVICE);
    }

    @Override
    protected void onActive() {
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
    }

    @Override
    protected void onInactive() {
        locationManager.removeUpdates(listener);
    }
}

上面fragment可以改为:

public class MyFragment extends LifecycleFragment {
    public void onActivityCreated (Bundle savedInstanceState) {
        Util.checkUserStatus(result -> {
            if (result) {
                MyLocationListener.get(getActivity()).addObserver(this, location -> {
                   // update UI
                });
            }
        });
  }
}

这样即使有多个Activity或者Fragment在观察MyLocationListener实例,LiveData会优雅地管理它们,如果它们中的任何一个都是可见的(也就是活动的),它只会连接到系统服务。

下面是LiveData类提供的一些优点:

没有内存溢出
当观察者被绑定他们对应的LifeCycle以后,当页面销毁时他们会自动被溢出,不会导致内存溢出。

不会因为Activity的不可见导致Crash
当Activity不可见时,即使有数据变化,LiveData也不会通知观察者。因为此时观察者的LifeCyele并不处于Started或者RESUMED状态。

配置的改变
当当前Activity配置改变(如屏幕方向),导致重新从onCreate走一遍,这是观察者们会立刻收到配置变化前的最新数据。

资源共享
我们只需要一个LocationLivaData,连接系统服务一次,就能支持所有的观察者。

不再手动为生命周期处理
通过上面的代码可以知道,我们的Activity或者Fragment只要在需要观察数据的时候观察数据即可,不需要理会生命周期变化了。这一切都交给LiveData来自动管理。

LiveData的转换

有时,您可能希望在将LiveData值发送到观察者之前对LiveData值进行更改,或者您可能需要根据另一个值 返回一个不同的LiveData实例。

生命周期包提供了一个Transformations类,它可以帮助完成上面的这些操作。

Transformations.map()

在LiveData数据传递之前,改变数据后再传递:

LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});
Transformations.switchMap()

与Transformations.map()类似,只不过这里传递个switchMap()的方法必须返回一个LiveData对象。

private LiveData<User> getUser(String id) {
  ...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );

当你考虑在ViewModel中使用LifeCycle对象时,这种转换就是一个可选的解决方案。
假如有一下需求,用户输入一个地址,我们在屏幕上更新这个地址对应的邮编,简单的写法如下:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    public MyViewModel(PostalCodeRepository repository) {
       this.repository = repository;
    }

    private LiveData<String> getPostalCode(String address) {
       // DON'T DO THIS
       return repository.getPostCode(address);
    }
}

这样写问题显然很严重,当每次调用getPostalCode方法后,UI代码中都需要对getPostalCode的返回值做注册观察者操作,并且还要移除上一个观察者,这样显然是低效率的。此外,如果这时UI因为配置的变化(屏幕旋转)重建了,那么它会触发再次调用getPostalCode,而不是使用之前的调用结果。

因此我们可以做如下转换:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    private final MutableLiveData<String> addressInput = new MutableLiveData();
    public final LiveData<String> postalCode =
            Transformations.switchMap(addressInput, (address) -> {
                return repository.getPostCode(address);
             });

  public MyViewModel(PostalCodeRepository repository) {
      this.repository = repository
  }

  private void setInput(String address) {
      addressInput.setValue(address);
  }
}

注意,这里我们将postalCode访问限制符写成public final,因为它将始终不变,UI只要在需要用的时候将观察者注册到postalCode中就行。这是当用户调用setInput后,如果postalCode上有可活动的观察者,那么repository.getPostCode(address)就会被调用,如果此时没有可活动的观察者,则repository.getPostCode(address)不会被调用。

自定义转换
有多个不同的特定的转换在应用程序中可能是有用的,但他们不是默认情况下提供的。实现自己的转换可以使用MediatorLiveData类,这是专门用来发出正确听其他LiveData实例和处理事件。MediatorLiveData需要关心正确传播源LiveData主动/不活跃的状态。你可以转换类的实现细节。

交流QQ群:196040873
点击链接加入群【Android那点事】

<think>嗯,我现在需要解决Android中的LiveData数据倒灌问题。让我先理解一下什么是数据倒灌。根据引用[2],数据倒灌类似于粘性广播,当新的观察者注册时,LiveData会把最后一次的数据传递给它。比如在Fragment或Activity重新创建后,LiveData会立即发送旧数据,这可能导致重复处理或者界面显示错误。 那用户提到的解决方案,引用[1]里有几种措施,比如使用SingleLiveEvent或者反射修改version。还有引用[3]提到了引入依赖包,可能和正确使用LiveData组件有关。另外引用[4]中的例子显示在旋转屏幕后出现数据重复加载的问题,可能是因为LiveData保存了之前的状态,新的观察者接收到了旧数据。 首先,我需要回顾常见的解决方案。比如SingleLiveEvent,这是一个限制只能发送一次事件的类。不过根据引用[1],这种方法可能有局限性,比如多个观察者只能有一个接收到事件。然后还有事件包装类,比如将数据包裹在Event类中,通过标记来判断是否已处理。这样可以避免重复消费。 另外引用[5]提到LiveData的作用域问题,如果LiveData存在于ViewModel中,而ViewModel的生命周期长于Fragment,当Fragment重建时,LiveData仍然持有旧数据,导致倒灌。所以可能需要确保ViewModel的正确作用域,或者在数据更新时清除旧数据。 还有引用[3]中的依赖包是否正确引入,比如是否使用了AndroidX的Lifecycle组件。正确的依赖可能帮助避免一些问题,但主要问题还是在于如何处理数据的发送和接收。 现在思考解决方案: 1. 事件包装类(Event Wrapper):在数据层外面包装一个Event类,里面有一个标记位,记录是否已经被处理。观察者检查这个标记,如果已经处理过则忽略。这种方法需要在每次发送数据时创建新的Event实例,并在观察后标记为已处理。 2. SingleLiveEvent:这是一个自定义的LiveData子类,重写observe方法,确保每次数据变化后只能被观察一次。但要注意多观察者的情况,可能只有最后一个观察者能收到事件。 3. 反射修改Version:通过反射修改LiveData内部的版本号,使得新观察者不会收到旧数据。但这种方法可能不兼容不同系统版本,存在风险。 4. 使用Kotlin的Channel或SharedFlow:如果项目已经迁移到Kotlin协程,可以考虑使用Flow替代LiveData,尤其是SharedFlow,可以配置重放次数为0,避免数据倒灌。 5. UnPeekLiveData:有些开源库提供了封装好的解决方案,比如UnPeekLiveData,它内部处理了数据的分发逻辑,防止重复发送。 然后需要评估每种方法的优缺点。例如,事件包装类比较灵活,但需要额外的代码;SingleLiveEvent适合单次事件,但多观察者可能有问题;反射方法可能不稳定;使用Flow需要项目支持协程,但更现代化。 用户可能希望具体的实现步骤,所以需要给出代码示例。例如,Event类的实现,如何在ViewModel中包装数据,以及在观察时的处理。 另外,引用[1]中提到“简单粗暴解决数据倒灌”,可能指的是直接使用反射或者重置Version的方法。但这种方法需要谨慎,因为可能破坏LiveData的内部状态。 总结,最推荐的可能是事件包装类和UnPeekLiveData。给出这两种方法的详细实现,并说明注意事项。例如,在Event类中,如何处理多个观察者,或者如何确保标记位的正确性。 最后,需要验证这些解决方案是否适用于用户的具体情况,比如是否处理了旋转屏幕后的数据恢复,或者多个Fragment之间通信的问题。同时,提醒用户根据项目需求选择合适的方案,并注意测试不同场景下的表现。</think>### Android LiveData数据倒灌解决方案 数据倒灌指新注册的观察者接收到LiveData最后一次的历史数据,可能导致重复操作或界面异常。以下是主流解决方案: --- #### 一、事件包装法(推荐) 通过包装数据对象,标记事件是否已被消费。 **实现步骤**: 1. 定义事件包装类: ```kotlin class Event<T>(private val content: T) { private var hasBeenHandled = false fun getContentIfNotHandled(): T? { return if (hasBeenHandled) null else { hasBeenHandled = true content } } } ``` 2. ViewModel中使用Event包装数据: ```kotlin class MyViewModel : ViewModel() { private val _data = MutableLiveData<Event<String>>() val data: LiveData<Event<String>> = _data fun loadData() { _data.value = Event("新数据") } } ``` 3. 观察时处理未消费事件: ```kotlin viewModel.data.observe(this) { event -> event.getContentIfNotHandled()?.let { data -> // 处理数据(仅触发一次) } } ``` **优点**:灵活可控,适用于多观察者场景[^1]。 --- #### 二、SingleLiveEvent(限单次事件) 自定义LiveData子类,确保数据仅分发一次。 **实现**: ```kotlin class SingleLiveEvent<T> : MutableLiveData<T>() { private val pending = AtomicBoolean(false) override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { super.observe(owner) { t -> if (pending.compareAndSet(true, false)) { observer.onChanged(t) } } } override fun setValue(value: T) { pending.set(true) super.setValue(value) } } ``` **限制**:多个观察者时仅最后一个生效,适合弹窗、导航等单次事件[^2]。 --- #### 三、UnPeekLiveData(开源方案) 使用第三方库(如[UnPeek-LiveData](https://github.com/KunMinX/UnPeek-LiveData))自动管理数据可见性: ```gradle implementation 'com.kunminx.arch:unpeek-livedata:7.8.0' ``` **使用方式**: ```kotlin val liveData = UnPeekLiveData<String>() liveData.observeInActivity(activity) { data -> // 仅接收新数据,避免倒灌 } ``` **优点**:无需手动标记,简化代码[^4]。 --- #### 四、反射重置Version(谨慎使用) 通过反射修改LiveData内部版本号,跳过历史数据: ```kotlin fun <T> LiveData<T>.clearStickyData() { val mVersionField = LiveData::class.java.getDeclaredField("mVersion") mVersionField.isAccessible = true mVersionField.set(this, mVersionField.get(this) as Int + 1) } ``` **风险**:反射破坏封装性,可能引发兼容性问题[^1]。 --- #### 五、改用Kotlin Flow 若项目支持协程,可用`SharedFlow`替代LiveData: ```kotlin private val _data = MutableSharedFlow<String>(replay = 0) // 不重放历史数据 val data: SharedFlow<String> = _data.asSharedFlow() // 发送数据 viewModelScope.launch { _data.emit("新数据") } // 观察数据 lifecycleScope.launch { viewModel.data.collect { data -> // 处理数据(无倒灌) } } ``` **优势**:天然支持响应式编程,避免粘性问题[^5]。 --- ### 选择建议 | 场景 | 推荐方案 | |------|----------| | 通用事件分发 | 事件包装法 | | 单次触发操作(如弹窗) | SingleLiveEvent | | 现代化架构(已用协程) | Kotlin Flow | | 快速集成 | UnPeekLiveData | **注意事项**: - 避免在`ViewModel`中直接暴露`MutableLiveData`,应通过`LiveData`对外暴露[^3]。 - 跨Fragment通信时,确保`ViewModel`的作用域正确(如`by activityViewModels()`)[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值