1. 程式人生 > >最新Retrofit + RxJava + MVP

最新Retrofit + RxJava + MVP

此處搭建的框架是目前最新版本,專案今天剛搭建好,(^__^) 嘻嘻……。
先擼上包:
compile ‘com.jakewharton:butterknife:8.6.0’
compile ‘com.jakewharton:butterknife-compiler:8.6.0’
compile ‘io.reactivex.rxjava2:rxjava:2.1.0’
compile ‘io.reactivex.rxjava2:rxandroid:2.0.1’
compile ‘com.squareup.retrofit2:converter-gson:2.3.0’
compile ‘com.squareup.retrofit2:retrofit:2.3.0’
compile ‘com.google.code.gson:gson:2.8.0’
compile ‘com.squareup.retrofit2:adapter-rxjava2:2.3.0’

本人還是挺嫌棄Gson的,一定會有人說,嫌棄還用,我勒個去,我想支援下國產用fastjson,丫的,retrofit2.X沒給出支援包,不想使用其它第三方包,只能等有空的時候自己摸索一個出來了,更高效的LoganSquare也不支援,無語了,那就考慮下jackson,發現jackson的包1M多,算了,jackson洗洗睡吧,無奈之下選擇了gson,肯定會有人想,為啥主流的json解析工具gson這麼受嫌棄,gson在解析的效率上對比其它幾個第三方,還是明顯偏低的。

廢話不多說,直接擼程式碼,因為寫了很多詳細的註釋,未來還會寫幾篇帖子專門分析,此處就不多做介紹。

先看下目錄結構圖

結構

結構2

除了刪除test檔案和刪了baseUrl裡的地址外,其它全部上傳到我的github了,最後會附上地址。

BaseActivity:

/**
 * Created by Zero on 2017/5/25.
 */
public abstract class BaseActivity<Pre extends BasePresenter> extends AppCompatActivity implements OnClickListener {

