編輯:關於Android編程
關於Retrofit,
Rxjava,
MVP等的使用現在已經成為了
Android項目開發的標配,而
Clean架構都能很好的兼顧這些方面,使得代碼結構清晰,
而且在一般在項目開發中多少都會用到緩存,但是我們知道OkHttp只支持
GET緩存,
而我們很多時候需要使用POST請求並緩存數據.那麼這時候我們就需要考慮手動來實現
POST緩存了,而
Clean architecture也考慮到了這一個方面
原理
說到手動緩存,那麼就需要判斷是否存在緩存,緩存是否過期和在請求之前判斷是否有網絡連接等判斷,進而對相關需要緩存的請求數據進行緩存下來
Clean就是在請求之前通過判斷來創建不同的
layer獲取數據達到了這個效果,關於清晰架構的介紹Architecting Android…The evolution.
在清晰架構的實現中,既用到了上面提到的一些技術,同時也很好的處理了緩存的問題.作者在[Module/data]中實現了網絡及緩存相關.在[Module/domain]中主要包含了一些 UseCase,而在[Module/presentation]中包含了一些
Presenter操作
下面原文中的一張圖可以很好的說明問題.
流程解析
首先在
Clean架構中有一個叫做
UseCase的接口承載著相當重要的作用,其下是更加細粒度的數據請求操作,比如原項目中的結構
UseCase (interface) --------------- abstract buildUseCaseObservable();
|
|-----GetUserList|GetUserDetails(impl)
|
|---UserRepository(interface)
|
|-----UserDataRepository(impl)
|
|---------UserDataStoreFactory(創建工廠,根據不同的情況創建不同的UserDataStore(Disk或者Cloud))
|
|-----------DiskUserDataStore(從UserCache中獲取數據)
|
|-----------CloudUserDataStore(從api中獲取數據並存入UserCache中)
從上面的流程分析圖和源碼中,我們可以看到
UserRepository與
UserDataStore兩個接口非常的相似,一個是獲取數據的
interface,一個是保存數據的
interface.
中間再經過轉換完成,而在實際開發中我們的接口可能達到好幾百個,並非Demo中的兩個接口,那麼用這種方式寫起來就會顯得有些吃力不討好的樣子,同時這裡的
UseCase接口的
excute方法需要傳入一個
Subscriber,但是裡面只是做了線程切換的操作
貌似並沒有發揮其作用.
public interface UserRepository {
Observable> users();
Observable user(final int userId);
}
public interface UserDataStore {
Observable> userEntityList(final String listId);
Observable userEntityDetails(final String userId);
}
public abstract class UseCase {
//...
protected abstract Observable buildUseCaseObservable();
@SuppressWarnings("unchecked")
public void execute(Subscriber useCaseSubscriber) {
this.subscription = this.buildUseCaseObservable()
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler())
.subscribe(useCaseSubscriber);
}
//...
}
同時
UserRepository會調用工廠類來創建
UserDataStore實例來獲取數據,而
UserDataStore的兩層中的邏輯也十分的相似,
CloudUserDataStore中獲取到網絡數據後保存到
UserCache中
而DiskUserDataStore則直接從
UserCache中獲取數據.
@Singleton
public class UserDataRepository implements UserRepository {
private final UserDataStoreFactory userDataStoreFactory;
//通過工廠創建UserDataStore,通過UserDataStore獲取數據
@Override public Observable> users() {
//we always get all users from the cloud
final UserDataStore userDataStore = this.userDataStoreFactory.createCloudDataStore();
return userDataStore.userEntityList().map(this.userEntityDataMapper::transform);
}
@Override public Observable user(int userId) {
final UserDataStore userDataStore =
this.userDataStoreFactory.create(String.valueOf(userId), false);
return userDataStore.userEntityDetails(String.valueOf(userId)).map(this.userEntityDataMapper::transform);
}
}
class CloudUserDataStore implements UserDataStore {
private final RestApi restApi;
private final UserCache userCache;
//將保存到UserCache中
private final Action1 saveToCacheAction = userEntity -> {
if (userEntity != null) {
CloudUserDataStore.this.userCache.put(userEntity);
}
};
CloudUserDataStore(RestApi restApi, UserCache userCache) {
this.restApi = restApi;
this.userCache = userCache;
}
@Override public Observable> userEntityList() {
return this.restApi.userEntityList();
}
@Override public Observable userEntityDetails(final int userId) {
return this.restApi.userEntityById(userId).doOnNext(saveToCacheAction);
}
class DiskUserDataStore implements UserDataStore {
private final UserCache userCache;
DiskUserDataStore(UserCache userCache) {
this.userCache = userCache;
}
@Override public Observable> userEntityList() {
//TODO: implement simple cache for storing/retrieving collections of users.
throw new UnsupportedOperationException("Operation is not available!!!");
}
//從UserCache中獲取數據,這裡以userId作鍵
@Override public Observable userEntityDetails(final int userId) {
return this.userCache.get(userId);
}
}
改造
為了便於實際開發使用,並針對如上問題,我的做法是會免去定義兩個接口的方式,直接存儲請求下來的數據,這樣的話在獲取緩存後,也不需要再次轉換為請求的數據,當然作者只是個示例
其次這個接口不能包含我們所有的請求api,雖然我們可以復用
retrofit的
Api,但這些實現類中的邏輯都是一樣的,都是獲取數據和保存數據,實現類似的邏輯顯得非常的不優雅
那麼我們的上層接口就呼之欲出了,就是返回數據一個方法,具體的layer具體的實現.
/**
* Interface that represents a Repository for getting {@link Wrapper} related data.
*/
public interface Repository {
/**
* Get an {@link rx.Observable} which will emit a {@link T}.
*/
Observable request(final Wrapper _wrapper);
}
這裡有一個
T泛型的包裝類,其中包裝了請求相關方法,參數,數據存儲類型等,源代碼如下
public class Wrapper {
private boolean refresh = true;//是否請求最新數據
private final T t;//請求數據的類型,和返回的Observable中的T一樣
private final Type typeOfT;// Gson實例化數據用到的,避免TypeToken的泛型類型丟失問題
private final Class[] paramsType;//請求數據的的參數類型
private final Object[] params;//請求的參數
private final String methodName;// Retrofit接口中定義的方法名,供請求調用
private final Builder mBuilder;// Wrapper的構建起builder
public Wrapper(boolean _refresh, T _t, Type _typeOfT, Class[] _paramsType, Object[] _params,
String _methodName, Builder _builder) {
this.refresh = _refresh;
this.t = _t;
this.typeOfT = _typeOfT;
this.paramsType = _paramsType;
this.params = _params;
this.methodName = _methodName;
this.mBuilder = _builder;
}
public Builder getBuilder() {
return mBuilder;
}
public boolean isRefresh() {
return refresh;
}
public T getT() {
return t;
}
public Type getTypeOfT() {
return typeOfT;
}
public String getMethodName() {
return methodName;
}
public Object[] getParams() {
return params;
}
public Class[] getParamsType() {
return paramsType;
}
public String getUnique() {
Object[] params = getParams();
String result = getMethodName();
if (null != params && params.length > 0) {
for (Object o : params) {
result += o.toString();
}
}
return result;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private boolean refresh = true;
private T t;
private Type typeOfT;
private Object[] params;
private Class[] paramsType;
private String methodName;
public Builder T(T _t) {
t = _t;
return this;
}
public Builder typeOfT(Type typeOfT) {
this.typeOfT = typeOfT;
return this;
}
public Builder method(String method) {
methodName = method;
return this;
}
public Builder params(Object[] params) {
this.params = params;
return this;
}
public Builder paramsType(Class[] _paramsType) {
this.paramsType = _paramsType;
return this;
}
public Builder isRefresh(boolean isRefresh) {
this.refresh = isRefresh;
return this;
}
public Wrapper build() {
return new Wrapper(refresh, t, typeOfT, paramsType, params, methodName, this);
}
}
}
而我們的
DataRepository,
DiskDataStore,
CloudDataStore實現了上述接口,其中
DataRepository依舊是調用工廠來獲取不同的
Repository
@Singleton public class DataRepository implements Repository {
private final DataStoreFactory userDataStoreFactory;
@Inject public DataRepository(DataStoreFactory dataStoreFactory) {
this.userDataStoreFactory = dataStoreFactory;
}
@Override public Observable request(Wrapper _wrapper) {
final Repository userDataStore =
this.userDataStoreFactory.create(_wrapper);
return userDataStore.request(_wrapper);
}
}
而
DataStoreFactory中的邏輯也非常的簡單.
public Repository create(Wrapper _wrapper) {
Repository dataStore;
//是否有網絡
if (!isThereInternetConnection()) {
//緩存是否可用
if (cacheExpired(_wrapper)) {
dataStore = new DiskDataStore(this.userCache);
} else {
dataStore = createCloudDataStore();
}
} else {
//是否需要最新數據
if (!_wrapper.isRefresh() && cacheExpired(_wrapper)) {
dataStore = new DiskDataStore(this.userCache);
} else {
dataStore = createCloudDataStore();
}
}
return dataStore;
}
//是否有緩存,並且緩存是否過期
private boolean cacheExpired(Wrapper _wrapper) {
return !this.userCache.isExpired() && this.userCache.isCached(_wrapper.getUnique());
}
其余兩層
DataStore則更加的簡單了
DiskDataStore(UserCache userCache) {
this.userCache = userCache;
}
@Override public Observable request(Wrapper _wrapper) {
return this.userCache.get(_wrapper);
}
CloudDataStore(YaoduApi restApi, UserCache userCache) {
this.restApi = restApi;
this.userCache = userCache;
}
@SuppressWarnings("unchecked") @TargetApi(Build.VERSION_CODES.KITKAT) @Override
public Observable request(Wrapper _wrapper) {
Object[] params = _wrapper.getParams();
Class[] paramsType = _wrapper.getParamsType();
String methodName = _wrapper.getMethodName();
try {
Method method = restApi.getClass().getMethod(methodName, paramsType);//反射獲取方法
Observable observable = (Observable) method.invoke(restApi, params);//發射調用方法
return observable.doOnNext(_t -> {
Wrapper build = _wrapper.getBuilder().T(_t).typeOfT(_t.getClass()).build();
CloudDataStore.this.userCache.put(build);
});
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException _e) {
_e.printStackTrace();
}
return Observable.empty();
}
而這裡我們的
UserCase則不再需要了,雖然用了反射調用.但為在已成型的項目中使用
Clean architecture提供了方便.
測試
最後改造完成了,我們需要的是進行測試,看一下,我們的改造是否可用
Wrapper> wrapper = Wrapper.>builder().method("userEntityList")
.typeOfT(new TypeToken>() {
}.getType())
.build();
mRepository.request(wrapper)
.compose(Transformers.switchSchedulers())
.subscribe(new DefaultSubscriber>() {
@Override public void onNext(List _userEntities) {
super.onNext(_userEntities);
Toast.makeText(MainActivity.this, _userEntities.toString(), Toast.LENGTH_SHORT).show();
}
});
這裡直接使用了Retrofit定義的接口復用,是不是也十分的簡潔.
android 系統集成了一個輕量級的數據庫,SQLite只是一個嵌入式的數據庫引擎;android提供SQLiteDatabase代表一個數據庫,一旦應用程序獲得了SQ
今天在修改一個布局問題時候,發現自己對權重的理解還不夠。首先問題如圖:一個TextView沒有按要求顯示完整,顯示成了2行。怎麼辦呢?方法1:是把它左面的字體放小。結果師
本章內容 第1節 AnalogClock和DigitalClock 第2節 CalendarView 第3節 DatePicker和TimerPicker 第4節 Chr
關於自定義屬性,我們用的比較多的時候就是在自定義view的時候了,其實自定義屬性還有一些其余的妙用。這裡講解一個利用自定義的屬性為應用程序全局的替換背景的例子。1.And