IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Jetpack架构组件 (四)-- LiveData使用与原理分析 -> 正文阅读

[移动开发]Jetpack架构组件 (四)-- LiveData使用与原理分析

前言

在 LiveData 出现之前,一般状态分发我们使用EventBus或者RxJava,这些都很容易出现内存泄漏问题,而且需要我们手动管理生命周期。而 LiveData 则规避了这些问题,LiveData 是一个持有 Activity、Fragment 生命周期的数据容器。当数据源发生变化的时候,通知它的观察者更新 UI 界面。同时它可以只通知处于 Active 状态的观察者更新界面。所以不用担心内存泄漏问题。一般来说,LiveData很少单独使用,它更多的和Android Jetpack的其他组件搭配使用,比如和ViewModel。这篇文章就来介绍LiveData的使用。

1、 什么是LiveData

LiveData 是一个可观察的数据持有者,和常规的observable不同,LiveData 是可以感知生命周期的,也就是说它能够在 Activity、Fragment、Service 中正确的处理生命周期。

LiveData 的数据源一般是 ViewModel,也可以是其它可以更新 LiveData 的组件。当数据更新后,LiveData 就会通知它的所有观察者,比如 Activity。与 RxJava 的方法不同的是,LiveData 可以不通知所有观察者,它可以只通知处于 Active 状态的观察者。这对于Activity 和 Service 特别有用,因为它们可以安全地观察 LiveData 对象而不用担心内存泄漏的问题。还有一点需要注意的是一旦观察者重新恢复到活跃状态,它将会重新收到 LiveData 的最新数据。

2、 LiveData的基本使用

LiveData 是一个抽象类,它的最简单的实现类为 MutableLiveData,使用方法如下所示:

public class MainActivity extends AppCompatActivity {
 ?  MutableLiveData<String> mutableLiveData  = new MutableLiveData<>();
 ?  @Override
 ?  protected void onCreate(Bundle savedInstanceState) {
 ? ? ?  super.onCreate(savedInstanceState);
 ? ? ?  setContentView(R.layout.activity_main);
?
 ? ? ?  mutableLiveData.observe(this, new Observer<String>() {
 ? ? ? ? ?  @Override
 ? ? ? ? ?  public void onChanged(@Nullable final String s) {
 ? ? ? ? ? ? ?  Log.d("MainActivity", "onChanged:"+s);
 ? ? ? ? ?  }
 ? ? ?  });
 ? ? ?  mutableLiveData.postValue("LiveData 基本使用");
?
 ?  }
}

MutableLiveData 的 observe 方法有两个参数分别是 LifecycleOwner 和 Observer<T> ,第一个参数就是MainActivity 本身,第二个参数新建了一个Observer<String>,在onChanged方法中得到回调。postValue 方法会在主线程中更新数据,运行结果如下所示:

除了 MutableLiveData 的 postValue 方法,还可以使用 setValue 方法,它们之前的区别是,setValue 方法必须在主线程使用,如果是在子线程线程中更新 LiveData,则可以使用 postValue 方法。

3、 更改LiveData中的数据

如果我们想要在 LiveData 对象分发给观察者之前对其中存储的值进行更改,可以使用 Transformations.map() 和Transformations.switchMap(),下面通过简单的例子来讲解它们。

2.1 Transformations.map()

如果想要在 LiveData 对象分发给观察者之前对其中存储的值进行更改,可以使用Transformations.map()。

public class MainActivity extends AppCompatActivity {
 ?  MutableLiveData<String> mutableLiveData  = new MutableLiveData<>();
 ?  @Override
 ?  protected void onCreate(Bundle savedInstanceState) {
 ? ? ?  super.onCreate(savedInstanceState);
 ? ? ?  setContentView(R.layout.activity_main);
?
 ? ? ?  mutableLiveData.observe(this, new Observer<String>() {
 ? ? ? ? ?  @Override
 ? ? ? ? ?  public void onChanged(@Nullable final String s) {
 ? ? ? ? ? ? ?  Log.d("MainActivity", "onChanged:"+s);
 ? ? ? ? ?  }
 ? ? ?  });
 ? ? ?  LiveData transformedLiveData = Transformations.map(mutableLiveData, new Function<String, Object>() {
 ? ? ? ? ?  @Override
 ? ? ? ? ?  public Object apply(String name) {
 ? ? ? ? ? ? ?  return name + " + Transformations.map 的使用";
 ? ? ? ? ?  }
 ? ? ?  });
 ? ? ?  transformedLiveData.observe(this, new Observer() {
 ? ? ? ? ?  @Override
 ? ? ? ? ?  public void onChanged(@Nullable Object o) {
 ? ? ? ? ? ? ?  Log.d("MainActivity", "onChanged2:"+o.toString());
 ? ? ? ? ?  }
 ? ? ?  });
 ? ? ?  mutableLiveData.postValue("LiveData 基本使用");
 ?  }
}

