天天看點

《一個Android工程的從零開始》階段總結與修改6-BaseNetActivity

先扯兩句

前幾篇部落格把之前的BaseActivity做了拆解,分成了BaseActivity與BaseLayoutActivity,而BaseNetActivity确實也在之前做了一定的去耦合處理,雖然沒有發對應的部落格,但是如果有關注我github的朋友或許會發現,那就是封裝了一個NetUtils類,将原本Header的封裝,Retrofit的調用封裝全都拆解了出來,隻對應GET、POST請求暴露了兩個方法。不過目前的封裝實際上,還是針對的mvc架構。畢竟網絡請求還是沒有脫離BaseNetActivity與BaseNetFragment,無法達到進一步的去耦合,徹底擺脫Activity與Fragment的限制。于是查了一下MVP架構與MVVM架構。當然,我這種懶漢性格,不會去看什麼分析文檔了,那麼正式的純文字,看兩段我就睡給他看。是以就找了些demo類的:

1. JesseBraveMan的Android MVP架構搭建

2. 江南一點雨的 玩轉Android之MVVM開發模式實戰,炫酷的DataBinding!

3. SoloHo的Android,DataBinding的官方雙向綁定

大家按照上面的部落格直接敲代碼就能實作最基本的操作,餘下的部分就是适配自己的底層環境以及業務邏輯,這些内容我也會後續跟進學習,隻是目前《一個Android工程的從零開始》還是比較基礎的部分,甚至大學生之間拿來簡單搭建個畢業設計應該都可以,個人感覺說明的還算詳細了(除了部落格更新慢點。。。)。是以關于MVP和MVVM架構暫時就不內建進來了,目前如果大家使用過程中,真的遇到有需要從adapter或者是自定義View中進行網絡請求的,可以參考BaseNetActivity和BaseNetFragment自行封裝一個BaseNetAdapter或者BaseNetView。再簡單點,參考BaseNetActivity和BaseNetFragment直接給NetUtils傳遞參數即可(其實個人感覺MVP的BasePresenter與我的NetUtils也差不多)。

好了,閑言少叙,老規矩還是先上我的Git,然後開始正文吧。 MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)

正文

其實這部分,對于之前看過我部落格的朋友來說,其實沒有什麼新意,就像上面扯的一樣,隻是把大多數公共部分提出來而已,先說明一下我這裡的網絡架構是使用的Retrofit2,大家如果使用其他網絡架構的話,就辛苦自行替換一下喽。首先把在初級階段不那麼常用的部分拿到前面,那就是header:

private Map<String, String> headerParams;

/**
 * 初始化請求頭,具體情況根據需求設定
 */
public void initHeader() {
    headerParams = new HashMap<>();
    // 傳參方式為json
    headerParams.put("Content-Type", "application/json");
}
           

這就是傳說中的header了,是不是超簡單!!!好吧,我承認還需要調用,不過就是傳個參數喽,關于Retrofit Service的封裝,大家可以看一下我之前的部落格,

《一個Android工程的從零開始》-8、base(七) Retrofit的封裝,如果不想看,直接去Git找源碼就好,連結你知道的,就在上面。

如果背景需要其他的header參數,對應添加就好,都是鍵值對的方式,至于有沒有更複雜的header方式,我猜是有,不過反正至今沒遇到,那就管他去死。

當然,這裡呢目前這條header的意思是要傳的資料類型是json(隻針對POST請求,GET不受影響),需要說明一點的是,其實這裡設定json,伺服器收到的請求還不是json而是key—value形式(忘記了預設格式是不是formdata了),具體如何傳json,大家可以看一下我的這篇部落格——《一個Android工程的從零開始》階段總結與修改2-Retrofit 上傳JSON及尾址特殊字元轉譯問題,當然,也有過一個朋友,使用我的架構,去調取了“聚合資料”的接口,結果POST請求無法正常擷取資料,最後查證,是因為“聚合資料”的那個接口POST請求不支援JSON格式,隻能還原到原本的key-value格式,是以大家在使用架構的時候一定要具體問題具體分析,尤其是請求方式,最好能與背景約定好,不然麻煩也算不上,但至少要多很多操作。

好了跑題夠遠,該收回來了,上面的部分就是header的設定,對我而言,一般都會跟背景限制好header字段,然後統一傳參,對方用不上可以不取。不過萬一遇到一些嚴謹的公司,就要有好幾套不同的header,也禁止傳無用的資料,那就需要提供一個自定義header的入口,也比較好實作,相信能進入這種嚴謹公司的人完成這部分調整也不在話下,我就不班門弄斧,直接進行下一部分了。

