版權聲明:本文章原創於 RamboPan ,未經允許,請勿轉載。
源碼基於 Retrofit 2.6.0
變量擴展
因爲最近負責開發幾個應用需要加上一個功能:通過訪問服務器端,對比服務器端上軟件的版本號與當前應用的版本號,如果版本號大於當前應用就進行升級。
既然是要幾個應用都需要這個功能,那我們想的是肯定首先做成一個通用型的,每個應用都拷貝一份,然後調用不同的配置就好。
那按照我們常規的思路,就可以這樣,虛擬下代碼。
靜態變量
//聲明一個檢查類
public class UpdateChecker{
//應用1
public static final String TYPE_APP1 = "xxx1";
//應用2
public static final String TYPE_APP3 = "xxx3";
//應用3
public static final String TYPE_APP2 = "xxx2";
//針對不同的升級類型,進行調用
public void startCheck(String type){
switch (type){
case TYPE_APP1:
break;
case TYPE_APP2:
break;
case TYPE_APP3:
break;
default:
}
}
……
}
當然此處的的類型也可以換成 int ,我們在查找對應應用是否需要升級時調用對應的類別就行。
這種如果是自己用的話,肯定也是沒有問題的,如果要把程序交給別人使用,怎麼讓別人一眼就看到有哪些值可以進行調用。
那接手的人可以嘗試輸入 UpdateChecker. 之後,對應編程軟件彈出對應的靜態屬性或者方法提示,從中尋找看着比較像的選項。
更負責任的做法,就是在對應的方法上,加上註釋,並且使用 {@link } 進行標註,這樣一看註釋,就可以有相關鏈接,跳轉到對應的參數,查看對應參數的註釋。
/**
* 傳入不同的應用類型進行對應版本升級檢測。
* 可以使用這些值。
*
* {@link #TYPE_APP1} 應用1
* {@link #TYPE_APP2} 應用2
* {@link #TYPE_APP3} 應用3
*
* @param type 需要檢測的類型
*/
public void startCheck(String type){
switch (type){
case TYPE_APP1:
break;
case TYPE_APP2:
break;
case TYPE_APP3:
break;
default:
}
}
使用帶鏈接註釋很容易讓使用者快速找到相關的類,特別是如果此時的 TYPE 是在另一個專用的常量類裏,那不進行鏈接註釋,怕是不好找了。
當然此處也可能存在傳入值不適當的情況,比如傳了一個 abc ,那麼肯定是不合適的。
雖然這種事情發生概率極極極小,但是很多 Java 書中仍然推薦使用枚舉。因爲你只能選那幾個,隨便輸入值是不行的。
枚舉
public class UpdateChecker{
public void startCheck(AppType type){
switch (type){
case TYPE_APP1:
break;
case TYPE_APP2:
break;
case TYPE_APP3:
break;
default:
}
}
……
}
public enum AppType {
//應用1
TYPE_APP1,
//應用2
TYPE_APP2,
//應用3
TYPE_APP1;
}
其實從這看,也沒什麼差別,無法從枚舉中看出有什麼優勢,不過如果我們如果需要把某些固定的信息和對應的類型進行綁定時。
如果是用靜態變量進行擴展時,那就需要加更多的靜態變量,在前綴或者後綴上進行區別,多了還是會有眼花的風險;那如果換成枚舉就很簡單了。
比如我們需要綁定每個類別的名字和網絡地址。
public enum AppType {
//需要使用的應用類型
//TYPE_APP1
TYPE_APP1(AppName.TYPE_APP1,AppUrl.AppUrl1),
//TYPE_APP2
TYPE_APP2(AppName.TYPE_APP2,AppUrl.AppUrl2),
//TYPE_APP3
TYPE_APP3(AppName.TYPE_APP3,AppUrl.AppUrl3);
//需要存儲的對應信息
private final String name;
private final String url;
//構造函數中進行配置
AppType(String name,Strng url){
this.name = name;
this.url = url;
}
String getName(){
return name;
}
String getUrl(){
return url;
}
//名字常量類
public static class AppName{
public final static String AppType1 = "AppType1";
public final static String AppType2 = "AppType2";
public final static String AppType3 = "AppType3";
}
//網址常量類
public static class AppUrl{
public final static String AppUrl1 = "AppUrl1";
public final static String AppUrl2 = "AppUrl2";
public final static String AppUrl3 = "AppUrl3";
}
}
這樣看起來是不是清爽很多,首先在內部對不同屬性定義一個靜態類,然後添加不同的常量變量在類中。
在枚舉類中聲明需要存儲的對應 final 變量(因爲我們不希望這個值在後期進行變化),構造函數中聲明需要配置的一些變量,此處例子就是一個名字和網址。
那我們在聲明不同的枚舉類型時,加入不同的變量進行配置。
我們 UpdateChecker.startCheck() 時只需要傳入一個類型,在需要使用名字的時候使用 type.getName() ,在需要使用網址時,使用 type.getUrl() ,那麼就很簡單的把各種類型進行綁定。有效減少了填錯值的情況。
當然也可以試着用一個普通類來完成這種枚舉類的形式,在此就不寫了。
請求網絡檢測的時候使用的 Retrofit ,因爲註解看着很華麗,哈哈。
一般處理網絡回覆分爲異步和同步,其實 … 寫法最後也差不多。
我們就分析異步的情況。進行請求時,需要將邏輯處理的回調作爲參數進行傳入。先來看看這個回調。
public interface Callback<T> {
void onResponse(Call<T> call, Response<T> response);
void onFailure(Call<T> call, Throwable t);
}
onResponse() 方法是拿到回覆了,但是不一定是成功的,而且 response.body() 有可能是空,那麼我們就拆成三部分,分爲請求失敗,回覆失敗,回覆成功,我們新建一個類來實現。
封裝
public abstract class BaseCallback<T> implements Callback<T> {
@Override
public void onResponse(Call<T> call, Response<T> response) {
//如果返回成功,並且body 也不爲空。那麼就算成功,其他算回覆失敗。
if(response.isSuccessful() && response.body() != null){
responseSuccess(call, response);
}else{
responseFail(call, response);
}
}
@Override
public void onFailure(Call<T> call, Throwable t) {
requestFail(call, t);
}
public abstract void responseSuccess(Call<T> call, Response<T> response);
public abstract void responseFail(Call<T> call, Response<T> response);
public abstract void requestFail(Call<T> call, Throwable t);
}
看着好像還闊以,雖然只是封裝了一小步 …
接下來在使用這個抽象類,肯定需要返回結果進行一些日誌的打印,或者是發出對應的事件。比如究竟是沒網,還是 json 解析失敗。
private BaseCallback<UpdateKey> updateCallback =
new BaseCallback<UpdateKey>() {
@Override
public void responseSuccess(Call<UpdateKey> call,
Response<UpdateKey> response) {
……
mListener.onUpdateCheck(Result.CHECK_RESPONSE_JSON_FAIL);
}
@Override
public void responseFail(Call<UpdateKey> call,
Response<UpdateKey> response) {
……
mListener.onUpdateCheck(Result.CHECK_RESPONSE_FAIL);
}
@Override
public void requestFail(Call<UpdateKey> call,
Throwable t) {
……
mListener.onUpdateCheck(Result.CHECK_REQUEST_FAIL);
}
};
public interface OnUpdateListener{
void onUpdateCheck(Result errorCode);
void onDownloadProgress(int progress);
}
這我們做了一個監聽,對請求結果進行回調,此處的 Result 類也是定義的一個枚舉類。用來通知不同的請求結果。其實剛開始還行,就定義了幾個 Result 類型,然後在 responseSuccess() 對數據進行一些更細的校驗時,那麼就對應需要返回更多的 Result 類型。
我想到了,如果是給的 Java 源碼還好,你可以直接在 Result 類進行擴展,那如果是拿到的庫文件,那想加都沒辦法了,只能選擇一個 Result 類型使用。
這樣又不能準確的傳達出想要表達的信息,這樣看來使用枚舉在這種情況下還是有一點的缺陷。
那麼想來想去,可以把 Result 類型從 enum 換成 interface 類型。
public interface Result {
String getMsg();
Result NO_INTERNET_PERMISSION = new Result() {
@Override
public String getMsg() {
return MSG_NO_INTERNET_PERMISSION;
}
};
Result NO_WRITE_FILE_PERMISSION = new Result() {
@Override
public String getMsg() {
return MSG_NO_WRITE_FILE_PERMISSION;
}
};
Result CHECK_RESPONSE_JSON_FAIL = new Result() {
@Override
public String getMsg() {
return MSG_CHECK_RESPONSE_JSON_FAIL;
}
};
static final String MSG_NO_INTERNET_PERMISSION = "沒有獲取網絡權限";
static final String MSG_NO_WRITE_FILE_PERMISSION = "沒有獲取讀寫權限";
static final String MSG_CHECK_RESPONSE_JSON_FAIL = "JSON 字符解析失敗";
}
這樣,除了自己定義的一些類型,如果後期打包給別人用,別人使用時,直接 new 一個接口類,並且複寫 getMsg() 就可以定義出自己想要的 Result 類。
枚舉與接口對比
那什麼情況下使用枚舉合適,什麼時候使用接口合適 ?
我的想法是:如果你是需要封裝一個東西,但是又不希望人家進行改動或者擴展時,使用枚舉。如果你希望人家可以在此基礎上進行擴展,那麼就可以使用接口,並且寫一些相關的類,給他人蔘考用。
回調收尾
完成了請求過程中關於信息的傳輸,我們肯定需要把監聽,或者引用的一些其他資源置空,防止內存泄漏。我們可以這麼操作 …
private BaseCallback<UpdateKey> updateCallback =
new BaseCallback<UpdateKey>() {
@Override
public void responseSuccess(Call<UpdateKey> call,
Response<UpdateKey> response) {
……
mListener.onUpdateCheck(Result.CHECK_RESPONSE_JSON_FAIL);
clear();
}
@Override
public void responseFail(Call<UpdateKey> call,
Response<UpdateKey> response) {
……
mListener.onUpdateCheck(Result.CHECK_RESPONSE_FAIL);
clear();
}
@Override
public void requestFail(Call<UpdateKey> call,
Throwable t) {
……
mListener.onUpdateCheck(Result.CHECK_REQUEST_FAIL);
clear();
}
};
private void clear(){
……
mListener = null;
}
這樣雖然沒什麼問題,但是一點也不優雅,還要加三個地方,而且以後新的 BaseCallback 都要加,那我們就把這個 clear() 放在基類裏。
public abstract class BaseCallback<T> implements Callback<T> {
@Override
public void onResponse(Call<T> call, Response<T> response) {
//如果返回成功,並且body 也不爲空。那麼就算成功,其他算回覆失敗。
if(response.isSuccessful() && response.body() != null){
responseSuccess(call, response);
clear();
}else{
responseFail(call, response);
clear();
}
}
@Override
public void onFailure(Call<T> call, Throwable t) {
requestFail(call, t);
clear();
}
……
public abstract void clear();
}
那麼在 new 之後,我們需要多複寫一個 clear() 方法。
現在問題來了,如果按照現在的邏輯,在執行中會不會有什麼風險 ?
當然 ! 因爲我們 listener 在使用時並沒有進行 != null 判斷,如果在執行 responseSuccess() , responseFail() , requestFail() 回調時,進行了異步的操作,那麼很可能會出現空指針異常。
除了在進行非空判斷以後,還有一個小的技巧,就是調整三個方法的返回值,讓使用者來決定是否需要執行 clear() 。
此處用 responseSuccess() 進行演示,因爲其他情況失敗了,一般默認是輸出信息,不會進行異步操作,所以暫時就不用那兩個方法演示。
public abstract class BaseCallback<T> implements Callback<T> {
@Override
public void onResponse(Call<T> call, Response<T> response) {
if(response.isSuccessful() && response.body() != null){
//如果返回 true ,證明沒有異步操作,直接進行清除。
if(responseSuccess(call, response ,this)){
clear();
}else{
//如果返回 false 的話,需要使用者自己找時機清除。
//可以把此 baseCallback 對象傳到異步方法等執行完調用clear()
//不過稍微有點麻煩.
}
}else{
responseFail(call, response);
clear();
}
}
@Override
public void onFailure(Call<T> call, Throwable t) {
requestFail(call, t);
clear();
}
//如果返回 true,表明可以直接調用 clear ,如果返回 false ,則表明剩下還有異步操作。
//傳入 BaseCallback 是方便異步方法中仍然拿到 callback 引用。
public abstract boolean responseSuccess(Call<T> call,
Response<T> response,
BaseCallback callback);
……
}
responseSuccess() 通過返回 true 或者 false ,來決定是否需要 clear() ,如果是同步操作,那麼順序執行就可以執行 clear() ,反之就不執行。
如果其他地方有持有該 BaseCallback 對象時,也可以在異步操作完成後調用 clear() ,而不需要在 responseSuccess() 把此對象進行傳遞。
至於 responseFail() 與 requestFail() ,我們可以考慮做一個通用的日誌輸出方法,並且方法爲 protected ,那麼 new BaseCallback() 時只需要複寫一個方法,如果需要更改的話,再進行復寫,那麼不會看着比較臃腫。
此分析純屬個人見解,如果有不對之處或者欠妥地方,歡迎指出一起討論。