通过Transformations.map(),在mutableLiveData的基础上又加上了字符串" + Transformations.map 的使用"。

运行结果如下所示:

2.2 Transformations.switchMap()

public class MainActivity extends AppCompatActivity {
 ?  private MutableLiveData<String> mutableLiveData1;
 ?  private MutableLiveData<String> mutableLiveData2;
 ?  private MutableLiveData<Boolean> liveDataSwitch;
 ?  private LiveData transformedLiveData;
 ?  @Override
 ?  protected void onCreate(Bundle savedInstanceState) {
 ? ? ?  super.onCreate(savedInstanceState);
 ? ? ?  setContentView(R.layout.activity_main);
 ? ? ?  mutableLiveData1 = new MutableLiveData<>();
 ? ? ?  mutableLiveData2 = new MutableLiveData<>();
 ? ? ?  liveDataSwitch = new MutableLiveData<>();
?
 ? ? ? ? transformedLiveData= Transformations.switchMap(liveDataSwitch, new Function<Boolean, LiveData<String>>() {
 ? ? ? ? ?  @Override
 ? ? ? ? ?  public LiveData<String> apply(Boolean input) {
 ? ? ? ? ? ? ?  if (input) {
 ? ? ? ? ? ? ? ? ?  return mutableLiveData1;
 ? ? ? ? ? ? ?  } else {
 ? ? ? ? ? ? ? ? ?  return mutableLiveData2;
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ?  });
?
 ? ? ?  transformedLiveData.observe(this, new Observer<String>() {
 ? ? ? ? ?  @Override
 ? ? ? ? ?  public void onChanged(@Nullable final String s) {
 ? ? ? ? ? ? ?  Log.d("MainActivity", "onChanged:" + s);
 ? ? ? ? ?  }
 ? ? ?  });
 ? ? ?  liveDataSwitch.postValue(false);
 ? ? ?  mutableLiveData1.postValue("Transformations.switchMap 用法1");
 ? ? ?  mutableLiveData2.postValue("Transformations.switchMap 用法2");
 ?  }
}

MutableLiveData<Boolean>()用来控制切换,当 liveDataSwitch 的值为true时返回mutableLiveData1,否则返回 mutableLiveData2。

2.3 合并多个LiveData数据源

MediatorLiveData 继承自 mutableLiveData,它可以将多个 LiveData 数据源集合起来,可以达到一个组件监听多个 LiveData 数据变化的目的,用法如下所示:

public class MainActivity extends AppCompatActivity {
 ?  private MutableLiveData<String> mutableLiveData1;
 ?  private MutableLiveData<String> mutableLiveData2;
 ?  private MediatorLiveData mediatorLiveData;
?
 ?  @Override
 ?  protected void onCreate(Bundle savedInstanceState) {
 ? ? ?  super.onCreate(savedInstanceState);
 ? ? ?  setContentView(R.layout.activity_main);
 ? ? ?  mutableLiveData1 = new MutableLiveData<>();
 ? ? ?  mutableLiveData2 = new MutableLiveData<>();
 ? ? ?  mediatorLiveData = new MediatorLiveData<String>();
?
 ? ? ?  mediatorLiveData.addSource(mutableLiveData1, new Observer() {
 ? ? ? ? ?  @Override
 ? ? ? ? ?  public void onChanged(@Nullable Object o) {
 ? ? ? ? ? ? ?  Log.d("MainActivity", "onChanged1:" + o.toString());
 ? ? ? ? ?  }
 ? ? ?  });
?
 ? ? ?  mediatorLiveData.addSource(mutableLiveData2, new Observer() {
 ? ? ? ? ?  @Override
 ? ? ? ? ?  public void onChanged(@Nullable Object o) {
 ? ? ? ? ? ? ?  Log.d("MainActivity", "onChanged2:" + o.toString());
 ? ? ? ? ?  }
 ? ? ?  });
 ? ? ?  mediatorLiveData.observe(this, new Observer() {
 ? ? ? ? ?  @Override
 ? ? ? ? ?  public void onChanged(@Nullable Object o) {
 ? ? ? ? ? ? ?  Log.d("MainActivity", "onChanged:" + o.toString());
 ? ? ? ? ?  }
 ? ? ?  });
 ? ? ?  mutableLiveData1.postValue("mutableLiveData 1");
 ? ? ?  mutableLiveData2.postValue("mutableLiveData 2");
 ? ? ?  mediatorLiveData.postValue("mediatorLiveData 使用");
 ?  }
}

通过 MediatorLiveData 的 addSource 将两个 MutableLiveData 合并到一起,这样当任何一个 MutableLiveData数据发生变化时,MediatorLiveData 都可以感知到,运行结果如下所示:

4、LiveData源码解析

由于 LiveData 是一个 abstract class,我们不能直接生成他的实例。官方有提供他的实现类MutableLiveData,代码如下所示:

public class MutableLiveData<T> extends LiveData<T> {
 ?  ...
 ?  // 可以在子线程中调用
 ?  @Override
 ?  public void postValue(T value) {
 ? ? ?  super.postValue(value);
 ?  }
?
    // 必须在主线程中调用
 ?  @Override
 ?  public void setValue(T value) {
 ? ? ?  super.setValue(value);
 ?  }
}

创建了 LiveData 后,需要通过 observe 方法或者 observeForever 方法设置一个观察者,这个观察者接口就是Observer,代码如下:

public interface Observer<T> {
 ?  // 这个回调发生在主线程
 ?  void onChanged(T t);
}

LiveData 的添加观察者的代码如下:

public abstract class LiveData<T> {
    ...
    
    //  只有onStart之后,onStop之前,对数据的修改才会触发 observer.onChanged()
 ?  public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
 ? ? }
 ? ?
 ? ? // 无论何时,只要数据发生改变,就会触发 observer.onChanged()
 ?  public void observeForever(@NonNull Observer<? super T> observer) {
 ? ?
 ?  }
 
    ...
}

observeForever 的实现跟 observe 是类似的,这里我们重点看一下 observe()的实现过程,代码如下:

// 只能在主线程调用
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
 ?  assertMainThread("observe");
 ?  //如当前UI的状态的是DESTROYED,就不绑定这个回调了
 ?  if (owner.getLifecycle().getCurrentState() == DESTROYED) {
 ? ? ?  // ignore
 ? ? ?  return;
 ?  }
 ?  //创建生命周期感知的观察者包装类(把注册进来的observer包装成一个具有生命周边边界的观察者)
 ?  LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
 ?  //接着会判断该观察是否已经注册过了,如果是则抛异常, 对应观察者只能与一个owner绑定,不允许重复注册
 ?  ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
 ?  if (existing != null && !existing.isAttachedTo(owner)) {
 ? ? ?  throw new IllegalArgumentException("Cannot add the same observer"
 ? ? ? ? ? ? ?  + " with different lifecycles");
 ?  }
 ?  if (existing != null) {
 ? ? ?  return;
 ?  }
 ?  //把wrapper与Activity/Fragment的生命周期,建立关系,
 ?  //当UI的生命周期发生变化的时候,就会去回调wrapper中的onStateChanged方法
 ?  owner.getLifecycle().addObserver(wrapper);
}