    private static final String DIALOG_LOADING = "DialogLoading";
    private
boolean mVisible; private LoadingDialogFragment waitDialog = null; protected Pre presenter; protected final Handler mHandler = new MyHandler(this); private BroadcastReceiver receiver; private IntentFilter filter; private class MyHandler extends Handler { private final WeakReference<BaseActivity> mActivity; /** * 因為內部類會隱式強引用當前類,採用弱引用,避免長生命週期導致記憶體洩漏 * * @param activity */ private MyHandler(BaseActivity activity) { mActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { if (mActivity.get() != null) { requestOver(msg); } } } @Override protected final void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().setNavigationBarColor(Color.BLACK); } if (initLayout() != 0) { /** * 設定佈局,其實很多view註解框架都可以對layout抓取到,但還是習慣這樣寫,^_^ */ setContentView(initLayout()); ButterKnife.bind(this); } try { if (getPsClass() != null) { if (getPsClass().newInstance() instanceof BasePresenter) { /** * presenter例項化,new和newInstance()不清晰,自己百度 */ presenter = (Pre) getPsClass().newInstance(); /** * 把一些必要的資料和presenter傳過去 */ presenter.initBaseData(this, mHandler, getIView(), getIntent()); } else { throw new RuntimeException("必須繼承BasePresenter"); } } } catch (InstantiationException e) { /** * 不能newInstance()導致的錯誤 */ e.printStackTrace(); } catch (IllegalAccessException e) { /** * 許可權不足,主要是構造方法使用了private */ e.printStackTrace(); } initData(); initViewAndListen(); } /** * 傳入需要過濾的action不定引數 * * @param filterActions */ protected void registerReceiver(@NonNull String... filterActions) { filter = filter == null ? new IntentFilter() : filter; for (String action : filterActions) { filter.addAction(action); } registerReceiver(filter); } /** * 傳入filter,註冊廣播 * * @param filter */ protected void registerReceiver(@NonNull IntentFilter filter) { // TODO Auto-generated method stub receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { executeReceiver(context, intent); } }; LocalBroadcastManager.getInstance(this).registerReceiver( receiver, filter); } /** * 接收到廣播 * * @param context * @param intent */ protected void executeReceiver(Context context, Intent intent) { } /** * setontentview() * * @return */ abstract protected int initLayout(); /** * 使用比如ButterKnife可以不使用 */ abstract protected void initViewAndListen(); /** * 初始化簡單資料,比如傳過來的title */ abstract protected void initData(); /** * 不用多個類實現OnClickListener * * @param v */ abstract protected void onclick(View v); /** * @return presenter, 此處不能使用返回Pre型別,newInstance()方法是class下的,Pre不能使用newInstance()例項化 */ abstract protected Class getPsClass(); /** * 介面回撥 * * @return */ abstract protected BaseInterface getIView(); /** * 把傳送到view層的message傳遞到presenter層處理,因為採用了rxjava和retrofit, * 很多view不在使用handler傳送資料,所以沒寫成抽象方法 * * @param msg */ protected void requestOver(Message msg) { if (presenter != null) { presenter.handMsg(msg); } } protected void to(Intent intent) { startActivity(intent); } protected void to(Class<?> T) { Intent intent = new Intent(this, T); to(intent); } protected void to(Class<?> T, Bundle bundle) { Intent intent = new Intent(this, T); intent.putExtras(bundle); to(intent); } @Override public void onBackPressed() { if (waitDialog != null) { hideProcessDialog(); } else { super.onBackPressed(); } } public LoadingDialogFragment showProcessDialog() { return showProcessDialog(R.string.loading); } public LoadingDialogFragment showProcessDialog(int resId) { return showProcessDialog(getString(resId)); } private LoadingDialogFragment showProcessDialog(String msg) { if (mVisible) { FragmentManager fm = getSupportFragmentManager(); if (waitDialog == null) { waitDialog = LoadingDialogFragment.newInstance(msg); } if (!waitDialog.isAdded()) { waitDialog.show(fm, DIALOG_LOADING); } return waitDialog; } return null; } public void hideProcessDialog() { if (mVisible && waitDialog != null) { try { waitDialog.dismiss(); waitDialog = null; } catch (Exception ex) { ex.printStackTrace(); } } } @Override public void setVisible(boolean visible) { mVisible = visible; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { View v = getCurrentFocus(); if (isShouldHideKeyboard(v, ev)) { hideKeyboard(v.getWindowToken()); v.clearFocus(); } } return super.dispatchTouchEvent(ev); } /** * 根據EditText所在座標和使用者點選的座標相對比,來判斷是否隱藏鍵盤,因為當用戶點選EditText時則不能隱藏 * * @param v * @param event * @return */ private boolean isShouldHideKeyboard(View v, MotionEvent event) { if (v != null && (v instanceof EditText)) { int[] l = {0, 0}; v.getLocationInWindow(l); int left = l[0], top = l[1], bottom = top + v.getHeight(), right = left + v.getWidth(); if (event.getX() > left && event.getX() < right && event.getY() > top && event.getY() < bottom) { // 點選EditText的事件,忽略它。 return false; } else { return true; } } return false; } /** * 獲取InputMethodManager,隱藏軟鍵盤 * * @param token */ private void hideKeyboard(IBinder token) { if (token != null) { InputMethodManager im = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); im.hideSoftInputFromWindow(token, InputMethodManager.HIDE_NOT_ALWAYS); } } @Override public void onClick(View v) { // TODO Auto-generated method stub onclick(v); } @Override protected void onDestroy() { super.onDestroy(); /** * 移除mHandler,避免因為移除mHandler超activity生命週期工作造成記憶體洩漏 */ mHandler.removeCallbacksAndMessages(null); if (receiver != null) { LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver); } } }

這是我從上一個私活專案演化而來,當然,上個專案也是我從無到有,目前此類還少了一個對toolbar的封裝。

BaseObserver類

/**
 * Observer的封裝
 * Created by Zero on 2017/5/28.
 */

public class BaseObserver<T> implements Observer<ResponseBody> {

    private IResponse iResponse;
    private Gson mGson;
    private final Type finalNeedType;
    private static final int UNLOGIN_EXCEPTION = 33333;
    private static final int REQUEST_EXCEPTION = 1003;

    public BaseObserver(IResponse<T> iResponse) {
        this.iResponse = iResponse;
        mGson = new Gson();

        final Type[] types = iResponse.getClass().getGenericInterfaces();

        if (MethodHandler(types) == null || MethodHandler(types).size() == 0) {

        }
        finalNeedType = MethodHandler(types).get(0);
    }

    /**
     * 通過反射,拿到所需要的型別
     * @param types
     * @return
     */
    private List<Type> MethodHandler(Type[] types) {
        List<Type> needTypes = new ArrayList<>();

        for (Type paramType : types) {
            if (paramType instanceof ParameterizedType) {
                Type[] parenTypes = ((ParameterizedType) paramType).getActualTypeArguments();
                for (Type childType : parenTypes) {
                    needTypes.add(childType);
                    if (childType instanceof ParameterizedType) {
                        Type[] childTypes = ((ParameterizedType) childType).getActualTypeArguments();
                        for (Type type : childTypes) {
                            needTypes.add(type);
                        }
                    }
                }
            }
        }
        return needTypes;
    }

    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(ResponseBody responseBody) {

        try {
            /**
             * responseBody.string()當前打斷點,獲取不到值,具體原因還未去查詢,此處先用result接收
             */
            String result = responseBody.string();
            BaseResponse httpResponse = mGson.fromJson(result,finalNeedType);
            if (httpResponse.isSuccess()) {
                iResponse.onSuccess(httpResponse);
            } else {
                if (httpResponse.getCode() == UNLOGIN_EXCEPTION) {
                    iResponse.onError(new UnLoginException(httpResponse.getCode(), httpResponse.getMessage()));
                } else if (httpResponse.getCode() == REQUEST_EXCEPTION) {
                    iResponse.onError(new RequestExpiredException(httpResponse.getCode(), httpResponse.getMessage()));
                } else {
                    iResponse.onError(new APIException(httpResponse.getCode(), httpResponse.getMessage()));
                }
            }
        } catch (IOException e) {
            iResponse.onError(e);
        }
    }

    @Override
    public void onError(Throwable e) {
        iResponse.onError(e);
    }

    @Override
    public void onComplete() {

    }
}

RetrofitFactory類

/**
 * 此類主要是對retrofit進行配置
 * Created by Zero on 2017/5/26.
 */

public class RetrofitFactory {

    private RetrofitFactory() {
        new RuntimeException("反射個毛線,好玩嗎?");
    }

    private static OkHttpClient httpClient = MyOkHttpClient.getInstance();

    private static ApiService retrofitService;

    private static String baseUrl = "";

    private static Retrofit retrofit;

    /**
     * 預設為ApiService
     *
     * @return
     */
    public static ApiService getInstance() {
        if (retrofitService == null) {
            synchronized (RetrofitFactory.class) {
                if (retrofitService == null) {
                    retrofitService = getInstanceRetrofit().create(ApiService.class);
                }
            }
        }
        return retrofitService;
    }

    /**
     * baseUrl
     */
    private static void getBaseUrl() {
        baseUrl = HttpConfig.getServer();
    }

    private static Retrofit getInstanceRetrofit() {
        if (retrofit == null) {
            synchronized (RetrofitFactory.class) {
                if (retrofit == null) {
                    if (TextUtils.isEmpty(baseUrl)) {
                        getBaseUrl();
                    }

                    retrofit = new Retrofit.Builder()
                            .baseUrl(baseUrl)
                            .addConverterFactory(GsonConverterFactory.create())
                            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                            .client(httpClient)
                            .build();
                }
            }
        }
        return retrofit;
    }

    /**
     * 用於建立自定義的apiService
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T createRetrofitService(final Class<T> clazz) {
        return getInstanceRetrofit().create(clazz);
    }
}

RequestUtil類

/**
 * 請求的封裝入口
 * Created by Zero on 2017/5/25.
 */

public class RequestUtil {
    /**
     * get方式處理
     *
     * @param url
     * @param map
     * @param iResponse
     * @param <T>
     */
    public static <T> Observable<ResponseBody> getDispose(String url, Map map, final IResponse<T> iResponse) {
        Observable<ResponseBody> observable = RetrofitFactory.getInstance().executeGet(url, map);
        return getObservable(observable, iResponse, null);
    }

    private static <T> Observable<ResponseBody> getDispose(String url, Map map, final IResponse<T> iResponse, Map cacheMap) {
        Observable<ResponseBody> observable = RetrofitFactory.getInstance().executeGet(url, map);
        return getObservable(observable, iResponse, cacheMap);
    }

    /**
     * 自定義ApiService
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getCutomService(Class<T> clazz) {
        return RetrofitFactory.createRetrofitService(clazz);
    }

    /********************************post********************************/

    public static <T> void postDispose(String url, Map map, final IResponse<T> iResponse) {

        Observable<ResponseBody> observable = RetrofitFactory.getInstance().executePost(url, map);
        observable.observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(new BaseObserver<>(iResponse));
    }

    private static <T> Observable<ResponseBody> postDispose(String url, Map map, final IResponse<T> iResponse, Map cacheMap) {
        Observable<ResponseBody> observable = RetrofitFactory.getInstance().executePost(url, map);
        return getObservable(observable, iResponse, cacheMap);
    }

    /**
     * 獲取Observable物件,
     * 此處名稱的get為獲取的意思,不是資料請求方式
     * @param observable
     * @param iResponse
     * @param cacheMap
     * @param <T>
     * @return
     */
    private static <T> Observable<ResponseBody> getObservable(Observable<ResponseBody> observable, IResponse<T> iResponse, Map cacheMap) {
        if (cacheMap != null && cacheMap.size() > 0) {
            CacheManager.addData(cacheMap.get("cacheKey").toString(), observable, (int) cacheMap.get("period"));
        }
        observable.observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(new BaseObserver<>(iResponse));
        return observable;
    }


    /**************************************cache**************************************/

    private static <T> void cacheData(String url, Map map, final IResponse<T> iResponse, int period,boolean isGet){
        String cacheKey = url + getCacheKey(map);
        CacheObject data = CacheManager.getData(cacheKey);
        if (data == null) {
            Map cacheMap = new HashMap();
            cacheMap.put("cacheKey", cacheKey);
            cacheMap.put("period", period);
            if (isGet) {
                getDispose(url, map, iResponse, cacheMap);
            }else{
                postDispose(url, map, iResponse, cacheMap);
            }
        } else {
            getObservable((Observable<ResponseBody>) data.getObject(), iResponse, null);
        }
    }

    /**
     * get方式請求,需要做本地cache
     */
    public static <T> void getDisposeWithCache(String url, Map map, final IResponse<T> iResponse, int period) {
        cacheData(url,map,iResponse,period,true);
    }

    /**
     * post方式請求,需要做本地cache
     */
    public static <T> void postDisposeWithCache(String url, Map map, final IResponse<T> iResponse, int period) {
        cacheData(url,map,iResponse,period,false);
    }

    private static String getCacheKey(Map param) {
        if (param == null) {
            return "";
        }
        StringBuffer sb = new StringBuffer("");
        TreeMap treeMapParams = new TreeMap(param);
        for (Object key : treeMapParams.keySet()) {
            /**
             * 過濾掉token,根據自己需要
             */
            if (!key.toString().equals("token")) {
                sb.append(key).append("=").append(Uri.encode(treeMapParams.get(key).toString()));
            }
        }
        return sb.toString();
    }
}

RequestUtil、BaseObserver、RetrofitFactory三者相輔相成,通過RetrofitFactory進行retrofit的一系列配置,通過RequestUtil進行get/post資料請求,最後通過BaseObserver把RequestUtil資料請求中獲取到的Observer進一步處理。

CacheManager類

/**
 * 資料請求中對cache進行管理
 * Created by Zero on 2017/5/30.
 */

public class CacheManager {

    private static Map<String, CacheObject> cacheMap = new HashMap<>();

    /**
     * 新增到cache
     * @param key
     * @param data
     * @param period
     */
    public static void addData(String key, Object data, int period) {
        CacheObject cacheObject = getData(key);
        if (cacheObject != null) {
            cacheObject.setPeriod(period);
        } else {
            cacheObject = new CacheObject(data, period);
        }
        cacheMap.put(key, cacheObject);
    }

    /**
     * 獲取cache
     * @param key
     * @return
     */
    public static CacheObject getData(String key) {
        CacheObject cacheObject = cacheMap.get(key);
        if (cacheObject != null) {
            if (cacheObject.isValid()) {
                return cacheObject;
            } else {
                removeInvalidData(key);
            }
        }
        return null;
    }

    /**
     * 移除過期的key
     * @param key
     */
    public static void removeInvalidData(String key){
        if(cacheMap.containsKey(key)){
            cacheMap.remove(key);
        }
    }
}

CacheObject類

/**
 * Created by Zero on 2017/5/30.
 */

public class CacheObject {

    private long timestamp;
    private int period = -1;
    private Object data;

    /**
     * @param data
     * @param period -1 表示永不過期,大於0表示過期的時間,單位分鐘
     */
    public CacheObject(Object data, int period) {
        timestamp = System.currentTimeMillis();
        this.data = data;
        this.period = period;
    }

    public Object getObject() {
        return data;
    }

    public boolean isValid() {
        if (period == -1 || System.currentTimeMillis() < (timestamp + period * 60000)) {
            return true;
        }
        return false;
    }

    public void setPeriod(int period) {
        this.period = period;
    }

    public int getPeriod() {
        return period;
    }
}

CacheObject和CacheManager兩個類,分別是對cache進行配置和管理,在資料請求中,比如獲取省區縣三級目錄,這些都不需要多次請求的,可以載入到cache中,當以後再想使用的時候,直接從cache中獲取,減輕伺服器壓力。

LogInterceptor類

/**
 * 列印網路請求時傳輸的欄位還有返回的json資料
 * Created by Zero on 2017/5/27.
 */

public class LogInterceptor implements Interceptor {
    private final static String TAG = LogInterceptor.class.getSimpleName();

    @Override
    public okhttp3.Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        long t1 = System.nanoTime();
        okhttp3.Response response = chain.proceed(chain.request());
        long t2 = System.nanoTime();

        StringBuffer sb = new StringBuffer();
        sb.append(request.method()).append("\n");
        String url[] = request.url().toString().split("\\?");
        sb.append(url[0]).append("\n");
        if (url.length == 2) {
            String params[] = url[1].split("&");
            for (String param : params) {
                sb.append(Uri.decode(param)).append("\n");
            }
        }

        if(request.body() instanceof FormBody){
            FormBody postParams = ((FormBody) request.body());
            if (postParams != null) {
                sb.append("post:").append("\n");
                int size = postParams.size();
                for (int i = 0; i < size; i++) {
                    sb.append(postParams.encodedName(i) + "=" + java.net.URLDecoder.decode(postParams.encodedValue(i), "utf-8")).append("\n");
                }
            }
        }

        okhttp3.MediaType mediaType = response.body().contentType();
        String content = response.body().string();
        Log.v(TAG, String.format(Locale.getDefault(), "%s cost %.1fms%n%s", sb.toString(), (t2 - t1) / 1e6d, format(content)));
        //格式化列印json
        //       Log.v(TAG, String.format(Locale.getDefault(), "%s cost %.1fms%n%s", sb.toString(), (t2 - t1) / 1e6d, format(content)));
//        Log.v(TAG, String.format(Locale.getDefault(), "%s cost %.1fms%n%s", sb.toString(), (t2 - t1) / 1e6d, content));
        return response.newBuilder()
                .body(okhttp3.ResponseBody.create(mediaType, content))
                .build();
    }

    public static String format(String jsonStr) {

        int level = 0;
        StringBuffer jsonForMatStr = new StringBuffer();
        for (int i = 0; i < jsonStr.length(); i++) {
            char c = jsonStr.charAt(i);
            if (level > 0 && '\n' == jsonForMatStr.charAt(jsonForMatStr.length() - 1)) {
                jsonForMatStr.append(getLevelStr(level));
            }
            switch (c) {
                case '{':
                case '[':
                    jsonForMatStr.append(c + "\n");
                    level++;
                    break;
                case ',':
                    jsonForMatStr.append(c + "\n");
                    break;
                case '}':
                case ']':
                    jsonForMatStr.append("\n");
                    level--;
                    jsonForMatStr.append(getLevelStr(level));
                    jsonForMatStr.append(c);
                    break;
                default:
                    jsonForMatStr.append(c);
                    break;
            }
        }
        return jsonForMatStr.toString();
    }

    private static String getLevelStr(int level) {
        StringBuffer levelStr = new StringBuffer();
        for (int levelI = 0; levelI < level; levelI++) {
            levelStr.append("\t");
        }
        return levelStr.toString();
    }
}

描述

一圖勝千言,不做更多贅述。

由於專案今天剛搭建好,肯定也存在很多問題,日後會有不斷完善,今天就不多做敘述了,如有什麼疑問,可以留言,也可以加QQ群,如有什麼錯誤,也請多多指正,共勉共進。