/**
 * 初始化資料
 *
 * @param action 目前請求的尾址
 */
private Retrofit initBaseData(final String action) {
    // 監聽請求條件
    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.connectTimeout(, TimeUnit.SECONDS);
    builder.addInterceptor(new Interceptor() {
        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Logger.i("zzz", "request====" + action);
            Logger.i("zzz", "request====" + request.headers().toString());
            Logger.i("zzz", "request====" + request.toString());
            okhttp3.Response proceed = chain.proceed(request);
            Logger.i("zzz", "proceed====" + proceed.headers().toString());
            return proceed;
        }
    });

    Retrofit.Builder builder1 = new Retrofit.Builder()
            .client(builder.build())                                    // 配置監聽請求
            .addConverterFactory(GsonConverterFactory.create())         // 請求結果轉換(目前為GSON)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create()); // 請求接受工具(目前為RxJava2)
    builder1.baseUrl(BuildConfig.BASE_URL + action.substring(, action.lastIndexOf("/") + ));

    return builder1.build();
}
           

這裡主要是Retrofit的一些配置,通過注釋,大家也能了解到具體都是做什麼功能的,還是不加以贅述了。隻是說一點,那就是OkHttpClient的部分,注釋是“監聽請求條件”,也就是将我們向平台發送的資料做了一下回顯,這樣開發過程中,一旦出現什麼錯誤,也友善查找。說的猥瑣點,至少出問題了能做個判斷,自己沒錯的時候也友善甩鍋不是!

但是項目正式上線的時候,一定要把這個部分幹掉,我是因為release版和debug版通過自己封裝的Logger做了處理,大家如果使用系統的Log,一定要想着這部分,不然打開Android Studio運作一下APP,我們請求的資料就都暴露在對方眼前了。再加上後面傳回資料的輸出,那我們的項目就完完全全成透明的了。雖然說即便如此,他人使用代理一樣可以抓包,但是畢竟相對于前者來說,會玩代理的還是少數,老闆來問,我們也隻能說非戰之過了。

/**
 * Get請求
 *
 * @param action   請求接口的尾址
 * @param params   索要傳遞的參數
 * @param observer 求情觀察者
 */
public void get(final String action, Map<String, String> params, Observer<ResponseBody> observer) {
    RetrofitGetService getService = initBaseData(action).create(RetrofitGetService.class);
    if (params == null) {
        params = new HashMap<>();
    }

    if (null == headerParams){
        headerParams = new HashMap<>();
    }

    Logger.i("zzz", "request====" + new JSONObject(params));

    getService.getResult(action.substring(action.lastIndexOf("/") + ), headerParams, params)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(observer);
}

/**
 * Post請求
 *
 * @param action   請求接口的尾址
 * @param observer 求情觀察者
 */
public void post(final String action, String json, Observer<ResponseBody> observer) {
    RetrofitPostJsonService jsonService = initBaseData(action).create(RetrofitPostJsonService.class);
    RequestBody requestBody =
            RequestBody.create(MediaType.parse("application/json; charset=utf-8"),
                    json);

    Logger.i("zzz", "request====" + json);

    jsonService.postResult(action.substring(action.lastIndexOf("/") + ), requestBody)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(observer);
}
           

上面兩個方法分别是對GET的封裝以及對POST(參數格式JSON)的封裝,畢竟隻是個demo,是以這部分我GET中使用的是傳header的形式,而POST中使用的是不需要傳header的形式,大家在使用的時候,針對應業務需求統一一下即可。以上也就是NetUtils中的全部内容,完整代碼如下:

public class NetUtils {
    private Map<String, String> headerParams;

    /**
     * 初始化請求頭,具體情況根據需求設定
     */
    public void initHeader() {
        headerParams = new HashMap<>();
        // 傳參方式為json
        headerParams.put("Content-Type", "application/json");
    }

    /**
     * 初始化資料
     *
     * @param action 目前請求的尾址
     */
    private Retrofit initBaseData(final String action) {
        // 監聽請求條件
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(, TimeUnit.SECONDS);
        builder.addInterceptor(new Interceptor() {
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Logger.i("zzz", "request====" + action);
                Logger.i("zzz", "request====" + request.headers().toString());
                Logger.i("zzz", "request====" + request.toString());
                okhttp3.Response proceed = chain.proceed(request);
                Logger.i("zzz", "proceed====" + proceed.headers().toString());
                return proceed;
            }
        });