4.2 LifecycleBoundObserver

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
 ?  @NonNull
 ?  final LifecycleOwner mOwner;
?
 ?  LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
 ? ? ?  super(observer);
 ? ? ?  mOwner = owner;
 ?  }
?
 ?  @Override
 ?  boolean shouldBeActive() {
 ?      /*
 ? ? ?  观察者是否活跃就等于mOwner的状态是否大于等于STARTED;
 ? ? ?  如果页面当前不可见,你发送了一条消息,此时是不会被分发的,
 ? ? ?  可以避免后台任务抢占资源,当页面恢复可见才会分发。
 ? ? ?  */
 ? ? ?  return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
 ?  }
?
    // Activity/Fragment的生命周期变化会回调此方法。
 ?  @Override
 ?  public void onStateChanged(@NonNull LifecycleOwner source,
 ? ? ? ? ?  @NonNull Lifecycle.Event event) {
 ? ? ?  Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
 ? ? ?  // 如果当接收到DESTROYED的事件会自动解除跟owner的绑定,避免内存泄露
 ? ? ?  if (currentState == DESTROYED) {
 ? ? ? ? ?  removeObserver(mObserver);
 ? ? ? ? ?  return;
 ? ? ?  }
 ? ? ?  Lifecycle.State prevState = null;
 ? ? ?  while (prevState != currentState) {
 ? ? ? ? ?  prevState = currentState;
 ? ? ? ? ?  //说明宿主的状态发生了变化,此时会判断宿主是否处于活跃状态
 ? ? ? ? ?  activeStateChanged(shouldBeActive());
 ? ? ? ? ?  currentState = mOwner.getLifecycle().getCurrentState();
 ? ? ?  }
 ?  }
