應用模板代碼地址:https://github.com/thfhongfeng/AndroidAppTemplate
App最核心的東西就是數據,因此數據請求框架是App必不可少的,也是最重要的一個組件架構。
常用的數據主要有兩種:文本數據,文件數據。
依據于這兩種數據的處理,數據請求框架的請求接口也就順理成章的包括了至少下面幾個:
1. 文本的數據請求(文本以Json方式組織)
2. 文件的上傳請求
3. 文件的下載請求
4. 網絡附屬信息的獲取與設置(cookies等)
本應用模板的數據請求框架的實現主要在tool模塊的request文件夾中。
請求框架目錄分為四部分:
1. 請求接口及本地化封裝實體(最外層接口及實體)
2. 具體實現(impl文件夾)
3. 攔截器(interceptor文件夾)
4. 回調(callback文件夾)
請求接口IRequestManager:
public interface IRequestManager {
String SESSION_ID = "JSESSIONID";
String COOKIE_KEY = "Cookie";
String MOBILE_MODEL_KEY = "mobileModel";
void setJsonRequest(@NonNull RequestBean requestBean,
@NonNull IResponseListener.OnResponseListener listener);
void setDownloadRequest(@NonNull DownloadRequestBean requestBean, @NonNull IResponseListener.OnDownloadListener listener);
void setUploadRequest(@NonNull UploadRequestBean requestBean, @NonNull IResponseListener.OnUploadListener processListener,
@NonNull IResponseListener.OnResponseListener responseListener);
void cancelBySign(Object sign);
void cancelAll();
void addGlobalSessionCookie(HashMap<String, String> headerMap);
void removeGlobalSessionCookie(List<String> keyList);
Map<String, String> getSessionCookie();
String getSessionId();
void setSessionId(String sessionId);
void clearCookie();
enum RequestType {
STRING, // stringRequest
UPLOAD, // uploadRequest
DOWNLOAD, // downloadRequest
BITMAP // bitmapRequest
}
enum ActionType {
COMMON, // common
RETRY_AFTER_RE_LOGIN, // retry after re-login
RETRY_WHEN_ERROR // retry when error
}
}
使用者通過RequestManager獲取具體的請求框架管理者實體,而具體請求管理者通過實現IRequestManager來為使用者提供服務。這是一個典型的工廠模式。使用者需要在初始化請求框架的時候提供一個RequestManager的工廠給框架。這個工廠類通過配置APP_THIRD_DATA_SOURCE_PROVIDER來確定使用哪個具體的請求框架實現者,從而做到請求框架第三方庫的可替代性。
public interface IRequestManagerFactory {
IRequestManager makeRequestManager(Context context, HashMap<String, String> head);
}
public static void init(Context context, HashMap<String, String> head, @NonNull IRequestManagerFactory factory) {
if (context != null) {
mApplicationContext = context;
} else {
mApplicationContext = AppUtils.getApplication();
}
mRequestManager = factory.makeRequestManager(context, head);
mLoadingRequestMap = new HashMap<>();
mErrorRequestMap = new HashMap<>();
}
RequestManager.init(this, new IRequestManagerFactory() {
@Override
public IRequestManager makeRequestManager(Context context, HashMap<String, String> head) {
switch (com.pine.config.BuildConfig.APP_THIRD_DATA_SOURCE_PROVIDER) {
case "local":
return DbRequestManager.getInstance().init(context, head, new IDbRequestServer() {
@Override
public DbResponse request(Bundle bundle) {
return RouterManager.getInstance(ConfigKey.BUNDLE_DB_SEVER_KEY).callDataCommandDirect(mApplication,
RouterDbServerCommand.callDbServerCommand, bundle);
}
});
default:
switch (BuildConfig.APP_THIRD_HTTP_REQUEST_PROVIDER) {
case "nohttp":
return NoRequestManager.getInstance().init(context, head);
default:
return NoRequestManager.getInstance().init(context, head);
}
}
}
});
需要說明的是:本框架的網絡請求實際上只實現了一個基于nohttp的三方庫網絡請求(以后補充),local方式是為了方便demo演示,而使用本地數據模擬的網絡請求。從這里也可以看出本框架的解耦性:即不關注數據來源,網絡的也好,本地的也好,都可以通過這個框架來請求(只要給出實現);不關注具體的第三方實現庫(nohttp也好,其它的也好,只要在impl中繼承IRequestManager做好實現即可)。
請求框架流程圖:
登陸請求響應攔截器
請求響應的登陸判斷基本上是app必備要素(很多請求都是需要登陸的,而未登陸或者session失效等會導致請求響應401),也比較典型。這個就可以通過請求響應攔截器來處理。
- 首先,在LoginApplication中為請求框架添加了一個LoginResponseInterceptor登陸請求響應攔截器,也就是說所有的請求響應都會先被這個攔截器做攔截過濾,只有符合條件的請求響應才能通過(當服務端請求響應告訴客戶端需要登陸時,客戶端應該進行的通用操作:比如跳到登陸界面等)。
public class LoginApplication extends BaseApplication {
private final static String TAG = LogUtils.makeLogTag(LoginApplication.class);
public static void attach() {
switch (BuildConfig.APP_THIRD_DATA_SOURCE_PROVIDER) {
case "local":
break;
default:
RequestManager.addGlobalResponseInterceptor(new LoginResponseInterceptor());
break;
}
}
}
- 在各類請求得到請求響應后,先交給LoginResponseInterceptor進行處理。
@Override
public boolean onIntercept(int what, RequestBean requestBean, Response response) {
if (requestBean.getCallback() instanceof LoginCallback) {
mIsReLoginProcessing = false;
if (!response.isSucceed() && what == LoginCallback.RE_LOGIN_CODE) {
LoginApplication.setLogin(false);
if (!tryToSendReLogin()) { // 發出自動登錄失敗
flushAllNoAuthRequest();
mNoAuthRequestMap.clear();
return false;
}
return true;
} else if (what == LoginCallback.RE_LOGIN_CODE) {
String res = (String) response.getData();
try {
JSONObject jsonObject = new JSONObject(res);
if (jsonObject == null || !jsonObject.optBoolean(LoginConstants.SUCCESS, false)) {
mPerReLoginCount = 0;
reloadAllNoAuthRequest();
}
} catch (JSONException e) {
}
}
} else if (requestBean.getRequestType() == IRequestManager.RequestType.STRING && requestBean.isReloadForNoAuthWhenReLogin()) {
if (response.getResponseCode() == ResponseCode.NOT_LOGIN) { // 攔截401錯誤
if (mNoAuthRequestMap != null &&
!mNoAuthRequestMap.containsKey(requestBean.getKey())) {
mNoAuthRequestMap.put(requestBean.getKey(), requestBean);
}
if (!mIsReLoginProcessing) {
LoginApplication.setLogin(false);
if (!tryToSendReLogin()) { // 發出自動登錄失敗
flushAllNoAuthRequest();
mNoAuthRequestMap.clear();
}
}
return true;
}
}
if (IRequestManager.ActionType.RETRY_AFTER_RE_LOGIN == requestBean.getActionType()) {
if (mNoAuthRequestMap != null &&
mNoAuthRequestMap.containsKey(requestBean.getKey())) {
mNoAuthRequestMap.remove(requestBean);
}
}
return false;
}
登陸請求響應流程圖:
對于登陸請求:
- 正常的登陸請求,不做處理,直接通過。
- 重登陸請求:登陸失敗---在次數允許范圍內嘗試再次重登陸,如果發出請求失敗(次數超限,請求數據不合規等),則通過flushAllNoAuthRequest將所有未授權的非登陸請求響應(mNoAuthRequestMap中保存的RequestBean)返回給調用者,并clear掉這些請求;如果發出請求成功,則直接攔截,等待本次重登陸請求響應。登陸成功---重新發出之前需要授權而失敗的非登陸請求。
對于非登陸請求:
如果需要登陸,但當前不是登陸狀態,服務端返回401,將該請求添加到mNoAuthRequestMap中,并發出重登陸請求。同樣的,發送失敗,則通過flushAllNoAuthRequest將所有未授權的非登陸請求響應(mNoAuthRequestMap中保存的RequestBean)返回給調用者,并clear掉這些請求;如果發出請求成功,則直接攔截,等待本次重登陸請求響應。登陸成功---重新發出之前需要授權而失敗的非登陸請求。