        Retrofit.Builder builder1 = new Retrofit.Builder()
                .client(builder.build())                                    // 配置監聽請求
                .addConverterFactory(GsonConverterFactory.create())         // 請求結果轉換(目前為GSON)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()); // 請求接受工具(目前為RxJava2)
        builder1.baseUrl(BuildConfig.BASE_URL + action.substring(, action.lastIndexOf("/") + ));

        return builder1.build();
    }

    /**
     * Get請求
     *
     * @param action   請求接口的尾址
     * @param params   索要傳遞的參數
     * @param observer 求情觀察者
     */
    public void get(final String action, Map<String, String> params, Observer<ResponseBody> observer) {
        RetrofitGetService getService = initBaseData(action).create(RetrofitGetService.class);
        if (params == null) {
            params = new HashMap<>();
        }

        if (null == headerParams){
            headerParams = new HashMap<>();
        }

        Logger.i("zzz", "request====" + new JSONObject(params));

        getService.getResult(action.substring(action.lastIndexOf("/") + ), headerParams, params)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(observer);
    }

    /**
     * Post請求
     *
     * @param action   請求接口的尾址
     * @param observer 求情觀察者
     */
    public void post(final String action, String json, Observer<ResponseBody> observer) {
        RetrofitPostJsonService jsonService = initBaseData(action).create(RetrofitPostJsonService.class);
        RequestBody requestBody =
                RequestBody.create(MediaType.parse("application/json; charset=utf-8"),
                        json);

        Logger.i("zzz", "request====" + json);

        jsonService.postResult(action.substring(action.lastIndexOf("/") + ), requestBody)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(observer);
    }
}
           

而這部分搞定之後,下面就要回到我們的BaseNetActivity中了,其實BaseNetActivity也是相對簡單的,因為我們已經有了NetUtils中的get方法以及post方法,其他的隻需要你對應業務做一下處理即可。是以第一步是什麼呢?恭喜你,答對了,就是在BaseNetActivity中建立NetUtils對象!

/**
 * 加載提示框
 */
private CustomProgressDialog customProgressDialog;

private NetUtils netUtils;
protected Map<String, String> params;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    customProgressDialog = new CustomProgressDialog(activity, R.style.progress_dialog_loading, "玩命加載中。。。");
    netUtils = new NetUtils();
    netUtils.initHeader();
}
           

這裡調用了initHeader方法,不是必須的,還是一切看業務邏輯,還記得小時候考試那個抄别人卷子,最後連對方名字都抄上的人嗎?沒錯,那就是我。除此之外,這裡還建立了一個進度加載提示框,相關内容大家還是到源碼中尋找答案吧。至于這裡使用的params,我使用的是Map

/**
 * Get請求
 *
 * @param action     請求接口的尾址
 * @param clazz      要轉換的Bean類型(需繼承BaseBean)
 * @param showDialog 顯示加載進度條
 */
protected <T extends BaseBean> void get(final String action, Class<T> clazz, boolean showDialog) {
    if (!isNetworkAvailable()) {
        toast("網絡異常,請檢查網絡是否連接配接");
        error(action, new Exception("網絡異常,請檢查網絡是否連接配接"));
        return;
    }
    if (showDialog) {
        showLoadDialog();
    }
    if (params == null) {
        params = new HashMap<>();
    }
    netUtils.get(action, params, new MyObserver<>(action, clazz));
    params = null;
}

private class MyObserver<T extends BaseBean> implements Observer<ResponseBody> {

    private Class<T> clazz;
    private String action;

    MyObserver(String action, Class<T> clazz) {
        this.clazz = clazz;
        this.action = action;
    }

    @Override
    public void onSubscribe(@NonNull Disposable d) {

    }