?
 ?  @Override
 ?  boolean isAttachedTo(LifecycleOwner owner) {
 ? ? ?  return mOwner == owner;
 ?  }
?
 ?  @Override
 ?  void detachObserver() {
 ? ? ?  mOwner.getLifecycle().removeObserver(this);
 ?  }
}

4.3 ObserverWrapper

状态变更后,如果观察者处于活跃状态会触发数据的分发流程

private abstract class ObserverWrapper {
 ?  final Observer<? super T> mObserver;
 ?  boolean mActive;
 ?  //ObserverWrapper在每次注册的时候都会重新new,所以mLastVersion每次都是-1开始。
 ?  int mLastVersion = START_VERSION;
?
 ?  ObserverWrapper(Observer<? super T> observer) {
 ? ? ?  mObserver = observer;
 ?  }
?
 ?  abstract boolean shouldBeActive();
?
 ?  boolean isAttachedTo(LifecycleOwner owner) {
 ? ? ?  return false;
 ?  }
?
 ?  void detachObserver() {
 ?  }
?
 ?  void activeStateChanged(boolean newActive) {
 ?      //当前的生命周期和上一次的生命周期状态,是否发生变化,没有发生变化,就直接返回。
 ? ? ?  if (newActive == mActive) {
 ? ? ? ? ?  return;
 ? ? ?  }
 ? ? ?  // immediately set active state, so we'd never dispatch anything to inactive
 ? ? ?  // owner
 ? ? ?  mActive = newActive;
 ? ? ?  // 更新活跃值 活跃1 非活跃-1
 ? ? ?  changeActiveCounter(mActive ? 1 : -1);
 ? ? ?  if (mActive) {
 ? ? ?      // 如果是活跃状态,分发值
 ? ? ? ? ?  dispatchingValue(this);
 ? ? ?  }
 ?  }
}

changeActiveCounter 方法代码如下:

@MainThread
void changeActiveCounter(int change) {
    // 上一次的活跃数
 ?  int previousActiveCount = mActiveCount;
 ?  // 当前活跃数
 ?  mActiveCount += change;
 ?  if (mChangingActiveState) {
 ? ? ?  return;
 ?  }
 ?  mChangingActiveState = true;
 ?  try {
 ? ? ?  while (previousActiveCount != mActiveCount) {
 ? ? ? ? ? // 从不活跃变为活跃状态,需要回调 onActive方法
 ? ? ? ? ?  boolean needToCallActive = previousActiveCount == 0 && mActiveCount > 0;
 ? ? ? ? ?  // 从活跃状态变为不活跃状体,需要回调onInactive方法
 ? ? ? ? ?  boolean needToCallInactive = previousActiveCount > 0 && mActiveCount == 0;
 ? ? ? ? ?  previousActiveCount = mActiveCount;
 ? ? ? ? ?  if (needToCallActive) {
 ? ? ? ? ? ? ?  onActive();
 ? ? ? ? ?  } else if (needToCallInactive) {
 ? ? ? ? ? ? ?  onInactive();
 ? ? ? ? ?  }
 ? ? ?  }
 ?  } finally {
 ? ? ?  mChangingActiveState = false;
 ?  }
}

dispatchingValue(this)数据分发流程控制:(内部执行considerNotify(initiator)方法),这个方法我们后面分析。

4.4 setValue 发送数据源码分析

postValue 最终也是调用 setValue 方法,这里就不做分析了,我们主要分析 setValue 方法,代码如下:

@MainThread
 ?  protected void setValue(T value) {
 ? ? ?  assertMainThread("setValue");
 ? ? ?  // 这个mVersion是属于LiveData的,然后只在setValue
 ? ? ?  // (postValue最终也会调用setValue)的时候会自增1
 ? ? ?  mVersion++;
 ? ? ?  // 保存这次变化的数据
 ? ? ?  mData = value;
 ? ? ?  // 分发数据
 ? ? ?  dispatchingValue(null);
 ?  }

