Android數據層架構的實現 上篇

本文首發于微信公眾號——世界上有意思的事,搬運轉載請注明出處,否則將追究版權責任。微信號:a1018998632,交流qq群:859640274

最近我們app的服務器吃不消了,所以我在為服務器增加緩存層之后,又想到在app端進行二級緩存以減少app對服務器的訪問。我想很多app應該在項目的初期架構的時候就考慮到了這個問題,但是我當時開發這個app的時候完全不懂架構和設計模式,所以對性能根本沒有考慮,導致了現在服務器經常崩潰。關于服務器的優化之后有時間再說,今天我們先來看看如何對一個app的數據的請求和緩存進行架構。

一.數據請求的分類、數據的可緩存性分析和數據同步##

如何對請求進行歸類?這是個問題,在Http請求里設定了八種請求的方式,但是顯然并不適合我們這里的情況。我對我們app中的數據請求進行歸類之后進行了一下兩種分類

  • 1.根據需要對請求做那些操作進行分類:
    • 1.GET:需要去內存、硬盤和服務器中取數據的請求,請求時可以提供參數,但是參數并不會上傳到內存、硬盤和服務器中。
    • 2.INSERT:需要將數據上傳至內存、硬盤和服務器中,返回的數據根據具體情況判斷是否需要緩存到內存或者硬盤中。
    • 3.NONE:向服務器進行驗證操作,必須和服務器連接,上傳的參數不會被存儲在內存、硬盤和服務器中,返回的數據也不會被緩存在內存或者硬盤中
  • 2.根據請求需要進行到哪個層次進行分類:
    • 1.to_memory:僅僅向內存進行數據請求。
    • 2.to_local:在向內存請求數據無果之后,去硬盤中進行數據請求
    • 3.to_network:在向內存和硬盤請求數據無果之后,去服務器中進行數據請求。

什么樣的數據更適合在本地進行緩存呢?

  • 1.我們首先可以考慮服務器數據庫中不常更新的數據表,例如:國家、地區、機關的信息表等等,這種數據需要客戶端開發人員與后臺人員進行溝通后確認。我認為像更改頻率在 1天/次以上 并且 用戶不可操作 的數據表就可以稱為不常更新數據表。
  • 2.僅僅關于個人的信息,例如:用戶個人信息、用戶聯系人表、用戶行為表等等。這里需要注意的一點是僅僅關于個人,因為像手Q發的動態和百度貼吧的帖子也是個人發的信息,那么這類數據適不適合進行本地緩存呢?我的結論是 不適合 ,因為像這種個人數據,摻雜著其他用戶的信息,一旦其他用戶和這種個人數據交互的時候,本人app中的數據就需要花很大的力氣進行數據同步。(這里的例子不是很準確,因為像手Q和貼吧都可以以推送的形式進行數據同步并且送達率很高。而我們一般開發者使用的都是第三方的推送,一旦推送量比較大送達率很難接近50%,所以這種數據同步的方式不可取。)

前面我們說了兩類可以在本地進行緩存的數據表,那么這兩類數據該如何與服務器同步?

  • 1.對于不常更新的數據表,我們可以選定兩三個除GET以外的用戶使用頻率不高的請求(例如:登錄接口等等),這些請求在使用的時候必然會連上服務器。我們可以在這些接口中附加<時間戳:數據表>的鍵值對列表字段,然后讓服務器對時間戳進行判斷,判斷本地緩存的數據表中的數據是否被更新過,最后在接口中返回需要更新的數據表標識,以便客戶端在下次在本地請求該過期表的時候跳過內存和硬盤緩存直接去服務器取回相應的更新數據并更新本地表。
  • 2.對于個人數據,只要保證網絡請求成功的時候也在本地緩存中插入緩存,即可同步數據。
  • 3.其實還有一種方式就是在服務器更改數據之后,將數據推送給客戶端,但是我在比較了第三方推送的推送率之后選擇了放棄這個辦法,因為客戶端和服務器之間的數據完整性是比較重要的。

二、數據引擎的設計模式##

分析了上面的三個問題之后,我們得想想要使用什么樣的方法來設計我們的數據引擎,熟悉java設計模式的同學可能會想到很多東西。我最近看了 Android源碼設計模式解析與實戰 之后也了解到了許多設計模式,現在的問題是怎么用這些設計模式呢?還好我最近又翻譯了Fresco和OkHttp的源碼,所以可以從這兩個牛逼的開源項目中吸取一點經驗。

  • 1.單例:這個我想絕大部分同學應該沒問題,所以簡單講講:我們可以將 內存緩存、硬盤緩存、Http請求服務、數據引擎 這些需要花費大量資源創建的實例設置為單例。
  • 2.Builder模式:對于一些構造參數比較多的類,我們可以使用這個模式來使代碼變得清晰;
  • 3.外觀模式:我們的數據引擎,只需要暴露出一個 數據引擎的實例就可以了,其他像 內存緩存、硬盤緩存、Http請求服務等,都可以作為子系統集成在數據引擎實例中。
  • 4.靜態工廠模式:對于有多個構造函數的類,我們可以使用這個模式,使客戶端在使用的時候不會傳錯參數。
  • 5.設計模式六大原則中的五點:
    • 1.單一職責原則:數據引擎中每一個子系統都只能關注自己的功能實現,不參與其他功能的任何邏輯
    • 2.開閉原則:對于數據引擎,我們不能在其內部持有各個子系統的具體實現,在開發過程中我們可能會隨時遇見更好的子系統的實現,為了方便更改實現,我們需要在數據引擎中持有各個子功能抽象出來的接口,當我們遇見更好的子系統的實現的時候只需要讓其實現該接口,可以隨時更換實現。
    • 3.里氏替換原則:在2的基礎上,任何子系統的實現都能直接放入數據引擎中,而不需要任何其他的配置,如if等條件。
    • 4.依賴倒置原則:各個子功能的實現類不需要有任何交流,交流都是通過子功能接口實現的。用職責鏈模式實現這個原則,后面會講到。
    • 5.接口隔離原則:每個子功能的接口需要最精簡。
  • 6.職責鏈模式:這里是借鑒OkHttp的攔截器。將 MemoryCache-》DiskCache-》NetWork 這樣的運行流程以攔截器鏈的形式串起來。攔截器鏈中子功能的交流都是以接口的形式,這也就實現了 依賴倒置原則。
  • 7.緩存生命周期的log展示:這里是借鑒Fresco,內存緩存和硬盤緩存 的緩存條目 從存在到刪除可以看成一個完整的生命周期。為了方便debug,我們可以將(插入、更新、刪除、清空等操作)看成一個個事件,然后建立一個監聽器監聽整個生命周期。
  • 8.享元模式:由于我們請求和緩存可能會比較頻繁,所以我們可以以對象池的形式復用對象,以減少大量創建和銷毀對象所需要的時間。

三、具體代碼實現##

務虛了這么久,也應該務實了。先上項目例子數據引擎架構項目源碼,建議大家下載過來,然后結合博客一起觀看下面我來分析一下數據引擎架構的實現代碼。

1.請求和返回類設計

我們可以將所有的數據請求,當成類似于網絡請求的格式。這樣一來我們可以創建兩個類一個Request,一個Response供客戶端使用

public class Request {

private static final String TAG="Request";
private static final Object RECYCLER_LOCK = new Object();
private static int MAX_RECYCLED = 5;
private static Request sFirstRecycledRequest;
private static int sRecycledCount;

private Request mNextRecycledRequest;

@NonNull
private RequestFlag mRequestFlag;
@NonNull
private CacheKey mCacheKey;
@Nullable
private Object mParam;
private boolean isCacheToMemory=true;
private boolean isSaveToLocal=true;
private boolean[] interceptorIsEnable=new boolean[]{true,true,true,true,true,true,true,true,true};

public static Request setFlag(@NonNull RequestFlag requestFlag){
    return obtain(requestFlag,null,null,null,-1);
}

public static Request setFlagParam(@NonNull RequestFlag requestFlag, @NonNull Object param){
    return obtain(requestFlag,param,null,null,-1);
}

public static Request setFlagParamKey(@NonNull RequestFlag requestFlag, @NonNull Object param, @NonNull String key){
    return obtain(requestFlag,param,key,null,-1);
}

public static Request setFlagParamInterceptorIsEnable(@NonNull RequestFlag requestFlag, @NonNull Object param, @NonNull boolean[] interceptorIsEnable){
    return obtain(requestFlag,param,null,interceptorIsEnable,-1);
}

public static Request setFlagParamWhichServiceUnable(@NonNull RequestFlag requestFlag, @NonNull Object param, int whichUnable){
    return obtain(requestFlag,param,null,null,whichUnable);
}

public void recycle() {
    synchronized (RECYCLER_LOCK) {
        if (sRecycledCount < MAX_RECYCLED) {
            reset();
            sRecycledCount++;

            if (sFirstRecycledRequest != null) {
                mNextRecycledRequest= sFirstRecycledRequest;
            }
            FLog.v(TAG,"回收Request  sRecycledCount:"+sRecycledCount);
            sFirstRecycledRequest= this;
        }
    }
}

private static Request obtain(@NonNull RequestFlag requestFlag, Object param, String key, boolean[] interceptorIsEnable, int whichUnable) {
    synchronized (RECYCLER_LOCK) {
        if (sFirstRecycledRequest!= null) {
            Request requestToReuse = sFirstRecycledRequest;
            sFirstRecycledRequest= requestToReuse.mNextRecycledRequest;
            requestToReuse.mNextRecycledRequest= null;

            requestToReuse.mRequestFlag=requestFlag;
            if (param==null){
                requestToReuse.mCacheKey=new SimpleCacheKey(requestFlag.toString());
            }else {
                requestToReuse.mParam=param;
                if (key!=null){
                    requestToReuse.mCacheKey = new SimpleCacheKey(key);
                }else {
                    requestToReuse.mCacheKey=new SimpleCacheKey(JsonUtil.objectToJson(param));
                    if (interceptorIsEnable!=null) {
                        requestToReuse.interceptorIsEnable = interceptorIsEnable;
                    }else {
                        if (whichUnable!=-1)requestToReuse.interceptorIsEnable[whichUnable]=false;
                    }
                }
            }
            sRecycledCount--;
            FLog.v(TAG,"從對象池中獲取Request  sRecycledCount:"+sRecycledCount);
            return requestToReuse;
        }
    }
    FLog.v(TAG,"對象池已空,創建一個Request  sRecycledCount:"+sRecycledCount);
    if (param==null){
        return new Request(requestFlag);
    }else {
        if (key!=null){
            return new Request(requestFlag,param,key);
        }else {
            if (interceptorIsEnable!=null) {
                return new Request(requestFlag,param,interceptorIsEnable);
            }else {
                if (whichUnable==-1){
                    return new Request(requestFlag,param);
                }else {
                    return new Request(requestFlag,param,whichUnable);
                }
            }
        }
    }
}

private void reset() {
    mRequestFlag=null;
    mCacheKey=null;
    mParam=null;
    isCacheToMemory=true;
    isSaveToLocal=true;
    interceptorIsEnable=new boolean[]{true,true,true,true,true,true,true,true,true};
}

private Request() {
}

private Request(@NonNull RequestFlag requestFlag) {
    mRequestFlag = requestFlag;
    mCacheKey=new SimpleCacheKey(mRequestFlag.toString());
}

private Request(@NonNull RequestFlag requestFlag, @Nullable Object param) {
    mRequestFlag = requestFlag;
    mParam = param;
    mCacheKey=new SimpleCacheKey(JsonUtil.objectToJson(param));
}

private Request(@NonNull RequestFlag requestFlag, @Nullable Object param, boolean[] interceptorIsEnable) {
    mRequestFlag = requestFlag;
    mParam = param;
    this.interceptorIsEnable = interceptorIsEnable;
    mCacheKey=new SimpleCacheKey(JsonUtil.objectToJson(param));
}

private Request(@NonNull RequestFlag requestFlag, @Nullable Object param, int whichUnable) {
    mRequestFlag = requestFlag;
    mParam = param;
    interceptorIsEnable[whichUnable]=false;
    mCacheKey=new SimpleCacheKey(JsonUtil.objectToJson(param));
}

private Request(@NonNull RequestFlag requestFlag, @Nullable Object param, String key) {
    mCacheKey = new SimpleCacheKey(key);
    mRequestFlag = requestFlag;
    mParam = param;
}

public Request setParam(@Nullable Object param) {
    this.mParam = param;
    return this;
}

public Request setRequestFlag(@NonNull RequestFlag requestFlag) {
    mRequestFlag = requestFlag;
    return this;
}

public Request setServiceIsEnable(int serviceNum, boolean isEnable) {
    if (serviceNum<interceptorIsEnable.length)interceptorIsEnable[serviceNum]=isEnable;
    return this;
}

public Request setInterceptorIsEnable(boolean[] interceptorIsEnable) {
    this.interceptorIsEnable = interceptorIsEnable;
    return this;
}

public boolean getWhichServiceIsEnable(int serviceNum) {
    return serviceNum < interceptorIsEnable.length && interceptorIsEnable[serviceNum];
}

public Request setCacheKey(@NonNull CacheKey cacheKey) {
    mCacheKey = cacheKey;
    return this;
}

public Request setCacheToMemory(boolean cacheToMemory) {
    isCacheToMemory = cacheToMemory;
    return this;
}

public Request setSaveToLocal(boolean saveToLocal) {
    isSaveToLocal = saveToLocal;
    return this;
}

public boolean[] getInterceptorIsEnable() {
    return interceptorIsEnable;
}

@Nullable
public Object getParam() {
    return mParam;
}

@NonNull
public RequestFlag getRequestFlag() {
    return mRequestFlag;
}

@NonNull
public CacheKey getCacheKey() {
    return mCacheKey;
}

public boolean isCacheToMemory() {
    return isCacheToMemory;
}

public boolean isSaveToLocal() {
    return isSaveToLocal;
}

@Override
public String toString() {
    return "Request:{" +
            "mCacheKey=" + mCacheKey +
            ", mRequestFlag=" + mRequestFlag +
            ", mParam=" + mParam +
            ", isCacheToMemory=" + isCacheToMemory +
            ", isSaveToLocal=" + isSaveToLocal +
            ", interceptorIsEnable=" + Arrays.toString(interceptorIsEnable) +
            '}';
}
}
  • 1.Request:我們在考慮請求的時候我總結了以下幾個在請求鏈之中需要用到的東西
    • 1.Object mParam:請求的參數,由于各種數據請求需要的參數是不同的,所以我們可以將請求參數設置為Object,在具體請求中進行更改
    • 2.CacheKey mCacheKey:CacheKey是一個接口,這個接口的實現類會作為內存和硬盤緩存時候的key來使用
    • 3.RequestFlag mRequestFlag:每一個不同的請求我們都需要用一個enum類來標識。RequestFlag是一個接口,我們根據前面第一個請求的分類(NONE、GET、INSERT),實現了三個不同的enum,然后讓這三個enum實現RequestFlag。這樣一來就能用一個RequestFlag,標識出一個請求的種類,一個RequestFlag中除了有請求的分類,里面還有請求的層次(to_memory、to_local、to_network)。
    • 4.boolean isCacheToMemory:在請求從硬盤或者網絡返回的時候,我們需要判斷該請求返回的數據是否需要被內存緩存,默認是需要的
    • 5.boolean isSaveToLocal:在請求從網絡返回的時候,我們需要判斷該請求返回的數據是否需要被存儲在本地,默認是需要的
    • 6.boolean[] interceptorIsEnable:在請求鏈之中,我們可能需要屏蔽某些攔截器,此時可以在這里設置哪些攔截器奏效,注意這里的攔截是指請求上傳時候的攔截。
    • 7.除上面幾個參數,其他的參數都是為了創建對象池構建的,這里的對象池使用了鏈表的方式,在使用obtain方法獲取的時候,將鏈首的對象取出。在使用recycle方法回收本對象的時候,將本對象設為鏈首。鏈表設置了最大值,當超過最大值的時候,就直接使用創建的對象
    • 8.在獲取Reuqest對象的時候,使用了由于要傳入的參數比較多,所以使用了靜態工廠模式和類似Builder的鏈式創建模式

public class Response<Response1,Response2 ,Response3> {

private boolean isSuccess;
private Exception mException;

private Response1 mResponse1;
private Response2 mResponse2;
private Response3 mResponse3;

public static <T1,T2,T3> Response<T1,T2,T3>  getFailedResponse(Exception exception){
    return new Response<T1,T2,T3>(false,exception);
}

public static Response getCommonResponseOne(Object response1){
    return new Response<Object,Object,Object>(response1);
}

public static Response getCommonResponseTwo(Object response1,Object response2){
    return new Response<Object,Object,Object>(response1,response2);
}

public static Response getCommonResponseThree(Object response1,Object response2,Object response3){
    return new Response<Object,Object,Object>(response1,response2,response3);
}

public static <T> Response getResponseOne(T response1){
    return new Response<T,Object,Object>(response1);
}

public static <T1,T2> Response getResponseTwo(T1 response1,T2 response2){
    return new Response<T1,T2,Object>(response1);
}

public static <T1,T2,T3> Response getResponseOne(T1 response1,T2 response2,T3 response3){
    return new Response<T1,T2,T3>(response1,response2,response3);
}

private Response(boolean isSuccess, Exception exception) {
    this.isSuccess = isSuccess;
    mException = exception;
}

private Response(Response1 response1) {
    mResponse1 = response1;
    isSuccess=true;
}

private Response(Response1 response1, Response2 response2) {
    mResponse1 = response1;
    mResponse2 = response2;
    isSuccess=true;
}

private Response(Response1 response1, Response2 response2, Response3 response3) {
    mResponse1 = response1;
    mResponse2 = response2;
    mResponse3 = response3;
    isSuccess=true;
}

public Response1 getResponse1() {
    return mResponse1;
}

public void setResponse1(Response1 response1) {
    mResponse1 = response1;
}

public Response2 getResponse2() {
    return mResponse2;
}

public void setResponse2(Response2 response2) {
    mResponse2 = response2;
}

public Response3 getResponse3() {
    return mResponse3;
}

public void setResponse3(Response3 response3) {
    mResponse3 = response3;
}

public boolean isSuccess() {
    return isSuccess;
}

public Exception getException() {
    return mException;
}
}
  • 2.Response:這個類比較簡單,因為我們在客戶端使用的時候,可能會將一個數據結果轉化為多個實體供界面使用,所以有三個泛型參數可以設置。還有就是請求出錯的時候返回的Exception,注意這里的出錯僅僅指的是異常,其他類似于數據不符合的情況不在考慮之中。

2.攔截鏈的骨架

我們在前面的設計模式中提到了,在設計一個架構的時候需要面向接口編程,這樣才會符合前面說的設計模式六大原則。

public interface Interceptor {
Object intercept(Chain chain,boolean enable) throws Exception;

Service getService();
interface Chain {
    Object proceed() throws Exception;

    Request getRequest();
}
}   

public interface Service {
boolean isEnabled();

void setEnable(boolean enable);

Object in(Request request, Object in) throws Exception;

Object out(Request request) throws Exception;
}
  • 1.Interceptor接口:我們前面提到的攔截器鏈,就是由一個個攔截器組成,而每個攔截器會實現Interceptor接口。####
    • 1.intercept方法就是每個攔截器要做的事情,在各個具體的攔截器中實現
    • 2.Chain其實就是整個攔截鏈的上下文,這個接口的實現會持有 攔截器鏈、Request和當前攔截器的編號。就相當于Chain負責調用了處理每個Interceptor的調用和返回
  • 2.Service接口:我們不可能在Interceptor的intercept實現所有的邏輯,所以我們需要將攔截器中需要的操作封裝成一個個的服務,這樣能有效減小耦合。而每個Interceptor中持有的都只是Service接口,不會持有具體的實現。
    • 1.前面我們說了,在某些情況下需要將攔截器設置為是否可用,前兩個方法就是這一個用處
    • 2.in方法表示要將參數in放入這個服務之中,典型的例子就是將返回的數據放入內存緩存和硬盤緩存中
    • 3.out方法表示要從服務中返回數據,典型的例子就是從內存、硬盤、內存緩存中返回數據

3.攔截器鏈和攔截器的實現

我實現了四個攔截器:內存緩存攔截器-->新線程攔截器-->硬盤儲存攔截器-->網絡請求攔截器。和一個攔截器鏈,這個鏈中使用了上面四個攔截器。

public class RealInterceptorChain implements Interceptor.Chain {

public static String TAG="RealInterceptorChain";
private static final Object RECYCLER_LOCK = new Object();
private static int MAX_RECYCLED = 5;
private static RealInterceptorChain sFirstRecycledChain;
private static int sRecycledCount;

private RealInterceptorChain mNextRecycledChain;

private Request mRequest;
private final List<Interceptor> interceptors;
private int index=0;

private RealInterceptorChain(Request request) {
    mRequest=request;
    this.interceptors = DataEngine.mInterceptors;
}

@Override
public Object proceed() throws Exception {
    if (index >= interceptors.size()){
        throw new AssertionError();
    }
    Interceptor interceptor = interceptors.get(index);
    boolean isEnable=mRequest.getInterceptorIsEnable()[index];
    index++;
    return interceptor.intercept(this,isEnable);
}

@Override
public Request getRequest() {
    return mRequest;
}

public void recycle() {
    synchronized (RECYCLER_LOCK) {
        if (sRecycledCount < MAX_RECYCLED) {
            reset();
            sRecycledCount++;

            if (sFirstRecycledChain != null) {
                mNextRecycledChain = sFirstRecycledChain;
            }
            sFirstRecycledChain = this;
            FLog.v(TAG,"回收Chain  sRecycledCount:"+sRecycledCount);
        }
    }
}

public static RealInterceptorChain obtain(Request request) {
    synchronized (RECYCLER_LOCK) {
        if (sFirstRecycledChain != null) {
            RealInterceptorChain eventToReuse = sFirstRecycledChain;
            sFirstRecycledChain = eventToReuse.mNextRecycledChain;
            eventToReuse.mNextRecycledChain = null;
            eventToReuse.mRequest=request;
            sRecycledCount--;
            FLog.v(TAG,"從對象池中獲取Chain  sRecycledCount:"+sRecycledCount);
            return eventToReuse;
        }
    }
    FLog.v(TAG,"對象池已空,創建一個Chain  sRecycledCount:"+sRecycledCount);
    return new RealInterceptorChain(request);
}

private void reset() {
    mRequest = null;
    index=0;
}
}
  • 1.RealInterceptorChain:實現了前面說的Chain,有以下特點
    • 1.Request mRequest:該鏈的請求,用于所有的攔截器
    • 2.List<Interceptor> interceptors:攔截器鏈,存有所有攔截器
    • 3.int index:用于標記請求已經運行到了哪一個攔截器中
    • 4.其他的參數和Request類似,都是為構造對象池而設計
    • 5.proceed()方法:在請求開始的時候,這個方法會被調用。在獲取了攔截器1之后,將index++,會調用攔截器1的intercept方法,并傳入本Chain對象和該Reuqest中該攔截器是否起作用的標志isEnable。在攔截器1中會根據情況,適時調用本Chain對象的proceed()方法,這時又回到了這個方法,但是使用的攔截器變了。像這樣調用完了所有的攔截器之后會按層次返回并在相應的攔截器中對返回的參數進行處理。

@Immutable

public class MemoryCacheInterceptor implements Interceptor {

public static String TAG="MemoryCacheInterceptor";
private final Service memoryCacheService;

public MemoryCacheInterceptor(Service memoryCacheService) {
    this.memoryCacheService = memoryCacheService;
}

@Override
public Object intercept(final Chain chain, boolean enable) throws Exception {
    final Request request=chain.getRequest();
    Object response = null;
    boolean isEnable=enable&&getService().isEnabled();
    RequestFlag requestFlag= request.getRequestFlag();
    FLog.v(TAG,request.getRequestFlag().toString()+"請求進入");

    if (requestFlag.getRequestLevel()==RequestLevel.to_memory){
        //在這種情況下,如果不開啟內存緩存,那么就直接返回null
        if (!isEnable) response=null;
        switch (requestFlag.getRequestType()){
            case GET:
                //返回內存緩存中的結果
                response= memoryCacheService.out(request);
                break;
            case INSERT:
                //在更新內存緩存之后,返回更新后的結果
                response= memoryCacheService.in(request,null);
                break;
            case NONE:
                //在memory下,不會有這種請求
                response= null;
                break;
        }
    }else {
        boolean isNeedToNextService=true;
        switch (requestFlag.getRequestType()){
            case GET:
                if (isEnable){
                    //內存緩存服務可以使用,就使用他
                    response= memoryCacheService.out(request);
                    //如果從內存緩存中獲取的結果不為null,那么就不需要去下一個服務取數據
                    if (response!=null)isNeedToNextService=false;
                }
                //這里不用 break 可以直接在需要進入下一個服務的情況下,復用代碼。
            case INSERT:
                // 如果請求是GET,表示從內存中獲取的結果是null,所以需要進入下一個服務獲取數據
                // 如果請求是INSERT 由于這種請求肯定要經過下一個服務,所以不需要判斷本服務是否可用
                // 下一個服務開始要去本地或者網絡獲取數據了,所以返回的是 Observable
                if (isNeedToNextService)
                    response= ((Observable<?>)chain.proceed())
                            .map(new Func1<Object, Object>() {
                            @Override
                            public Object call(Object o) {
                                //如果回調后的結果不是null并且允許該數據被緩存(默認是允許的,除非在下一個服務返回的時候禁止了),就緩存這個結果
                                if (o != null && request.isCacheToMemory())try {
                                    memoryCacheService.in(request, o);
                                } catch (Exception e) {
                                    //此時是 內存緩存的存儲出了問題
                                    FLog.e(TAG, "內存緩存的存儲出了問題", e);
                                    return Response.getFailedResponse(e);
                                }
                                return o;
                            }
                        });
                break;
            case NONE:
                //這種請求直接進入下一個服務,下一個服務開始要去本地或者網絡獲取數據了,所以返回的是 Observable
                response= chain.proceed();
                break;
        }
    }
    FLog.v(TAG,request.getRequestFlag().toString()+"  "+TAG+"返回");
    return response;
}

@Override
public Service getService() {
    return memoryCacheService;
}

}

  • 2.MemoryCacheInterceptor是第一個攔截器,當調用RealInterceptorChain#proceed()的時候這個攔截器的intercept()會被調用。
    • 1.看代碼可知,整個MemoryCacheInterceptor都是使用Service來進行具體功能的實現,并沒有和具體的實現類耦合,這么以來以后進行拓展或者更改Service的實現就會比較方便。
    • 2.整個方法中在開頭創建了幾個local對象,request和response不用多說,isEnable是一個標記該攔截器是否奏效的flag。只有當Request中的flag和該Service的flag同時奏效,這個攔截器才會被奏效。其實也很好理解,Service的flag就相當于總開關,Request的flag就相當于臨時開關,只有總開關和臨時開關都開啟的時候這個Service才會被使用。
    • 3.RequestFlag是一個很重要的對象,我們前面說了這個類中包含了RequestType和RequestLevel,這些都會在接下來的分派中使用到。
    • 4.接下來會根據RequestFlag中的RequestLevel進行一個選擇,如果這里的RequestLevel是to_memory的話,那說明這個請求僅僅需要向內存中獲取數據,判斷完成了之后再根據RequestType進行插入或獲取數據。
    • 5.如果RequestFlag不是to_memory,那么這個請求就有進入下一個攔截器的可能。
      • 1.GET:如果不是to_memory,那么這個請求會先去內存中取數據,如果內存中沒有數據,就要進入其他線程去本地或者網絡取數據了
      • 2.INSERT:如果不是to_memory,又由于是插入,所以肯定需要將數據插入到本地或者服務器中,所以使用了RxJava的線程切換,返回的是一個Observable,在這個Observable的回調中,有些插入操作可能會返回一些數據,此時就要根據是否有返回數據和Request中的是否需要在內存中緩存數據來判斷是否需要進行內存緩存。Request#isCacheToMemory()這個參數可以在本地數據攔截器或者網絡攔截器中被修改,所以返回的是否需要內存緩存,就看具體情況了。
      • 3.NONE:這種請求是必須去網絡中進行驗證的,所以既不需要內存緩存也不需要在從內存中獲取數據。由于需要切換線程,所以返回的也是Observable。
    • 6.最后就是返回Object response,這個response可能是一般的數據類,也可能是Observable,所以在調用處需要進行判斷,這在后面會講解到

@Immutable

public class NewThreadInterceptor implements Interceptor {
public static String TAG="NewThreadInterceptor";

@Override
public Observable<Object> intercept(final Chain chain, boolean enable)  {
    return Observable.just(chain)
            .observeOn(Schedulers.io())
            .map(new Func1<Chain, Object>() {
                @Override
                public Object call(Chain chain) {
                    try {
                        return chain.proceed();
                    } catch (Exception e) {
                        //此時是本地存儲或者網絡請求出了問題
                        FLog.e(TAG,"本地存儲或者網絡請求出了問題",e);
                        return Response.getFailedResponse(e);
                    }
                }
            });
}

@Override
public Service getService() {
    return null;
}
}
  • 3.NewThreadInterceptor是第二個攔截器,用于適配Rxjava,會返回一個Observable,在線程切換了之后會調用Chain#proceed()觸發下一個攔截器的intercept()。

@Immutable

public class LocalDataInterceptor implements Interceptor {
public static String TAG="LocalDataInterceptor";
private final Service localDataService;

public LocalDataInterceptor(Service localDataService) {
    this.localDataService = localDataService;
}

@Override
public Object intercept(Chain chain,boolean enable) throws Exception {
    final Request request=chain.getRequest();
    Object response = null;
    boolean isEnable=enable&&getService().isEnabled();
    RequestFlag requestFlag= request.getRequestFlag();
    FLog.v(TAG,request.getRequestFlag().toString()+"請求進入");

    if (requestFlag.getRequestLevel()==RequestLevel.to_local){
        //在這種情況下,如果不開啟本地數據服務,那么就直接返回null
        if (!isEnable)return null;
        switch (requestFlag.getRequestType()){
            case GET:
                //返回本地數據中的結果
                response= localDataService.out(request);
                break;
            case INSERT:
                //在更新內存緩存之后,返回更新后的結果
                response= localDataService.in(request,null);
                break;
            case NONE:
                //根據傳入的信息和內存緩存中的信息,經過驗證操作后,驗證返回結果
                response= localDataService.in(request,null);
                break;
        }
    }else {
        boolean isNeedToNextService=true;
        switch (requestFlag.getRequestType()){
            case GET:
                if (isEnable) {
                    //本地存儲服務可以使用,就使用他
                    response = localDataService.out(request);
                    //如果從本地儲存中獲取的結果不為null,那么就不需要去下一個服務取數據
                    if (response != null) isNeedToNextService = false;
                    //這里不用 break 可以直接在需要進入下一個服務的情況下,復用代碼。
                }
            case INSERT:
                // 如果請求是GET,表示從本地獲取的結果是null,所以需要進入下一個服務獲取數據
                // 如果請求是INSERT 由于這種請求肯定要經過下一個服務,所以不需要判斷本服務是否可用
                if (isNeedToNextService){
                    response= chain.proceed();
                    //如果下一個服務取來的結果不是null,并且這個數據被允許存在本地(默認是允許的,除非在下一個服務返回的時候禁止了),,就把這個結果存起來
                    if (response!=null&&request.isSaveToLocal()) localDataService.in(request,response);
                }
                break;
            case NONE:
                //由于這種請求肯定要經過下一個服務,所以不需要判斷本服務是否可用,也不需要將返回的結果存在本地
                response= chain.proceed();
                break;
        }
    }
    FLog.v(TAG,request.getRequestFlag()+"  "+TAG+"返回");
    return response;
}

@Override
public Service getService() {
    return localDataService;
}
}
  • 3.第三個攔截器是LocalDataInterceptor,這個攔截器提供本地儲存服務,具體實現與請求分派方式和MemoryCacheInterceptor類似,不過也有幾個不同點
    • 1.在to_local的情況下NONE是有用處的。
    • 2.由于不需要像MemoryCacheInterceptor一樣切換線程,所以這里的調用都是同步返回,返回的也都是數據類。

@Immutable

public class NetworkInterceptor implements Interceptor{
public static String TAG="NetworkInterceptor";

private final Service mNetworkService;

public NetworkInterceptor(Service networkService) {
    this.mNetworkService = networkService;
}

@Override
public Object intercept(Chain chain,boolean enable) throws Exception {
    final Request request=chain.getRequest();
    boolean isEnable=enable&&getService().isEnabled();
    FLog.v(TAG,request.getRequestFlag().toString()+"請求進入");
    //如果網絡服務不可用,就返回null
    if (!isEnable)return null;
    Object response=mNetworkService.out(chain.getRequest());
    FLog.v(TAG,request.getRequestFlag().toString()+"  "+TAG+"返回");
    return response;
}

@Override
public Service getService() {
    return mNetworkService;
}
}
  • 4.第四個攔截器是NetworkInterceptor,這個就是網絡請求的攔截器了,由于網絡請求服務和本地儲存服務、內存服務不同,所以在網絡服務可用的情況下,只會調用Service#out(),而Service#in()不會被調用。

整個攔截器鏈的實現就是上面這樣,可以看出雖然我并沒有講解各個攔截器中的服務具體是怎么實現的,但是這并不影響整個攔截器鏈的邏輯。由于我們定義了Service這個抽象的接口,我們在攔截器鏈的實現過程中,并不需要去在意Service的具體邏輯,這就是將攔截器和服務解耦,而一旦解耦了,Service的實現類中無論如何變化,都影響不到整個攔截器鏈的框架。

由于字數太多:所以分成了兩篇:Android數據層架構的實現 下篇

不販賣焦慮,也不標題黨。分享一些這個世界上有意思的事情。題材包括且不限于:科幻、科學、科技、互聯網、程序員、計算機編程。下面是我的微信公眾號:世界上有意思的事,干貨多多等你來看。

世界上有意思的事

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,662評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,322評論 11 349
  • 這幾天西餐廳離職的員工還蠻多的,所以也算是有感而發,當然也算是完成新聞組這個月的任務,與本部門的諸君共勉。 我還記...
    pudding閱讀 781評論 0 2
  • 【作者】郭興平 【派別】文魁派 【導師】袁文魁 【導圖講解】 中心圖是帶有月餅字樣及圖像的圖片,直接點明中心主題。...
    郭興平閱讀 2,617評論 3 0