简单介绍下LiveData

引子——

在Android开发中,为了避免ANR,通常耗时的操作(网络请求、数据库操作)都会在子线程中执行,执行完成后,以接口回调的方式去传递数据。这种情况下就比较容易出现一种内存泄漏的情况——内部类和外部模块的引用。具体什么情况,可以边看代码边说明:

public class HttpActivity extends AppCompatActivity {

   @Override

   protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       setContentView(R.layout.activity_http);

       HttpUtils.loadInfo(new CallBack() {

           //CallBack内部类创建时,会隐式的持有外部类的引用

           //如果activity在finish()后,这个内部类还没有执行完(或者说没有被释放)

           //会导致activity对象也无法释放,无法被gc回收(还有其他对象,持有activity的引用)

           //造成内存泄漏

           @Override

           public void onSuccess(String info) {

               //切换到主线程,更新UI

               runOnUiThread(() -> {

                   //进行更新UI

               });

           }

       });

   }

}

//demo http工具类

public class HttpUtils {

   public static void loadInfo(CallBack callBack){

       //耗时操作,需要开启子线程去执行

       AsyncTask.execute(() -> {

           //...

           //经历了非常耗时操作

           callBack.onSuccess("获取到数据");

       });

   }

}

上面代码进行了几个个简单操作:

1.开启一个子线程执行耗时操作,获取数据

2.获取数据通过CallBack进行接口回调,把数据传回给activity

3.activity获取到数据后,切换到主线程更新UI

以上3个操作,应该算是Android开发中最常见的情况之一了,异步获取数据,然后回调,切换到主线程中更新。然而就这么简单的事,很容易造成内存泄漏(代码注释中已大概说明)。

虽然看起来很容易造成内存泄漏,但实际影响可能并没有想象中的大:一个原因是当子线程执行完之后,会释放callBack对象,也就是释放了activity的引用,使得activity也可以被gc回收了(当然如果因为某些原因,线程没有执行完,会导致activity一直得不到释放);另一个原因是activity对象,不会在短时间内,被大量创建

但是,问题存在,而不去解决,最后必然会爆发的,当然,更关键的是,如果不去解决,怎么引出本文要介绍的LiveData呢?

先来看看使用LIveData是什么情况,再看看LiveData是如何避免内存泄漏的:

public class HttpActivity extends AppCompatActivity {

   @Override

   protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       setContentView(R.layout.activity_http);

       HttpUtils.loadInfo().observe(this, new Observer<String>() {

           //画重点,这里也是内部类,同样会隐式持有activity的引用

           //那么会造成内存泄漏么?

           @Override

           public void onChanged(@Nullable String s) {

               //进行更新UI

               //不需要切换到主线程

           }

       });

   }

}

public class HttpUtils {

   public static LiveData<String> loadInfo(){

       MutableLiveData<String> data = new MutableLiveData<>();

       //耗时操作,需要开启子线程去执行

       AsyncTask.execute(() -> {

           //...

           //经历了非常耗时操作

           data.postValue("获取到数据");

       });

       //这里虽然返回了LiveData

       //但是并不一定能获取到数据

       return data;

   }

}

先来看下具体做了些什么:

1.activity调用loadInfo()方法返回了一个LiveData<String>

2.activity通过LiveData.observe()方法,提供了一个接口回调对象Observer,观察是否有获取到数据

3.在AsyncTask.execute()中,执行耗时操作,获取到数据后,调用了LiveData.postValue()方法

4.activity在onChanged()方法中,更新UI

画两个重点:

1.在获取到LiveData对象时,并不一定立刻获取到数据,当LiveData.postValue()时,才会获取到数据,回调在Observer.onChanged()方法中,同时这里不需要切换到主线程。

2.new Observer()时,这也是个内部类,同样持有activity引用,但是没有内存泄漏隐患、没有内存泄漏隐患、没有内存泄漏隐患

* If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will

* automatically be removed.

源码中有这么一段说明:当Lifecycle.State变成DESTROYED时,observer将会被自动移除。

顾名思义:就是当activity的生命周期变成destroy时,LiveData会释放observer对象,是的observer对象可以被回收,同时activity对象也可以被回收了。

很方便吧,不用顾虑内存泄漏问题,当然,这仅仅是其中一点,还有其他方便的地方

LiveData的优点:

1.避免内存泄漏

原因和栗子,都在上面了

2.自动切换到主线程

作为Google的亲儿子,自然会解决一个主要问题:更新UI时必须在主线程中。所以onChanged()方法是被切换到主线程中执行的。

liveData.setValue("可以在主线程中调用");

liveData.postValue("可以在子线程中调用,然后切换到主线程中通知");

LiveData提供了两个通知方法,setValue和postValue:

①.setValue方法调用后,是立即通知观察者,数据发生了改变。必须在主线程调用的,如果在子线程中调用,会报出IllegalStateException: Cannot invoke setValue on a background thread异常

