最新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在解析的效率上對比其它幾個第三方,還是明顯偏低的。
廢話不多說,直接擼程式碼,因為寫了很多詳細的註釋,未來還會寫幾篇帖子專門分析,此處就不多做介紹。
先看下目錄結構圖
除了刪除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群,如有什麼錯誤,也請多多指正,共勉共進。