延遲加載策略,避免重復(fù)加載的幾種方式(性能優(yōu)化)
常見(jiàn)場(chǎng)景需求:
1: 搜索功能開(kāi)發(fā),從網(wǎng)絡(luò)和本地?cái)?shù)據(jù)庫(kù)加載數(shù)據(jù)搜索。依次輸入a b c;
2: 在點(diǎn)餐或者商品頁(yè)面, 增加商品的件數(shù),在結(jié)算按鈕旁邊顯示總件數(shù)和總金額;
常見(jiàn)做法:
1: 我們?cè)谶M(jìn)行此類(lèi)功能的開(kāi)發(fā)過(guò)程之中,如果每輸入一個(gè)數(shù)字或者點(diǎn)擊依次按鈕就要做一次網(wǎng)絡(luò)請(qǐng)求或者是做一次計(jì)算。
2: 但是在這類(lèi)操作未到結(jié)束的時(shí)候,中間的請(qǐng)求和計(jì)算都是無(wú)意義的,如果在網(wǎng)絡(luò)狀況不好,或者是手機(jī)硬件計(jì)算能力不強(qiáng)的時(shí)候,手機(jī)的負(fù)荷就會(huì)增加,同時(shí)用戶(hù)體驗(yàn)性變得很差。
思考: 我們應(yīng)該如何解決和優(yōu)化這類(lèi)問(wèn)題呢?
設(shè)置一個(gè)延遲時(shí)間,過(guò)濾掉變化較快的操作,只考慮最后一次的操作和顯示
如何理解這個(gè)方案:
比如說(shuō)我在進(jìn)行搜索操作的時(shí)候,依次輸入a b c d e f等字符, 在a的時(shí)候我本就應(yīng)該發(fā)起搜索請(qǐng)求的, 但是我在輸入到b的時(shí)候,搜索的字符就是ab, 所以之前搜索a的請(qǐng)求就是無(wú)效的, 同理可得 adbcdef才是搜索的關(guān)鍵字, 在每一次輸入字符之后, 都設(shè)置一個(gè)延遲搜索時(shí)間為1s, 如果在1s內(nèi)有重復(fù)的搜索請(qǐng)求出現(xiàn), 就將上一次的請(qǐng)求給remove掉,重新設(shè)置最新的延遲請(qǐng)求, 如果在1s內(nèi)沒(méi)有被remove掉,那么就執(zhí)行這個(gè)請(qǐng)求。
實(shí)現(xiàn)方式:
1: Handler + message方式
2: Executor+Future
3: RxJava
Handler + Message實(shí)現(xiàn)延遲處理
原理: 使用Handler 配合Handler的removeCallBacks方法和removeMessages方法來(lái)移除call back和message, 延遲使用postDelayed()實(shí)現(xiàn)。
在editText變化的時(shí)候就調(diào)用delaySearch(s),延遲1000ms, 發(fā)送消息延遲1000ms處理, 如果說(shuō)在1000ms以?xún)?nèi),delaySearch被重復(fù)調(diào)用起來(lái), 那么就移除掉之前的請(qǐng)求操作,然后繼續(xù)執(zhí)行postDelay,并且傳入最新的輸入值s。
Image.png
Executor+Future方式
1: 描述:
有一個(gè)任務(wù)提交給future, future來(lái)完成這個(gè)任務(wù),期間可以去做任何事情,一段時(shí)間之后,我們就可以從future那里獲取到結(jié)果。
2: 實(shí)現(xiàn):
1: 創(chuàng)建一個(gè)SingleThreadExecutor;
2: 調(diào)用scheduleExecutorService.schedule()方法延遲執(zhí)行某一個(gè)任務(wù)task
3: 如果當(dāng)前存在這個(gè)線(xiàn)程池,則調(diào)用取消,進(jìn)而取消這個(gè)任務(wù),然后重新執(zhí)行延遲任務(wù)。
此方法原理很類(lèi)似于Handler-message的延遲處理,但是這是屬于java executor-future的threadPool的框架。
2Image.png
3: Executor框架:
1: Executor框架引入的并發(fā)庫(kù)是一些與executor相關(guān)的一些功能類(lèi),其中包括線(xiàn)程池,executor, executors, ExecutorService, CompletionService,F(xiàn)uture,Callable 等
2:并發(fā)編程的一種編程方式是把任務(wù)拆分為一些列的小任務(wù),即Runnable,然后在提交給一個(gè)Executor執(zhí)行,Executor.execute(Runnalbe) 。Executor在執(zhí)行時(shí)使用內(nèi)部的線(xiàn)程池完成操作。
3:使用Executors創(chuàng)建線(xiàn)程池的幾種方式:
Executors類(lèi),提供了一些工廠(chǎng)方法來(lái)創(chuàng)建線(xiàn)程池,同時(shí)返回的線(xiàn)程池都實(shí)現(xiàn)了ExecutorService接口。
1:ExecutorService newFixedThreadPool(int nThreads): 創(chuàng)建一個(gè)固定數(shù)目的線(xiàn)程的線(xiàn)程池
2:ExecutorService newCachedThreadPool():創(chuàng)建一個(gè)可緩存的線(xiàn)程池,調(diào)用execute將重用之前的線(xiàn)程,如果無(wú)可用線(xiàn)程,則創(chuàng)建一個(gè)新的線(xiàn)程添加到線(xiàn)程。終止并從緩存中移除那些已經(jīng)60s未被使用的線(xiàn)程
3:ExecutorService newSingleThreadExecutor():創(chuàng)建一個(gè)單線(xiàn)程化的executor
4:ScheduledExecutorService newScheduledThreadPool(int corePoolSize): 創(chuàng)建一個(gè)支持定時(shí)及周期性的任務(wù)執(zhí)行的線(xiàn)程池,多數(shù)情況可用來(lái)使用Timer類(lèi)
4: Executor的生命周期: 運(yùn)行、關(guān)閉、終止
5: Callable 和 Future:
Future<V>代表一個(gè)異步執(zhí)行的操作,通過(guò)get()方法可以獲得操作的結(jié)果,如果異步操作還沒(méi)有完成,則,get()會(huì)使當(dāng)前線(xiàn)程阻塞。
FutureTask<V>實(shí)現(xiàn)了Future<V>和Runable<V>。
Callable代表一個(gè)有返回值得操作。
ExecutoreService提供了submit()方法,傳遞一個(gè)Callable,或Runnable,返回Future。如果Executor后臺(tái)線(xiàn)程池還沒(méi)有完成Callable的計(jì)算,這調(diào)用返回Future對(duì)象的get()方法,會(huì)阻塞直到計(jì)算完成。
RxJava實(shí)現(xiàn)
一: 題外話(huà):在講到rxJava之前,首先需要了解一下其概念和簡(jiǎn)單的API,設(shè)計(jì)思想和擴(kuò)展
觀察者模式(Observer pattern) VS 訂閱者模式(publish-subscribe pattern)
觀察者模式: 實(shí)現(xiàn)松耦合,在此模式中, 被觀察者(Subject/Observable)它只需維護(hù)一套觀察者(Observer)集合,這些觀察者都實(shí)現(xiàn)了相同的接口,subject在產(chǎn)生變化的時(shí)候通知Observer時(shí),調(diào)用統(tǒng)一的方法
訂閱者模式: 在發(fā)布訂閱者模式中,發(fā)布者并不會(huì)直接的通知訂閱者,發(fā)布者和訂閱者彼此互不認(rèn)識(shí),發(fā)布者只需要告訴代理者,我要發(fā)消息給誰(shuí)誰(shuí)誰(shuí), 訂閱者只需要告訴代理者,我要訂閱來(lái)自誰(shuí)誰(shuí)誰(shuí)的消息
對(duì)比: 觀察者和被觀察者是松耦合的關(guān)系,發(fā)布者和訂閱者是不耦合關(guān)系,觀察者模式,用于單個(gè)應(yīng)用內(nèi)部,訂閱者模式用于跨應(yīng)用模式,常見(jiàn)于消息中間件
二、RxJava 的簡(jiǎn)介:
1: 響應(yīng)式編程:基于異步數(shù)據(jù)流概念的編程模式, 可以被觀測(cè) 被過(guò)濾 被操作,事件可以被等待 被處罰,事件作為一個(gè)合理的映射到我們軟件。
2:常見(jiàn)的寫(xiě)法:
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個(gè)角色: Publisher Subscriber Subscription Processor,Publisher 和 Subscriber。Publisher 可以發(fā)出一系列的事件,而 Subscriber 負(fù)責(zé)和處理這些事件。
在Rxjava 1.x中,最熟悉的是Observable這個(gè)類(lèi), 在RxJava2.x 中,出現(xiàn)了新的ObservableEmmiter, 還有Disposable
4:基本使用:
1: 初始化Observable
2: 初始化Observer
3: 建立訂閱關(guān)系
//初始化Observable(被觀察者)
Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> e) throws Exception {
}
}).subscribe(new Observer<Integer>() { //訂閱事件
//內(nèi)部初始化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() {
}
});
創(chuàng)建 Observable 時(shí),回調(diào)的是 ObservableEmitter ,字面意思即發(fā)射器,并且直接 throws Exception。其次,在創(chuàng)建的 Observer 中,也多了一個(gè)回調(diào)方法:onSubscribe,傳遞參數(shù)為Disposable,Disposable 相當(dāng)于 RxJava 1.x 中的 Subscription, 用于解除訂閱。
5: 關(guān)于線(xiàn)程的切換
1> subscribeOn() 指定的就是發(fā)射事件的線(xiàn)程,observerOn 指定的就是訂閱者接收事件的線(xiàn)程。
2> 多次指定發(fā)射事件的線(xiàn)程只有第一次指定的有效,也就是說(shuō)多次調(diào)用 subscribeOn() 只有第一次的有效,其余的會(huì)被忽略。(subscribeOn() => Schedulers.newThread() 和 Schedulers.io() 對(duì)發(fā)射線(xiàn)程進(jìn)行切換)
3> 但多次指定訂閱者接收線(xiàn)程是可以的,也就是說(shuō)每調(diào)用一次 observerOn(),下游的線(xiàn)程就會(huì)切換一次。 (observeOn => Schedulers.newThread() 和 Schedulers.io() 對(duì)接收線(xiàn)程進(jìn)行切換)
Schedulers.io() 代表io操作的線(xiàn)程, 通常用于網(wǎng)絡(luò),讀寫(xiě)文件等io密集型的操作;Schedulers.computation() 代表CPU計(jì)算密集型的操作, 例如需要大量計(jì)算的操作;Schedulers.newThread() 代表一個(gè)常規(guī)的新線(xiàn)程;
AndroidSchedulers.mainThread() 代表Android的主線(xiàn)程
6: 操作符:
1: 轉(zhuǎn)換操作符:
2: 過(guò)濾操作符
3: 組合類(lèi)操作符
6.1: Map操作符:
map 操作符可以將一個(gè) Observable 對(duì)象通過(guò)某種關(guān)系轉(zhuǎn)換為另一個(gè)Observable 對(duì)象
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 操作符進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)解析
使用 RxJava 的時(shí)候總是和 Retrofit 進(jìn)行結(jié)合使用,采用 OkHttp3 ,配合 map,doOnNext ,線(xiàn)程切換進(jìn)行簡(jiǎn)單的網(wǎng)絡(luò)請(qǐng)求:
1)通過(guò) Observable.create() 方法,調(diào)用 OkHttp 網(wǎng)絡(luò)請(qǐng)求;
2)通過(guò) map 操作符集合 gson,將 Response 轉(zhuǎn)換為 bean 類(lèi);
3)通過(guò) doOnNext() 方法,解析 bean 中的數(shù)據(jù),并進(jìn)行數(shù)據(jù)庫(kù)存儲(chǔ)等操作;
4)調(diào)度線(xiàn)程,在子線(xiàn)程中進(jìn)行耗時(shí)操作任務(wù),在主線(xiàn)程中更新 UI ;
5)通過(guò) subscribe(),根據(jù)請(qǐng)求成功或者失敗來(lái)更新 UI 。
Observable.create(new ObservableOnSubscribe<Response>() {
@Override
public void subscribe(@NonNull ObservableEmitter<Response> e) throws Exception {
//調(diào)用 OkHttp 網(wǎng)絡(luò)請(qǐng)求
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:轉(zhuǎn)換前:" + 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 可以實(shí)現(xiàn)多個(gè)網(wǎng)絡(luò)請(qǐng)求依次依賴(lài)
1: 將傳入的事件對(duì)象裝換成一個(gè)Observable對(duì)象;
2: 這是不會(huì)直接發(fā)送這個(gè)Observable, 而是將這個(gè)Observable激活讓它自己開(kāi)始發(fā)送事件;
3: 每一個(gè)創(chuàng)建出來(lái)的Observable發(fā)送的事件,都被匯入同一個(gè)Observable,這個(gè)Observable負(fù)責(zé)將這些事件統(tǒng)一交給Subscriber的回調(diào)方法。
6.3:zip 操作符,實(shí)現(xiàn)多個(gè)接口數(shù)據(jù)共同更新 UI
Observable.zip(observable1, observable2, new BiFunction<MobileAddress, CategoryResult, String>() {
@Override
public String apply(@NonNull MobileAddress mobileAddress, @NonNull CategoryResult categoryResult) throws Exception {
return "合并后的數(shù)據(jù)為:手機(jī)歸屬地:"+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 操作符實(shí)現(xiàn)心跳間隔任務(wù)
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: 設(shè)置文本 :"+aLong );
mRxOperatorsText.append("accept: 設(shè)置文本 :"+aLong +"\n");
}
});
}
6.5: doOnNext()允許我們?cè)诿看屋敵鲆粋€(gè)元素之前做一些額外的事情。
6.6: doOnComplete()在onComplete()調(diào)用時(shí)候回調(diào)
6.7: onSubscribe: 開(kāi)始采用subscribe連接。
onNext: 對(duì)Next事件作出響應(yīng)。
onError: 對(duì)Error事件作出響應(yīng)。
onComplete: 事件執(zhí)行完畢。
6.8:Consumer和Action這兩個(gè)function都是可以簡(jiǎn)略 Observer的回調(diào)
6.9:

subscribe(): 不帶任何參數(shù),也就是說(shuō)觀察者沒(méi)有任何回調(diào)。
subscribe(Observer<? super T> observer): 將Observer作為參數(shù),它有四個(gè)回調(diào)方法, onSubscribe, onNext, onError, onComplete
subscribe(Consumer<? super T> onNext): 將Consumer作為參數(shù),Consumer中有個(gè)回調(diào)方法accept,accept帶有一個(gè)參數(shù),接受被觀察者發(fā)射過(guò)來(lái)的數(shù)據(jù)
subscribe(Consume<? super T> onNext, Consumer<? super Throwable> onError)帶有兩個(gè)Consumer參數(shù),分別負(fù)責(zé)onNext和onError的回調(diào)
subscribe(Consume<? super T> onNext, Consumer<? super Throwable> onError, Action onComplete)帶有三個(gè)參數(shù),分別負(fù)責(zé)onNext、onError和onComplete的回調(diào)
subscribe(Consume<? super T> onNext, Consumer<? super Throwable> onError, Action onComplete, Consumer<? super Disposable> onSubscribe): 最后一個(gè)參數(shù)是觀察者和被觀察者建立連接回調(diào)。
6.10:Consumer和Action的用法和區(qū)別:
觸發(fā)回調(diào)的,Consumer自帶一個(gè)參數(shù),Action不帶參數(shù)。當(dāng)被觀察者發(fā)射 onNext時(shí),由于onNext帶有參數(shù),所以使用Consumer;當(dāng)被觀察者發(fā)送onComplete時(shí),由于onComplete不帶參數(shù),所以使用Action
7: Subject:它既是Observable,又是observer。也就是既可以發(fā)送事件,也可以接收事件。
四個(gè)子類(lèi)PublishSubject、ReplaySubject、BehaviorSubject、AsyncSubject的區(qū)別:
1: PublicSubject:接收到訂閱之后的所有數(shù)據(jù)。
2: ReplaySubject:接收到所有的數(shù)據(jù),包括訂閱之前的所有數(shù)據(jù)和訂閱之后的所有數(shù)據(jù)。
3: BehaviorSubject:接收到訂閱前的最后一條數(shù)據(jù)和訂閱后的所有數(shù)據(jù)。
4: AsyncSubject:不管在什么位置訂閱,都只接接收到最后一條數(shù)據(jù)
AsyncSubject :僅發(fā)出源Observable的最后一個(gè)值
BehaviorSubject :當(dāng)觀察者訂閱時(shí),會(huì)發(fā)出源Observable的最近發(fā)出的項(xiàng)目和所有后續(xù)項(xiàng)目。
PublishSubject :在訂閱時(shí)發(fā)出源Observable的所有后續(xù)項(xiàng)。
ReplaySubject :不管訂閱者何時(shí)訂閱,都會(huì)發(fā)出源Observable的所有項(xiàng)目。
8: 用PublishSubject和Debounce操作符很容易實(shí)現(xiàn)延遲搜索。

debounce方法的第一個(gè)參數(shù)是時(shí)間,第二個(gè)是時(shí)間的單位
PublishSubject在訂閱時(shí)并不立即觸發(fā)訂閱事件,允許我們?cè)谌我鈺r(shí)刻手動(dòng)調(diào)用onNext(),onError(),onCompleted來(lái)觸發(fā)事件。