②.postValue。这个就是可以在子线程调用,调用之后,会切换到主线程,然后再通知观察者数据发生改变

3.根据生命周期变化,动态获取数据

* The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}

* or {@link Lifecycle.State#RESUMED} state (active).

源码说明:只有在STARTED或者RESUMED状态,observer才会接收到数据发生改变的通知。

试想这么一个场景:activity处于onPause或者onStop状态,此时有十几个数据变化的通知过来,我们是不是得更新十几次UI,但是此时频繁更新UI对我们来说没有意义,使用者看不见此时的activity。

所以,只有在STARTED或者RESUMED状态,observer才会接收到数据改变通知很有必要,可以节省很多资源(不需要去处理那么多次不需要的UI更新)

在onPause或者onStop时,LiveData即使postValue()十几次,observer都不会接收到通知,只有当activity回到STARTED或者RESUMED状态,才会获取到最后一次数据变更的通知

这里还有一个优点,在调用LiveData.postValue()方法之后,才调用LiveData.observer(),可以立即获取到数据,不需要重新去执行获取数据的操作。

4.Transformations(转换)。Transformations提供了两种转换LiveData的方式

①.Transformations.map(source,func)。有时获取到的数据类型,并不是观察者所直接需要的,需要进行一个转换。

public LiveData<Order> getOrder() {

       //耗时操作,去加载获取的信息

       LiveData<Goods> goodsData = getGoods();

       //把Goods 转换成Order

       return Transformations.map(goodsData, new Function<Goods, Order>() {

           @Override

           public Order apply(Goods input) {

               Order order = new Order();

               order.goodsName = input.name;

               order.profit = input.salePrice - input.buyPrice;

               return order;

           }

       });

   }

上面举例情况是:

(1).观察者需要Order数据

(2).并没有直接获取Order的方法,但是由Goods数据运算得到的Order

(3).有加载LiveData<Goods>的方法

所以要获取LiveData<Order>,就是通过Transformations.map()方法,把Goods转换成Order返回给观察者。这里稍微可以注意的是LiveData<Goods> goodsData = getGoods();只是获取到LiveData,可能数据还并没有立即获得(别忘了LiveData的机制)。

解释一下Transformations方法:

Transformations.map(source,func)是一个转换方法,source是源数据(被转换的数据),func是一个回调接口,source数据发生变化时,func把source的数据转换成Transformations.map()的返回类型

②Transformations.switchMap()。想下这么一个情景:第一个请求返回的数据,是第二个请求的参数,而第二个请求返回的数据,才是真正所需要的。(有没有人会想,在activity监听到数据后,再去启动第二个请求?)

public LiveData<User> queryUser(long userId){

   MutableLiveData userData =  new MutableLiveData<>();

   //开启子线程执行任务

   AsyncTask.execute(() -> {

           //...中间根据userId去做查询User

       });

   return userData;

}

public LiveData<User> getUser(){

   //第一个请求,去获取用户id

   LiveData<Long> idData = getUserId();

   return Transformations.switchMap(idData, new Function<Long, LiveData<User>>() {

       @Override

       public LiveData<User> apply(Long input) {

           //当获取到用户id后,根据用户id进行第二个请求

           return queryUser(input);

       }

   });

}

LiveData的优点和基本用法,已经大致说完了,想在文章的最后,简单聊聊LiveData的设计。

LiveData的设计中心思想很简单,主要就是使用观察者模式。

observe观察LiveData的数据变化,LiveData数据发生变化,就通知给observe;

LiveData去观察LifecycleOwner的生命周期变化,当生命周期到DESTROYED时,移除observe;

//LiveData observe 方法

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {

   if (owner.getLifecycle().getCurrentState() == DESTROYED) {

       // ignore

       //生命周期已经结束,不需要添加进去

       return;

   }

   LifecycleBoundObserver  wrapper = new LifecycleBoundObserver(owner, observer);

   //mObservers 是一个map集合,这里用来存放observer这个观察者

   //当LiveData数据有变化时,就会遍历mObservers,通知observer

   ObserverWrapper existing = mObservers .putIfAbsent(observer, wrapper);

   //一个observer只能被加入到一个LifecycleOwner中

   if (existing != null && !existing.isAttachedTo(owner)) {

       throw new IllegalArgumentException("Cannot add the same observer"

               + " with different lifecycles");

   }

   if (existing != null) {

       return;

   }

   //加入LifecycleOwner的观察队列

   //当生命周期到DESTROYED时,LifecycleBoundObserver会把自身从mObservers集合中移除

   owner.getLifecycle().addObserver(wrapper);

}

LiveData的介绍到这里结束了,记住LiveData的两个特点,会在开发中最常用到的:1.自动切换到主线程

2.跟生命周期绑定,自动解绑,不需要开发者做解绑处理(也就是不用担心内存泄漏)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容