    @Override
    public void onNext(@NonNull ResponseBody responseBody) {
        hideLoadDialog();
        try {
            String responseString = responseBody.string();
            Logger.i("responseString", action + "********** responseString get  " + responseString);
            success(action, (T) new Gson().fromJson(responseString, clazz));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onError(@NonNull Throwable e) {
        Logger.i("responseString", "responseString get  " + e.toString());
        error(action, e);
    }

    @Override
    public void onComplete() {
        params = null;
    }
}
           

這裡是以封裝get請求與為例,傳入的參數分别為網絡請求尾址、參數接收類、以及是否顯示提示框。這三類個人認為是最基礎的部分了,大家使用過程中可以根據業務調整參數,當然,這部分也是在封裝接口,是以調整的時候,最好盡可能相容多個請求,而不要一個接口對應一個方法,那樣的就不如直接請求NetUtilsL ,BaseNetActivity這一層的封裝就沒有用了。

而下面封裝的MyObserver完全是為了結果統一擷取,而不需要每封裝一個方法,就定義一次結果取值的部分,那樣過于繁瑣。當然,這樣自然不可能就完全兼顧所有請求,是以特殊情況,我們可以提供一個标志位,去做對應處理:

/**
 * Get請求
 *
 * @param action     請求接口的尾址
 * @param showDialog 顯示加載進度條
 */
protected <T extends BaseBean> void getImage(final String action, boolean showDialog) {
    if (!isNetworkAvailable()) {
        toast("網絡異常,請檢查網絡是否連接配接");
        error(action, new Exception("網絡異常,請檢查網絡是否連接配接"));
        return;
    }
    if (showDialog) {
        showLoadDialog();
    }
    if (params == null) {
        params = new HashMap<>();
    }
    netUtils.get(action, params, new MyObserver<>(action, ));
    params = null;
}

private class MyObserver<T extends BaseBean> implements Observer<ResponseBody> {
    private Class<T> clazz;
    private String action;
    /**
     * 傳回結果狀态:0、正常Bean;1、Bitmap
     */
    private int resultStatus = ;
    MyObserver(String action, Class<T> clazz) {
        this.clazz = clazz;
        this.action = action;
    }
    MyObserver(String action, int resultStatus) {
        this.action = action;
        this.resultStatus = resultStatus;
    }
    @Override
    public void onSubscribe(@NonNull Disposable d) {
    }
    @Override
    public void onNext(@NonNull ResponseBody responseBody) {
        hideLoadDialog();
        try {
            switch (resultStatus) {
                case :
                    String responseString = responseBody.string();
                    Logger.i("responseString", action + "********** responseString get  " + responseString);
                    success(action, (T) new Gson().fromJson(responseString, clazz));
                    break;
                case :
                    success(action, BitmapFactory.decodeStream(responseBody.byteStream()));
                    Logger.i("responseString", action + "********** 圖檔擷取成功 ");
                    break;
                default:
                    break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void onError(@NonNull Throwable e) {
        Logger.i("responseString", "responseString get  " + e.toString());
        error(action, e);
    }
    @Override
    public void onComplete() {
        params = null;
    }
}
           

都說需求(規則)是死的,人是活的,可有的時候活人改活規則,真容易把其他活人逼死。。。之是以有這個感慨完全是因為上面這個封裝,如今開發APP,圖檔加載的部分肯定是繞不過去的,什麼頭像啊、産品樣例啊、朋友圈曬娃曬狗曬美食的檢視啊,我寫過最奇葩的是一個自定義日記本封面尺寸是 350dp*100dp,WTF(這個感歎來自于截圖)!!!不過一般而言都是擷取的圖檔Url,然後使用各種圖檔加載控價加載即可。可一直這樣人怎麼能進步啊,這不就需要擷取圖檔位元組流了嗎?

設計之初,想過直接取responseBody.string(),沒有值再去取位元組流,就在為自己的機智鼓掌時,發現這根本行不通,因為Observer的onNext中,responseBody.string()連續調用連詞,第二次都無法取得資料。無奈之下,就隻有建立一個标志位resultStatus,判斷究竟是擷取的String還是Bitmap。當然,如果同樣适用這個架構的情況下,遇到其他奇葩需求,也可以繼續擴充。

需要說明的是,一般而言,我們的請求還是擷取文本資料居多,是以這裡success的抽象方法還是傳回的“String action, BaseBean baseBean”,而傳回bitmap的沒有做抽象處理,需要使用的時候重寫一下就好。BaseNetAcrivity完整代碼如下,:

public abstract class BaseNetActivity extends BaseLayoutActivity {

    /**
     * 加載提示框
     */
    private CustomProgressDialog customProgressDialog;

    private NetUtils netUtils;

    protected Map<String, String> params;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        customProgressDialog = new CustomProgressDialog(activity, R.style.progress_dialog_loading, "玩命加載中。。。");
        netUtils = new NetUtils();
        netUtils.initHeader();
    }

    protected void refreshHeader() {
        netUtils.initHeader();
    }

    /**
     * Get請求
     *
     * @param action     請求接口的尾址
     * @param clazz      要轉換的Bean類型(需繼承BaseBean)
     * @param showDialog 顯示加載進度條
     */
    protected <T extends BaseBean> void get(final String action, Class<T> clazz, boolean showDialog) {
        if (!isNetworkAvailable()) {
            toast("網絡異常,請檢查網絡是否連接配接");
            error(action, new Exception("網絡異常,請檢查網絡是否連接配接"));
            return;
        }
        if (showDialog) {
            showLoadDialog();
        }
        if (params == null) {
            params = new HashMap<>();
        }
        netUtils.get(action, params, new MyObserver<>(action, clazz));
        params = null;
    }

    /**
     * Get請求
     *
     * @param action     請求接口的尾址
     * @param showDialog 顯示加載進度條
     */
    protected <T extends BaseBean> void getImage(final String action, boolean showDialog) {
        if (!isNetworkAvailable()) {
            toast("網絡異常,請檢查網絡是否連接配接");
            error(action, new Exception("網絡異常,請檢查網絡是否連接配接"));
            return;
        }
        if (showDialog) {
            showLoadDialog();
        }
        if (params == null) {
            params = new HashMap<>();
        }
        netUtils.get(action, params, new MyObserver<>(action, ));
        params = null;
    }

    /**
     * Post請求
     *
     * @param action     請求接口的尾址
     * @param clazz      要轉換的Bean類型(需繼承BaseBean)
     * @param showDialog 顯示加載進度條
     */
    protected <T extends BaseBean> void post(final String action, Class<T> clazz, boolean showDialog) {
        if (!isNetworkAvailable()) {
            toast("網絡異常,請檢查網絡是否連接配接");
            error(action, new Exception("網絡異常,請檢查網絡是否連接配接"));
            return;
        }
        if (showDialog) {
            showLoadDialog();
        }
        if (params == null) {
            params = new HashMap<>();
        }
        netUtils.post(action, String.valueOf(new JSONObject(params)), new MyObserver<>(action, clazz));
        params = null;
    }

    /**
     * Post請求
     *
     * @param action     請求接口的尾址
     * @param clazz      要轉換的Bean類型(需繼承BaseBean)
     * @param showDialog 顯示加載進度條
     */
    protected <T extends BaseBean> void post(final String action, String json, final Class<T> clazz, boolean showDialog) {
        if (!isNetworkAvailable()) {
            toast("網絡異常,請檢查網絡是否連接配接");
            error(action, new Exception("網絡異常,請檢查網絡是否連接配接"));
            return;
        }
        if (showDialog) {
            showLoadDialog();
        }
        netUtils.post(action, json, new MyObserver<>(action, clazz));
    }

    /**
     * 通路成功回調抽象方法
     *
     * @param action   網絡通路尾址
     * @param baseBean 傳回的資料Bean
     */
    protected abstract void success(String action, BaseBean baseBean);

    /**
     * 通路成功回調方法
     *
     * @param action 網絡通路尾址
     * @param bitmap 擷取的Bitmap
     */
    protected void success(String action, Bitmap bitmap) {
    }

    protected abstract void error(String action, Throwable e);

    /**
     * 顯示加載提示框
     */
    private void showLoadDialog() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                customProgressDialog.show();
            }
        });
    }

    /**
     * 隐藏加載提示框
     */
    private void hideLoadDialog() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (customProgressDialog != null && customProgressDialog.isShowing()) {
                    customProgressDialog.dismiss();
                }
            }
        });
    }

    private class MyObserver<T extends BaseBean> implements Observer<ResponseBody> {

        private Class<T> clazz;
        private String action;
        /**
         * 傳回結果狀态:0、正常Bean;1、Bitmap
         */
        private int resultStatus = ;

        MyObserver(String action, Class<T> clazz) {
            this.clazz = clazz;
            this.action = action;
        }

        MyObserver(String action, int resultStatus) {
            this.action = action;
            this.resultStatus = resultStatus;
        }

        @Override
        public void onSubscribe(@NonNull Disposable d) {

        }

        @Override
        public void onNext(@NonNull ResponseBody responseBody) {
            hideLoadDialog();
            try {
                switch (resultStatus) {
                    case :
                        String responseString = responseBody.string();
                        Logger.i("responseString", action + "********** responseString get  " + responseString);
                        success(action, (T) new Gson().fromJson(responseString, clazz));
                        break;

                    case :
                        success(action, BitmapFactory.decodeStream(responseBody.byteStream()));
                        Logger.i("responseString", action + "********** 圖檔擷取成功 ");
                        break;

                    default:
                        break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onError(@NonNull Throwable e) {
            Logger.i("responseString", "responseString get  " + e.toString());
            error(action, e);
        }

        @Override
        public void onComplete() {
            params = null;
        }
    }
}
           

附錄

《一個Android工程的從零開始》- 目錄

繼續閱讀