延迟加载策略,避免重复加载的几种方式(性能优化)
常见场景需求:
1: 搜索功能开发,从网络和本地数据库加载数据搜索。依次输入a b c;
2: 在点餐或者商品页面, 增加商品的件数,在结算按钮旁边显示总件数和总金额;
常见做法:
1: 我们在进行此类功能的开发过程之中,如果每输入一个数字或者点击依次按钮就要做一次网络请求或者是做一次计算。
2: 但是在这类操作未到结束的时候,中间的请求和计算都是无意义的,如果在网络状况不好,或者是手机硬件计算能力不强的时候,手机的负荷就会增加,同时用户体验性变得很差。
思考: 我们应该如何解决和优化这类问题呢?
设置一个延迟时间,过滤掉变化较快的操作,只考虑最后一次的操作和显示
如何理解这个方案:
比如说我在进行搜索操作的时候,依次输入a b c d e f等字符, 在a的时候我本就应该发起搜索请求的, 但是我在输入到b的时候,搜索的字符就是ab, 所以之前搜索a的请求就是无效的, 同理可得 adbcdef才是搜索的关键字, 在每一次输入字符之后, 都设置一个延迟搜索时间为1s, 如果在1s内有重复的搜索请求出现, 就将上一次的请求给remove掉,重新设置最新的延迟请求, 如果在1s内没有被remove掉,那么就执行这个请求。
实现方式:
1: Handler + message方式
2: Executor+Future
3: RxJava
Handler + Message实现延迟处理
原理: 使用Handler 配合Handler的removeCallBacks方法和removeMessages方法来移除call back和message, 延迟使用postDelayed()实现。
在editText变化的时候就调用delaySearch(s),延迟1000ms, 发送消息延迟1000ms处理, 如果说在1000ms以内,delaySearch被重复调用起来, 那么就移除掉之前的请求操作,然后继续执行postDelay,并且传入最新的输入值s。
Executor+Future方式
1: 描述:
有一个任务提交给future, future来完成这个任务,期间可以去做任何事情,一段时间之后,我们就可以从future那里获取到结果。
2: 实现:
1: 创建一个SingleThreadExecutor;
2: 调用scheduleExecutorService.schedule()方法延迟执行某一个任务task
3: 如果当前存在这个线程池,则调用取消,进而取消这个任务,然后重新执行延迟任务。
此方法原理很类似于Handler-message的延迟处理,但是这是属于java executor-future的threadPool的框架。
3: Executor框架:
1: Executor框架引入的并发库是一些与executor相关的一些功能类,其中包括线程池,executor, executors, ExecutorService, CompletionService,Future,Callable 等
2:并发编程的一种编程方式是把任务拆分为一些列的小任务,即Runnable,然后在提交给一个Executor执行,Executor.execute(Runnalbe) 。Executor在执行时使用内部的线程池完成操作。
3:使用Executors创建线程池的几种方式:
Executors类,提供了一些工厂方法来创建线程池,同时返回的线程池都实现了ExecutorService接口。
1:ExecutorService newFixedThreadPool(int nThreads): 创建一个固定数目的线程的线程池
2:ExecutorService newCachedThreadPool():创建一个可缓存的线程池,调用execute将重用之前的线程,如果无可用线程,则创建一个新的线程添加到线程。终止并从缓存中移除那些已经60s未被使用的线程
3:ExecutorService newSingleThreadExecutor():创建一个单线程化的executor
4:ScheduledExecutorService newScheduledThreadPool(int corePoolSize): 创建一个支持定时及周期性的任务执行的线程池,多数情况可用来使用Timer类
4: Executor的生命周期: 运行、关闭、终止
5: Callable 和 Future:
Future<V>代表一个异步执行的操作,通过get()方法可以获得操作的结果,如果异步操作还没有完成,则,get()会使当前线程阻塞。
FutureTask<V>实现了Future<V>和Runable<V>。
Callable代表一个有返回值得操作。
ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。
RxJava实现
一: 题外话:在讲到rxJava之前,首先需要了解一下其概念和简单的API,设计思想和扩展
观察者模式(Observer pattern) VS 订阅者模式(publish-subscribe pattern)
观察者模式: 实现松耦合,在此模式中, 被观察者(Subject/Observable)它只需维护一套观察者(Observer)集合,这些观察者都实现了相同的接口,subject在产生变化的时候通知Observer时,调用统一的方法
订阅者模式: 在发布订阅者模式中,发布者并不会直接的通知订阅者,发布者和订阅者彼此互不认识,发布者只需要告诉代理者,我要发消息给谁谁谁, 订阅者只需要告诉代理者,我要订阅来自谁谁谁的消息
对比: 观察者和被观察者是松耦合的关系,发布者和订阅者是不耦合关系,观察者模式,用于单个应用内部,订阅者模式用于跨应用模式,常见于消息中间件
二、RxJava 的简介:
1: 响应式编程:基于异步数据流概念的编程模式, 可以被观测 被过滤 被操作,事件可以被等待 被处罚,事件作为一个合理的映射到我们软件。
2:常见的写法:
Observable.from(getCommunitiesFromServer())
.flatMap(community -> Observable.from(community.houses))
.filter(house -> house.price>=5000000).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::addHouseInformationToScreen);
3: 基本概念:
RxJava中主要有4个角色: Publisher Subscriber Subscription Processor,Publisher 和 Subscriber。Publisher 可以发出一系列的事件,而 Subscriber 负责和处理这些事件。
在Rxjava 1.x中,最熟悉的是Observable这个类, 在RxJava2.x 中,出现了新的ObservableEmmiter, 还有Disposable
4:基本使用:
1: 初始化Observable
2: 初始化Observer
3: 建立订阅关系
//初始化Observable(被观察者)
Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> e) throws Exception {
}
}).subscribe(new Observer<Integer>() { //订阅事件
//内部初始化Observer
private Disposable mDisposable;
@Override
public void onSubscribe(Disposable d) {
mDisposable = d;
}
@Override
public void onNext(Integer integer) {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
创建 Observable 时,回调的是 ObservableEmitter ,字面意思即发射器,并且直接 throws Exception。其次,在创建的 Observer 中,也多了一个回调方法:onSubscribe,传递参数为Disposable,Disposable 相当于 RxJava 1.x 中的 Subscription, 用于解除订阅。
5: 关于线程的切换
1> subscribeOn() 指定的就是发射事件的线程,observerOn 指定的就是订阅者接收事件的线程。
2> 多次指定发射事件的线程只有第一次指定的有效,也就是说多次调用 subscribeOn() 只有第一次的有效,其余的会被忽略。(subscribeOn() => Schedulers.newThread() 和 Schedulers.io() 对发射线程进行切换)
3> 但多次指定订阅者接收线程是可以的,也就是说每调用一次 observerOn(),下游的线程就会切换一次。 (observeOn => Schedulers.newThread() 和 Schedulers.io() 对接收线程进行切换)
Schedulers.io() 代表io操作的线程, 通常用于网络,读写文件等io密集型的操作;Schedulers.computation() 代表CPU计算密集型的操作, 例如需要大量计算的操作;Schedulers.newThread() 代表一个常规的新线程;
AndroidSchedulers.mainThread() 代表Android的主线程
6: 操作符:
1: 转换操作符:
2: 过滤操作符
3: 组合类操作符
6.1: Map操作符:
map 操作符可以将一个 Observable 对象通过某种关系转换为另一个Observable 对象
eg1: Integer => String
Observable.just(1, 2, 3, 4, 5)
.map(new Func1<Integer, String>() {
@Override
public String call(Integer i) {
return "This is " + i;
}
}).subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s);
}
});
eg2: 采用 map 操作符进行网络数据解析
使用 RxJava 的时候总是和 Retrofit 进行结合使用,采用 OkHttp3 ,配合 map,doOnNext ,线程切换进行简单的网络请求:
1)通过 Observable.create() 方法,调用 OkHttp 网络请求;
2)通过 map 操作符集合 gson,将 Response 转换为 bean 类;
3)通过 doOnNext() 方法,解析 bean 中的数据,并进行数据库存储等操作;
4)调度线程,在子线程中进行耗时操作任务,在主线程中更新 UI ;
5)通过 subscribe(),根据请求成功或者失败来更新 UI 。
Observable.create(new ObservableOnSubscribe<Response>() {
@Override
public void subscribe(@NonNull ObservableEmitter<Response> e) throws Exception {
//调用 OkHttp 网络请求
Builder builder = new Builder()
.url()
.get();
Request request = builder.build();
Call call = new OkHttpClient().newCall(request);
Response response = call.execute();
e.onNext(response);
}
}).map(new Function<Response, MobileAddress>() {
@Override
public MobileAddress apply(@NonNull Response response) throws Exception {
if (response.isSuccessful()) {
ResponseBody body = response.body();
if (body != null) {
Log.e(TAG, "map:转换前:" + response.body());
return new Gson().fromJson(body.string(), MobileAddress.class);
}
}
return null;
}
}).observeOn(AndroidSchedulers.mainThread())
.doOnNext(new Consumer<MobileAddress>() {
@Override
public void accept(@NonNull MobileAddress s) throws Exception {
Log.e(TAG, "doOnNext: 保存成功:" + s.toString() + "\n");
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<MobileAddress>() {
@Override
public void accept(@NonNull MobileAddress data) throws Exception {
Log.e(TAG, "成功:" + data.toString() + "\n");
}, new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
Log.e(TAG, "失败:" + throwable.getMessage() + "\n");
}
});
6.2 flatMap操作符:flatMap 可以实现多个网络请求依次依赖
1: 将传入的事件对象装换成一个Observable对象;
2: 这是不会直接发送这个Observable, 而是将这个Observable激活让它自己开始发送事件;
3: 每一个创建出来的Observable发送的事件,都被汇入同一个Observable,这个Observable负责将这些事件统一交给Subscriber的回调方法。
6.3:zip 操作符,实现多个接口数据共同更新 UI
Observable.zip(observable1, observable2, new BiFunction<MobileAddress, CategoryResult, String>() {
@Override
public String apply(@NonNull MobileAddress mobileAddress, @NonNull CategoryResult categoryResult) throws Exception {
return "合并后的数据为:手机归属地:"+mobileAddress.getResult().getMobilearea()+"人名:"+categoryResult.results.get(0).who;
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
@Override
public void accept(@NonNull String s) throws Exception {
Log.e(TAG, "accept: 成功:" + s+"\n");
}
}, new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
Log.e(TAG, "accept: 失败:" + throwable+"\n");
}
});
6.4:采用 interval 操作符实现心跳间隔任务
mDisposable = Flowable.interval(1, TimeUnit.SECONDS)
.doOnNext(new Consumer<Long>() {
@Override
public void accept(@NonNull Long aLong) throws Exception {
Log.e(TAG, "accept: doOnNext : "+aLong );
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(@NonNull Long aLong) throws Exception {
Log.e(TAG, "accept: 设置文本 :"+aLong );
mRxOperatorsText.append("accept: 设置文本 :"+aLong +"\n");
}
});
}
6.5: doOnNext()允许我们在每次输出一个元素之前做一些额外的事情。
6.6: doOnComplete()在onComplete()调用时候回调
6.7: onSubscribe: 开始采用subscribe连接。
onNext: 对Next事件作出响应。
onError: 对Error事件作出响应。
onComplete: 事件执行完毕。
6.8:Consumer和Action这两个function都是可以简略 Observer的回调
6.9:
subscribe(): 不带任何参数,也就是说观察者没有任何回调。
subscribe(Observer<? super T> observer): 将Observer作为参数,它有四个回调方法, onSubscribe, onNext, onError, onComplete
subscribe(Consumer<? super T> onNext): 将Consumer作为参数,Consumer中有个回调方法accept,accept带有一个参数,接受被观察者发射过来的数据
subscribe(Consume<? super T> onNext, Consumer<? super Throwable> onError)带有两个Consumer参数,分别负责onNext和onError的回调
subscribe(Consume<? super T> onNext, Consumer<? super Throwable> onError, Action onComplete)带有三个参数,分别负责onNext、onError和onComplete的回调
subscribe(Consume<? super T> onNext, Consumer<? super Throwable> onError, Action onComplete, Consumer<? super Disposable> onSubscribe): 最后一个参数是观察者和被观察者建立连接回调。
6.10:Consumer和Action的用法和区别:
触发回调的,Consumer自带一个参数,Action不带参数。当被观察者发射 onNext时,由于onNext带有参数,所以使用Consumer;当被观察者发送onComplete时,由于onComplete不带参数,所以使用Action
7: Subject:它既是Observable,又是observer。也就是既可以发送事件,也可以接收事件。
四个子类PublishSubject、ReplaySubject、BehaviorSubject、AsyncSubject的区别:
1: PublicSubject:接收到订阅之后的所有数据。
2: ReplaySubject:接收到所有的数据,包括订阅之前的所有数据和订阅之后的所有数据。
3: BehaviorSubject:接收到订阅前的最后一条数据和订阅后的所有数据。
4: AsyncSubject:不管在什么位置订阅,都只接接收到最后一条数据
AsyncSubject :仅发出源Observable的最后一个值
BehaviorSubject :当观察者订阅时,会发出源Observable的最近发出的项目和所有后续项目。
PublishSubject :在订阅时发出源Observable的所有后续项。
ReplaySubject :不管订阅者何时订阅,都会发出源Observable的所有项目。
8: 用PublishSubject和Debounce操作符很容易实现延迟搜索。
debounce方法的第一个参数是时间,第二个是时间的单位
PublishSubject在订阅时并不立即触发订阅事件,允许我们在任意时刻手动调用onNext(),onError(),onCompleted来触发事件。