*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發布
輪詢的功能很常見了,之前Android中比較常用的方式就是通過Handler
來實現,發送一個Deley消息,在handlerMessage再根據條件發送消息,這種方式需要小心內存泄漏,需要自己處理這個問題。這個不是我們今天的重點,今天來看下另外一種輪詢的實現方式,通過RxJava來實現。有下面兩個特點:
1 自動解除輪詢和訂閱關系,沒有內存泄漏的風險;
2 可以跟Activity或者Fragment生命周期綁定,自動停止輪詢
涉及到的RxJava知識點:
1.Subject
2.takeUntil
3 filter
4 compose
因為后面很多邏輯用到上面的操作符,所以先簡單看下這幾個操作符,熱熱身。
1.Subject
從代碼可以看出來Subject既可以當觀察者也可以當被觀察者。
public abstract class Subject<T> extends Observable<T> implements Observer<T>
所以可以在生命周期中通過Subject發送事件然后又自己接收,從而根據事件類型做相應的操作。
Subject總共有四種類型
1 AsyncSubject
2 BehaviorSubject
3 PublishSubject
4 ReplaySubject
今天我們就說下第二種類型BehaviorSubject
,它可以給訂閱者發送訂閱前最近的事件和訂閱后發送的事件:
圖中橙色的就是訂閱前最近發送的事件,在訂閱后也可以收到。文字解釋始終太蒼白,我們來看下代碼:
BehaviorSubject<Integer> behaviorSubject = BehaviorSubject.create();
behaviorSubject.onNext(1);
behaviorSubject.onNext(2);
behaviorSubject.subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
Timber.tag(TAG).d("running num : " + integer);
}
});
behaviorSubject.onNext(3);
behaviorSubject.onNext(4);
上面代碼運行結果就是收到2, 3,4
2.takeUntil
這是一個操作符,可以這樣用
AObservable.takeUntil(BObservable)
可以AObservable監聽另外一個BObservable,如果BObservable開始發送數據,AObservable就不再發送數據。
看一下官方的圖片解釋,B發送0數據后,A就停止發送數據了。
talk is cheap, show me the code
:
Observable.interval(1, TimeUnit.SECONDS).
subscribeOn(Schedulers.io()).
takeUntil(Observable.timer(5, TimeUnit.SECONDS)).
subscribe(new Consumer<Long>() {
@Override
public void accept(Long num) throws Exception {
Timber.tag(TAG).d("running num : " + num);
}
});
上面代碼的意思就是從0開發每隔1秒發送一個數據,5s時停止發送,看下運行結果,和我們的預期完美一致:
3.filter
filter操作符就是過濾的意思,只有事件滿足過濾條件時被觀察者才會發送給觀察者。看下官方的解釋圖,很清晰明了我就不做解釋了哈。
看一下怎么用,這個代碼的意思還是每個1s發送數據,但是會進行過濾只發送偶數,也是5秒后停止發送:
Observable.interval(1, TimeUnit.SECONDS).
subscribeOn(Schedulers.io()).
filter(new Predicate<Long>() {
@Override
public boolean test(Long aLong) throws Exception {
return aLong % 2 == 0;
}
}).
takeUntil(Observable.timer(5, TimeUnit.SECONDS)).
subscribe(new Consumer<Long>() {
@Override
public void accept(Long num) throws Exception {
Timber.tag(TAG).e("running num : " + num);
}
});
上面代碼的運行效果,確實是只收到了偶數。
4.compose
compose操作符是用來對Observable進行轉換操作的,并且可以保證調用鏈不被破壞。
比如我們經常這樣用:
Observable.interval(1,TimeUnit.SECONDS)
.subscribeOn(Schedulers.io()).
observeOn(AndroidSchedulers.mainThread());
這部分代碼經常寫,怎么進行封裝呢?可能有的小伙伴立馬就想到下面的方法:
private Observable composeObservable(Observable observable){
return observable.subscribeOn(Schedulers.io()).
observeOn(AndroidSchedulers.mainThread());
}
但是上面這樣用就破壞了調用鏈了,因為你肯定得這樣調用,這樣就會變得怪怪的,不是Observable開頭了,變成函數開頭。
composeObservable(Observable.interval(1,TimeUnit.SECONDS)).subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
}
});
這個問題用compose就可以完美解決:
Observable.interval(1, TimeUnit.SECONDS).
compose(bindUntil(5)).
subscribe(new Consumer<Long>() {
@Override
public void accept(Long num) throws Exception {
Timber.tag(TAG).d("running num : " + num);
}
});
private ObservableTransformer<Long, Long> bindUntil(final long deleyTime) {
return new ObservableTransformer<Long, Long>() {
@Override
public ObservableSource<Long> apply(Observable<Long> upstream) {
return upstream.subscribeOn(Schedulers.io()).takeUntil(Observable.timer(deleyTime, TimeUnit.SECONDS));
}
};
}
操作符就到這了,需要詳細了解的小伙伴可以自行參考官方文檔哈。下面進入我們的正文,首先看下怎么使用。
5.使用
目前有兩種使用方式
1.bindIntervalEvent就是綁定事件進行輪詢,事件發生時將停止輪詢
2.bindLifeCycle,就是綁定生命周期,在指定生命周期發生時停止輪詢
在開始之前我們先定義事件Event,其中FragmentEvent
對應Fragment
的生命周期,ActivityEvent
對應Activity
的生命周期,BizEvent
對應我們自定義的事件:
INTERVAL就是對應RxJava中的interval操作符產生的周期事件,可以制定輪詢間隔;
TIMER就是對應的timer事件,可以制定多長事件后產生一個事件;
STOP就是停止事件,這個是自定義的;
ALL可以匹配所有事件。
public interface Event {
enum FragmentEvent implements EventInterface{
ATTACH,
CREATE,
CREATE_VIEW,
START,
RESUME,
PAUSE,
STOP,
DESTROY_VIEW,
DESTROY,
DETACH
}
enum ActivityEvent implements EventInterface{
CREATE,
START,
RESUME,
PAUSE,
STOP,
DESTROY
}
enum BizEvent implements EventInterface{
INTERVAL,
TIMER,
STOP,
ALL
}
}
talk is cheap, show me the code, 使用起來也很簡單,首先看下第一種的使用,在Activity中添加兩個按鈕,一個開始輪詢,一個停止輪詢,布局太簡單了就不貼代碼了哈,看下重點代碼:
private static final String TAG = MainActivity.class.getSimpleName() + "_POLLING";
//開啟輪詢
PollingManager.getInstance().bindIntervalEvent(1, TAG, Event.BizEvent.INTERVAL, null);
//停止輪詢
PollingManager.getInstance().stopPolling(TAG, Event.BizEvent.INTERVAL);
看下日志打印情況,接收到INTERVAL事件后就停止輪詢了。
接著看下第二種使用方式,有兩個步驟:
1.繼承
BaseActivity
,其中接口LifeInterface
需要自己實現
public abstract class BaseActivity extends Activity implements LifeInterface
public interface LifeInterface {
void bindLife();
String getTag();
}
2.在需要輪詢的Activity實現接口LifeInterface
的兩個方法,看下例子:
@Override
public String getTag() {
return TAG;
}
@Override
public void bindLife() {
PollingManager.getInstance().bindLifeCycle(getTag(), Event.ActivityEvent.PAUSE);
}
上面這個例子監聽PAUSE事件,在Activity進入onPause時會停止輪詢,看下日志打印情況:
完全符合我們的預期哈。
下面我們來看下代碼實現。
6.PollingManager
主要邏輯在PollingManager
中,這個是這個工具的門面,有點類似于外觀模式。
首先是單例模式,activeSubjectMap
是Subject的倉庫,所有注冊的輪詢Model保存的地方。
private HashMap<String, SubjectModel<EventInterface>> activeSubjectMap;
private static PollingManager manager;
private PollingManager() {
activeSubjectMap = new HashMap<>();
}
public static PollingManager getInstance() {
if (null == manager) {
synchronized (PollingManager.class) {
if (null == manager) {
manager = new PollingManager();
}
}
}
return manager;
}
對上面的輪詢Model進行下說明,每個Model封裝了輪詢器,RxJava訂閱關系disposable和Subject。disposable就是用來停止輪詢的時候解除訂閱關系防止內存泄漏。
//Subject
private BehaviorSubject<T> behaviorSubject;
//訂閱關系
private Disposable disposable;
//輪詢器
private PollingRequest pollingRequest;
public void clearSubject(){
if (null == disposable || disposable.isDisposed()) return;
disposable.dispose();
}
每個輪詢需要做的工作可以抽象出來就是上面的PollingRequest
,注釋比較清楚就不說了,每個PollingRequest對外接口就是execute
,其中doAction
是在每個輪詢到的時候會進行調用。
public abstract class PollingRequest {
//每個Subject的唯一標識
protected String tag;
//事件接口
protected EventInterface eventInterface;
//輪詢動作
protected PollingAction pollingAction;
public PollingRequest(String tag, EventInterface eventInterface, PollingAction pollingAction) {
this.tag = tag;
this.eventInterface = eventInterface;
this.pollingAction = pollingAction;
}
public abstract Disposable execute(PollingManager pollingManager);
public String getTag() {
return tag;
}
public EventInterface getEventInterface() {
return eventInterface;
}
}
public interface PollingAction {
void doAction(Object accept);
}
看下IntervalPolling的實現方式,邏輯也比較簡單,就是每隔inteval
s進行輪詢,輪詢間隔會調用doAction
完成動作。
public class IntervalPolling extends PollingRequest {
private int inteval;
public IntervalPolling(int interval, String tag, EventInterface eventInterface, PollingAction action) {
super(tag, eventInterface, action);
this.inteval = interval;
}
@Override
public Disposable execute(PollingManager manager) {
return Observable.interval(inteval, TimeUnit.SECONDS).
compose(manager.composeEvent(tag, eventInterface)).
observeOn(AndroidSchedulers.mainThread()).
doOnNext(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
Timber.tag(Constants.TAG).d("emit interval polling, Tag = " + tag + ", num = " + aLong);
}
}).
subscribe(new Consumer<Long>() {
@Override
public void accept(Long num) throws Exception {
if (null != pollingAction) {
pollingAction.doAction(num);
}
Timber.tag(Constants.TAG).d("running interval polling, Tag = " + tag + ", num = " + num);
}
});
}
}
上面可能比較費解的邏輯就是這一行:
compose(manager.composeEvent(tag, eventInterface))
調用PollingManager
中的composeEvent
方法,跟進去看看:
public ObservableTransformer<Long, Long> composeEvent(final String tag, final EventInterface outEvent) {
BehaviorSubject<EventInterface> subject = getSubject(tag);
if (null == subject) {
Timber.tag(Constants.TAG).e("subject = null");
return new EmptyObservableTransformer();
}
final Observable observable = subject.filter(new Predicate<EventInterface>() {
@Override
public boolean test(EventInterface event) throws Exception {
Timber.tag(Constants.TAG).i("receive event: %s", event);
boolean filter = outEvent == event || event == ALL;
if (filter) clearSubject(tag);
return filter;
}
});
return new ObservableTransformer<Long, Long>() {
@Override
public ObservableSource<Long> apply(Observable<Long> upstream) {
return upstream.subscribeOn(Schedulers.io()).takeUntil(observable);
}
};
}
首先就是
takeUntil
操作符,當Subject發送數據時,IntervalPolling
就會停止輪詢;
Subject什么時候發送數據?就是在subject.filter
返回真的時候。Subject會根據接收到的Event和訂閱時的Event進行相等,或者接收到的事件是ALL都會返回真。
其實上面的邏輯需要對RxJava有一定的了解,這個不在本文的范圍,小伙伴們自行網上查閱哈。
輪詢器,Model和觸發條件都有了,剩下的問題就是創建啟動和銷毀的問題了,先看下創建。
7.創建啟動
先看下第一種綁定事件的創建方式:
public BehaviorSubject<EventInterface> bindIntervalEvent(int interval, @NonNull String tag, @NonNull EventInterface eventInterface, PollingAction action){
//1.創建輪詢器
IntervalPolling intervalPolling = new IntervalPolling(interval, tag, eventInterface, action);
//2.創建Subject
createSubject(intervalPolling);
//3.啟動輪詢
startPolling(tag);
//4.返回Subject
return activeSubjectMap.get(tag).getBehaviorSubject();
}
邏輯比較簡單哈,其中第二步創建Subject時會將Subject和輪詢器緩存到HashMap<String, SubjectModel<EventInterface>> activeSubjectMap;
,其中key就是Subject的唯一標識tag。
生命周期的創建方式也是一樣的四個步驟,唯一不一樣的就是這里輪詢器是生命周期輪詢器。
public BehaviorSubject<EventInterface> bindLifeCycle(@NonNull String tag,@NonNull EventInterface eventInterface){
//1.創建輪詢器
PollingRequest request = new LifePolling(tag, eventInterface, null);
//2.創建Subject
createSubject(request);
//3.啟動輪詢
startPolling(tag);
//4.返回Subject
return activeSubjectMap.get(tag).getBehaviorSubject();
}
創建分析完了,就看下怎么停止輪詢了。
8.停止
停止的邏輯其實就是發射事件給Subject,這樣Subject自己可以接收到,然后進入Filter的邏輯進行判斷,和創建的時候注冊事件或者ALL事件一致就會停止輪詢了。
public boolean stopPolling(String tag, EventInterface event) {
BehaviorSubject<EventInterface> subject = getSubject(tag);
if (null == subject) {
Timber.tag(Constants.TAG).e("can not find subject according to the %s", tag);
return false;
}
subject.onNext(event);
Timber.tag(Constants.TAG).i("Stop Polling SubjectTag = " + tag + ", Event = " + event.toString());
return true;
}
最后再補充一點就是發射事件的邏輯,會掃描activeSubjectMap
中的所有Subject,然后發射事件:
public void emitEvent( @NonNull EventInterface event){
if (null == activeSubjectMap) return;
for (Map.Entry<String, SubjectModel<EventInterface>> next : activeSubjectMap.entrySet()) {
BehaviorSubject<EventInterface> behaviorSubject = next.getValue().getBehaviorSubject();
if (null == behaviorSubject) return;
behaviorSubject.onNext(event);
}
}
9.總結
到這里基本涉及的邏輯都分析完了,希望能提供給到家另外一種輪詢的實現方式,如果有什么問題歡迎留言哈,謝謝!
我的博客主頁juexingzhe歡迎關注哈。
祝大家越碼越開心。
歡迎關注公眾號:JueCode