接下来我们具体分析 dispatchingValue 方法,代码如下:

@SuppressWarnings("WeakerAccess") /* synthetic access */
void dispatchingValue(@Nullable ObserverWrapper initiator) {
 ?  if (mDispatchingValue) {
 ? ? ?  mDispatchInvalidated = true;
 ? ? ?  return;
 ?  }
 ?  //进入while循环前,设置为true,如果此时另外一个数据发生变化,到了此函数中就直接在上面返回了
 ?  mDispatchingValue = true;
 ?  do {
 ?      //开始for循环前,设置为false,for循环完,也会退出while循环
 ? ? ?  mDispatchInvalidated = false;
 ? ? ?  if (initiator != null) {
 ? ? ?  // 如果传递的观察者不为空,则把数据分发给他自己。
 ? ? ?  // 这个流程是Activity/Fragment的生命周期发生变化的时候会被触发
 ? ? ? ? ?  considerNotify(initiator);
 ? ? ? ? ?  initiator = null;
 ? ? ?  } else {
 ? ? ?      // 遍历集合中所有已注册的的观察者,逐个调用considerNotify,分发数据
 ? ? ? ? ?  for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
 ? ? ? ? ? ? ? ? ?  mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
 ? ? ? ? ? ? ?  considerNotify(iterator.next().getValue());
 ? ? ? ? ? ? ?  //这里mDispatchInvalidated 为true,表示在while循环未结束的时候,
 ? ? ? ? ? ? ?  // 有其他数据发生变化,并调用了该函数
 ? ? ? ? ? ? ?  //在上面的if判断中设置了 mDispatchInvalidated = true,
 ? ? ? ? ? ? ?  // 结束本次for循环,没有退出while循环,开始下一次for循环
 ? ? ? ? ? ? ?  if (mDispatchInvalidated) {
 ? ? ? ? ? ? ? ? ?  break;
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ?  }
 ?  } while (mDispatchInvalidated);
 ?  //退出while 后,设置为false,正常处理数据变化
 ?  mDispatchingValue = false;
}

这里有两个变量

  • mDispatchingValue 这个变量用来控制,是否进入while 循环,以及while 循环 是否已经结束

  • mDispatchInvalidated 这个变量用来控制for 循环是否要重新开始

看下considerNotify 的函数,调用了之前 LiveData 设置的 observer 的 onChanged 函数,代码如下:

private void considerNotify(ObserverWrapper observer) {
    // 观察者当前状态不活跃就不分发
 ?  if (!observer.mActive) {
 ? ? ?  return;
 ?  }
 ?  // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
 ?  //
 ?  // we still first check observer.active to keep it as the entrance for events. So even if
 ?  // the observer moved to an active state, if we've not received that event, we better not
 ?  // notify for a more predictable notification order.
 ?  // 如果当前的生命周期是非活跃,就不回调onChanged函数,
 ?  // 在LifecycleBoundObserver 中记录状态,当生命周期变为活跃,就回去更新数据
 ?  if (!observer.shouldBeActive()) {
 ? ? ?  observer.activeStateChanged(false);
 ? ? ?  return;
 ?  }
 ?  // 此处判断观察者接收消息的次数是否大于等于 发送消息的次数(observer被创建之初verison=-1 )
 ?  if (observer.mLastVersion >= mVersion) {
 ? ? ?  return;
 ?  }
 ?  observer.mLastVersion = mVersion;
 ?  // 这里会执行传入的observer的onChanged方法
 ?  observer.mObserver.onChanged((T) mData);
}

综上分析:LiveData的粘性事件

    • 通过同一个LiveData 在更改数据的时候调用 setValue(postValue 最终也会调用 setValue) 的时候内部维护的 mVersion 会自增1,mVersion 初始值是-1

    • 在监听数据变化的时候,即调用 observer 监听的时候,每次内部都会在通过包装类中new ObserverWrapper,内部维护了一个mLastVersion变量,mLastVersion也是初始值为-1,所以注意了,每执行一次 observer 方法内部维护 mLastVersion 都会新创建而变为 -1 ,如果 mLastVersion 小于 mVersion 的时候就表示有新的变化没有回调 observer 的 onChanged 方法。

    • 结论所以只要之前有发射过一次数据,那么后面注册的观察者都会接收到最精一次发射过的数据

这种情况被称为LiveData的粘性事件

扫描下方二维码关注公众号,获取更多技术干货。

?

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-18 16:06:39  更:2021-12-18 16:09:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:08:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码