Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android-CleanArchitecture實戰

Android-CleanArchitecture實戰

編輯:關於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架構圖

流程解析

首先在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定義的接口復用,是不是也十分的簡潔.

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved