【Android架構(gòu)】基于MVP模式的Retrofit2+RXjava封裝之?dāng)?shù)據(jù)預(yù)處理(六)

前言

mvp框架也用了相當(dāng)長的時(shí)間了,一般讓人比較糾結(jié)的就是后臺(tái)數(shù)據(jù)的處理問題消约。大多數(shù)的公司由于代碼的不規(guī)范肠鲫、經(jīng)手人員太多等等原因,后臺(tái)的代碼相當(dāng)混亂或粮,接口返回的數(shù)據(jù)格式也五花八門导饲,當(dāng)然,如果你能直接讓后臺(tái)大哥改代碼的話氯材,就另當(dāng)別論渣锦,大多數(shù)情況還是要Android端來背鍋。這里氢哮,我們就聊聊這個(gè)袋毙。

一般套路

我們會(huì)直接復(fù)制接口返回的json,然后用插件轉(zhuǎn)換為實(shí)體類(國際慣例命浴,不貼get和set)

public class ShareModel {
 
    private int status;
    private String msg;
    private List<DataBean> data;
    public static class DataBean {
        private String id;
        private String wshare_name;
        private String wshare_head;
        private String wshare_content;
  }
}

進(jìn)階套路

后臺(tái)返回的數(shù)據(jù)格式如下:

{
    "status": 1,
    "msg": "請(qǐng)求成功",
    "data": []
}

我們會(huì)定義一個(gè)BaseModel(國際慣例娄猫,不貼get和set)

public class BaseModel<T> implements Serializable {
    private int status;
    private String msg;
    private T data;
}

如果datalist的話贱除,還會(huì)定義個(gè)BaseListModel生闲,只是其中的dataList<T>而已。
然后月幌,在ApiServer中定義接口

 
    @FormUrlEncoded
    @POST("/mapi/index.php?ctl=user&act=userbaseinfo")
    Observable<BaseModel<UserModel>> getUserInfo(@FieldMap Map<String, String> params);

presenter中使用

    /**
     * 獲取用戶詳情
     *
     * @param params
     */
    public void getUserInfo(Map<String, String> params) {
        addDisposable(apiServer.getUserInfo(params), new BaseObserver<BaseModel<UserModel>>(baseView) {

            @Override
            public void onSuccess(BaseModel<UserModel> o) {
                baseView.onGetUserInfoSucc(o.getData());

            }

            @Override
            public void onError(String msg) {
                baseView.showError(msg);
            }
        });

    }

然后回調(diào)到activity或者fragment中處理碍讯,這部分就不詳細(xì)說了,可以看看之前的文章扯躺。

這樣看似沒有問題捉兴,但是如果后臺(tái)某個(gè)接口返回的數(shù)據(jù)的格式如下,

{
    "status": 1,
    "error": "請(qǐng)求成功",
    "data": []
}

有人說了录语,在BaseModelBaseListModel再加一個(gè)error字段不就好了倍啥?

如果數(shù)據(jù)是這樣呢?

{
    "code": 1,
    "error": "請(qǐng)求成功",
    "data": []
}

可能這張圖能表達(dá)你現(xiàn)在的心情


image.png

終極套路

雖然生活如此艱難澎埠,但是問題還是要解決的虽缕。

我們可以回想一下,http請(qǐng)求返回的是對(duì)象是ResponseBody蒲稳,它是怎么轉(zhuǎn)換為我們的實(shí)體類呢氮趋?
主要代碼在這里

retrofit.addConverterFactory(GsonConverterFactory.create())

我們跟進(jìn)去看看

public final class GsonConverterFactory extends Converter.Factory {
  /**
   * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
   * decoding from JSON (when no charset is specified by a header) will use UTF-8.
   */
  public static GsonConverterFactory create() {
    return create(new Gson());
  }

  /**
   * Create an instance using {@code gson} for conversion. Encoding to JSON and
   * decoding from JSON (when no charset is specified by a header) will use UTF-8.
   */
  @SuppressWarnings("ConstantConditions") // Guarding public API nullability.
  public static GsonConverterFactory create(Gson gson) {
    if (gson == null) throw new NullPointerException("gson == null");
    return new GsonConverterFactory(gson);
  }

  private final Gson gson;

  private GsonConverterFactory(Gson gson) {
    this.gson = gson;
  }

  @Override
  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
      Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonResponseBodyConverter<>(gson, adapter);
  }

  @Override
  public Converter<?, RequestBody> requestBodyConverter(Type type,
      Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonRequestBodyConverter<>(gson, adapter);
  }
}

可以看到伍派,主要邏輯是在GsonResponseBodyConverter里面

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
  private final Gson gson;
  private final TypeAdapter<T> adapter;

  GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }

  @Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      T result = adapter.read(jsonReader);
      if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
        throw new JsonIOException("JSON document was not fully consumed.");
      }
      return result;
    } finally {
      value.close();
    }
  }
}

可以看到,先是拿到字節(jié)流剩胁,然后調(diào)用TypeAdapterread方法诉植,轉(zhuǎn)換為我們的實(shí)體類,這個(gè)原理我們先不深究昵观,后面有時(shí)間在講晾腔。這里我們能不能做文章呢,答案是可以啊犬。

首先建车,這幾個(gè)類都是final修飾的,不能被繼承椒惨,不過沒事缤至,我們可以復(fù)制這幾個(gè)類的代碼,然后改個(gè)名字


image.png

其中康谆,BaseConverterFactoryBaseRequestBodyConverter與源碼一致领斥,只需要修改類名即可。重點(diǎn)在BaseResponseBodyConverter

public class BaseResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final Gson gson;
    private final TypeAdapter<T> adapter;

    BaseResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        this.gson = gson;
        this.adapter = adapter;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {

        String jsonString = value.string();
        try {
            JSONObject object = new JSONObject(jsonString);

            int code = object.getInt("code");
            if (code != 1) {
                String msg = object.getString("msg");
                if (TextUtils.isEmpty(msg)) {
                    msg = object.getString("error");
                }
                //異常處理
                throw new BaseException(msg, code);
            }

            return adapter.fromJson(object.getString("data"));

        } catch (JSONException e) {
            e.printStackTrace();
            //數(shù)據(jù)解析異常
            throw new BaseException(BaseException.PARSE_ERROR_MSG, BaseException.PARSE_ERROR);
        } finally {
            value.close();
        }
    }
}

判斷的代碼可以自己根據(jù)項(xiàng)目需要沃暗,自行添加

重要的事說三遍月洛,這里的判斷邏輯要根據(jù)實(shí)際情況寫!這里的判斷邏輯要根據(jù)實(shí)際情況寫孽锥!這里的判斷邏輯要根據(jù)實(shí)際情況寫嚼黔!

最近好幾個(gè)人問我,為啥后臺(tái)返回的json都拿到了惜辑,還走的onError 唬涧,請(qǐng)檢查這里的代碼!
BaseException

public class BaseException extends IOException {

    /**
     * 解析數(shù)據(jù)失敗
     */
    public static final int PARSE_ERROR = 1001;
    public static final String PARSE_ERROR_MSG = "解析數(shù)據(jù)失敗";

    /**
     * 網(wǎng)絡(luò)問題
     */
    public static final int BAD_NETWORK = 1002;
    public static final String BAD_NETWORK_MSG = "網(wǎng)絡(luò)問題";
    /**
     * 連接錯(cuò)誤
     */
    public static final int CONNECT_ERROR = 1003;
    public static final String CONNECT_ERROR_MSG = "連接錯(cuò)誤";
    /**
     * 連接超時(shí)
     */
    public static final int CONNECT_TIMEOUT = 1004;
    public static final String CONNECT_TIMEOUT_MSG = "連接超時(shí)";
    /**
     * 未知錯(cuò)誤
     */
    public static final int OTHER = 1005;
    public static final String OTHER_MSG = "未知錯(cuò)誤";


    private String errorMsg;
    private int errorCode;


    public String getErrorMsg() {
        return errorMsg;
    }

    public int getErrorCode() {
        return errorCode;
    }

    public BaseException(String errorMsg, Throwable cause) {
        super(errorMsg, cause);
        this.errorMsg = errorMsg;
    }

    public BaseException(String message, Throwable cause, int errorCode) {
        super(message, cause);
        this.errorCode = errorCode;
        this.errorMsg = message;
    }

    public BaseException(String message, int errorCode) {
        this.errorCode = errorCode;
        this.errorMsg = message;
    }
}

修改BaseObserver代碼盛撑,onNext中只處理成功回調(diào)碎节,onError中處理各種異常

public abstract class BaseObserver<T> extends DisposableObserver<T> {

    protected BaseView view;

    private boolean isShowDialog;


    public BaseObserver(BaseView view) {
        this.view = view;
    }

    public BaseObserver(BaseView view, boolean isShowDialog) {
        this.view = view;
        this.isShowDialog = isShowDialog;
    }

    @Override
    protected void onStart() {
        if (view != null && isShowDialog) {
            view.showLoading();
        }
    }

    @Override
    public void onNext(T o) {
        onSuccess(o);
    }

    @Override
    public void onError(Throwable e) {
        if (view != null && isShowDialog) {
            view.hideLoading();
        }
        BaseException be = null;

        if (e != null) {

            if (e instanceof BaseException) {
                be = (BaseException) e;

                //回調(diào)到view層 處理 或者根據(jù)項(xiàng)目情況處理
                if (view != null) {
                    view.onErrorCode(new BaseModel(be.getErrorCode(), be.getErrorMsg()));
                } else {
                    onError(be.getErrorMsg());
                }

            } else {
                if (e instanceof HttpException) {
                    //   HTTP錯(cuò)誤
                    be = new BaseException(BaseException.BAD_NETWORK_MSG, e, BaseException.BAD_NETWORK);
                } else if (e instanceof ConnectException
                        || e instanceof UnknownHostException) {
                    //   連接錯(cuò)誤
                    be = new BaseException(BaseException.CONNECT_ERROR_MSG, e, BaseException.CONNECT_ERROR);
                } else if (e instanceof InterruptedIOException) {
                    //  連接超時(shí)
                    be = new BaseException(BaseException.CONNECT_TIMEOUT_MSG, e, BaseException.CONNECT_TIMEOUT);
                } else if (e instanceof JsonParseException
                        || e instanceof JSONException
                        || e instanceof ParseException) {
                    //  解析錯(cuò)誤
                    be = new BaseException(BaseException.PARSE_ERROR_MSG, e, BaseException.PARSE_ERROR);
                } else {
                    be = new BaseException(BaseException.OTHER_MSG, e, BaseException.OTHER);
                }
            }
        } else {
            be = new BaseException(BaseException.OTHER_MSG, e, BaseException.OTHER);
        }

        onError(be.getErrorMsg());

    }

    @Override
    public void onComplete() {
        if (view != null && isShowDialog) {
            view.hideLoading();
        }

    }


    public abstract void onSuccess(T o);

    public abstract void onError(String msg);


}

ApiRetrofit中添加我們自定義的ConverterFactory

 .addConverterFactory(BaseConverterFactory.create())

這樣的話,ApiServer便可以這樣定義了

 @FormUrlEncoded
    @POST("/mapi/index.php?ctl=user&act=userbaseinfo")
    Observable<UserModel> getUserInfo(@FieldMap Map<String, String> params);

相應(yīng)的抵卫,presenter可以這樣寫

  public void getUserInfo(Map<String, String> params) {
        addDisposable(apiServer.getUserInfo(params), new BaseObserver<UserModel>(baseView) {

            @Override
            public void onSuccess(UserModel o) {
                baseView.onGetUserInfoSucc(o);

            }

            @Override
            public void onError(String msg) {
                baseView.showError(msg);
            }
        });

    }

是不是精簡了許多狮荔,快來試試吧

參考
RxJava2 + Retrofit2 完全指南 之 統(tǒng)一狀態(tài)碼/Exception處理

最后,獻(xiàn)上源碼 Github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末介粘,一起剝皮案震驚了整個(gè)濱河市殖氏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌姻采,老刑警劉巖雅采,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡总滩,警方通過查閱死者的電腦和手機(jī)纲堵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闰渔,“玉大人席函,你說我怎么就攤上這事「越В” “怎么了茂附?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長督弓。 經(jīng)常有香客問我营曼,道長,這世上最難降的妖魔是什么愚隧? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任蒂阱,我火速辦了婚禮,結(jié)果婚禮上狂塘,老公的妹妹穿的比我還像新娘录煤。我一直安慰自己,他們只是感情好荞胡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布妈踊。 她就那樣靜靜地躺著,像睡著了一般泪漂。 火紅的嫁衣襯著肌膚如雪廊营。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天萝勤,我揣著相機(jī)與錄音露筒,去河邊找鬼。 笑死纵刘,一個(gè)胖子當(dāng)著我的面吹牛邀窃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播假哎,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鞍历!你這毒婦竟也來了舵抹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤劣砍,失蹤者是張志新(化名)和其女友劉穎惧蛹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡香嗓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年迅腔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靠娱。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沧烈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出像云,到底是詐尸還是另有隱情锌雀,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布迅诬,位于F島的核電站腋逆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏侈贷。R本人自食惡果不足惜惩歉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望俏蛮。 院中可真熱鬧柬泽,春花似錦、人聲如沸嫁蛇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽睬棚。三九已至第煮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抑党,已是汗流浹背包警。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留底靠,地道東北人害晦。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像暑中,于是被迫代替她去往敵國和親壹瘟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353