天天看點

Android請求Webservice的封裝(利用運作時注解和反射實作參數設定和資料解析)

介紹

在工作中使用webservice請求的時候需要單獨寫參數的設定以及請求後的資料解析,于是對這塊進行了封裝,利用了運作時注解和反射機制實作參數封裝,資料傳回使用json格式資料,利用反射實作解析封裝。具體源碼請看這裡:https://github.com/zuolongsnail/WebserviceTest

不足:

1.利用運作時注解和反射在使用時效率不高;

2.參數的封裝和解析可以使用GSON實作;

具體設計

1.請求參數的封裝

這裡在定義webservice接口參數的時候有兩種方法,一是接口需要幾個業務字段就設計幾個參數;二是接口隻設計一個參數,把接口所有業務字段以鍵值對的方式都封裝到一個json串作為唯一的參數傳給服務端,服務端拿到這個參數以後再按鍵值對方式擷取業務字段。

由于webservice服務接收到請求的時候,擷取參數是沒法利用鍵值對的方式取參數值,隻能通過順序取值,我們這裡利用了注解來排序。以下是定義的注解:

/**
 * 自定義注解,封裝webservice請求參數
 * 
 * @使用方法 index值為參數順序,name為參數名稱,如果不設定則預設變量名為參數名稱
 * @WSReqParam(index = 1) private String uid;
 * @WSReqParam(name = "userId") private String uid;
 * 
 * @description @Retention: 定義注解的保留政策
 * @Retention(RetentionPolicy.SOURCE) //注解僅存在于源碼中,在class位元組碼檔案中不包括
 * @Retention(RetentionPolicy.CLASS) //預設的保留政策,注解會在class位元組碼檔案中存在,但運作時無法獲得
 * @Retention(RetentionPolicy.RUNTIME) //注解會在class位元組碼檔案中存在,在運作時可以通過反射擷取到
 * @Inherited //說明子類可以繼承父類中的該注解
 * 
 * @Target(ElementType.TYPE) //用于描述類、接口(包括注解類型) 或enum聲明
 * @Target(ElementType.FIELD) //用于描述字段、枚舉
 * @Target(ElementType.METHOD) //用于描述方法
 * @Target(ElementType.PARAMETER) //用于描述參數
 * @Target(ElementType.CONSTRUCTOR) //用于描述構造器
 * @Target(ElementType.LOCAL_VARIABLE) //用于描述局部變量
 * @Target(ElementType.ANNOTATION_TYPE)//注解
 * @Target(ElementType.PACKAGE) //用于描述包
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectReqParam {
	/**
	 * 請求參數的順序,适用于webservice請求
	 * 
	 * @description 使用1,2,3,4,5,6,7....定義參數順序,如果不定義,不會作為參數傳遞
	 * @return
	 * @author zuolong
	 */
	public int index() default 0;
	/**
	 * 請求參數的名稱,适用于webservice和http請求
	 * 
	 * @description 須與接口文檔中的參數名稱定義相同,不設定則預設變量名為參數的名稱
	 * @return
	 * @author zuolong
	 */
	public String name() default "";
}
           

那麼我們如何定義參數,這裡打個比方,登入接口需要兩個參數,依次是userAccount和password,給這兩個參數分别設定順序為1和2,index值為參數順序,name為參數名稱,如果不設定則預設變量名為參數名稱,如果參數類中需要添加不作為參數的變量,這裡隻要給變量不設定注解即可,參數類定義如下:

/**
 * 登入參數類
 */
public class LoginParams extends BaseReqParams {
    @InjectReqParam(index = 1)
    public String userAccount;
    @InjectReqParam(index = 2)
    public String password;
}
           

通過注解和反射設定webservice請求,代碼如下:

/**
 * 通過注解反射參數順序和名稱
 *
 * @return
 * @throws IllegalAccessException
 * @description
 * @author zuolong
 */
private Map<String, Map<String, String>> injectParamsMap()
        throws IllegalAccessException {
    // <參數順序index, <參數名稱name, 參數值value>>
    Map<String, Map<String, String>> paramsIndexMap = new HashMap<String, Map<String, String>>();
    Class<? extends BaseReqParams> cls = mReqParams.getClass();
    Field[] fields = cls.getDeclaredFields();
    // 循環周遊請求參數對象中的值
    if (fields != null && fields.length > 0) {
        for (Field field : fields) {
            // 判斷變量是否存在指定的注解
            if (field.isAnnotationPresent(InjectReqParam.class)) {
                // 獲得該成員的annotation
                InjectReqParam reqParam = field
                        .getAnnotation(InjectReqParam.class);
                // 封裝參數1.通過注解獲得該參數的順序
                int index = reqParam.index();
                if (index <= 0) {
                    continue;
                }
                // 封裝參數2.通過注解獲得該參數的名稱
                String name = reqParam.name();
                // 如果沒有設定name則預設變量名為參數名稱
                if (TextUtils.isEmpty(name)) {
                    name = field.getName();
                }
                // 封裝參數3.擷取參數值
                field.setAccessible(true);
                Object object = field.get(mReqParams);
                String value = null;
                if (object != null) {
                    value = object.toString();
                }
                // 封裝參數4.存儲參數
                Map<String, String> paramMap = new HashMap<String, String>();
                paramMap.put(name, value);
                paramsIndexMap.put(String.valueOf(index), paramMap);
            }
        }
    }
    return paramsIndexMap;
}

/**
 * 設定webservice請求參數
 *
 * @param soapObject
 * @return
 */
public SoapObject setProperty(SoapObject soapObject) {
    try {
        Map<String, Map<String, String>> paramsIndexMap = injectParamsMap();
        // 封裝參數5.參數排序後進行周遊
        TreeSet<String> indexTreeSet = new TreeSet<String>(
                new IndexComparator());
        indexTreeSet.addAll(paramsIndexMap.keySet());// 把參數順序值放在TreeSet中進行排序
        Iterator<String> iterator = indexTreeSet.iterator();
        while (iterator.hasNext()) {
            String indexKey = iterator.next();
            // 封裝參數6.擷取<參數名稱, 參數值>鍵值對,此時map中僅有一對
            Map<String, String> paramMap = paramsIndexMap.get(indexKey);
            Iterator<String> paramIterator = paramMap.keySet().iterator();
            String nameKey = paramIterator.next();
            String value = paramMap.get(nameKey);
            soapObject.addProperty(nameKey, value);
        }
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return soapObject;
}
           

2.傳回資料的解析

這裡我們定義了四種解析類型,在發起請求時定義好需要的解析類型,當然選擇哪一種解析時更友善得看接口傳回什麼樣的資料,四種解析類型如下:

/**
 * 清單解析類型,對應的傳回資料必須嚴格按照以下格式:
 * <ul>
 * <li>請求成功:
 * {
 *     "success":"true",
 *     "message":{
 *         "root":[
 *             {
 *                 "data1":"資料字段1",
 *                 "data2":"資料字段2"
 *             },
 *             {
 *                 "data1":"資料字段1",
 *                 "data2":"資料字段2"
 *             }
 *         ]
 *     }
 * }
 * <li>請求失敗或錯誤:
 * {
 *     "success":"false",
 *     "message":"失敗或錯誤資訊"
 * }
 * </ul>
 */
public static final int LIST_PARSE_TYPE = 1;
/**
 * 對象解析類型,對應的傳回資料必須嚴格按照以下格式:
 * <ul>
 * <li>請求成功:
 * {
 *     "success":"true",
 *     "message":{
 *         "data1":"資料字段1",
 *         "data2":"資料字段2"
 *     }
 * }
 * <li>請求失敗或錯誤:
 * {
 *     "success":"false",
 *     "message":"失敗或錯誤資訊"
 * }
 * </ul>
 */
public static final int OBJECT_PARSE_TYPE = 2;
/**
 * JSON解析類型,對應的傳回資料必須嚴格按照以下格式:
 * <ul>
 * <li>請求成功:
 * {
 *     "success":"true",
 *     "message":{
 *         可以包含任何JSON資料格式
 *     }
 * }
 * <li>請求失敗或錯誤:
 * {
 *     "success":"false",
 *     "message":"失敗或錯誤資訊"
 * }
 * </ul>
 */
public static final int JSON_PARSE_TYPE = 3;
/**
 * 結果解析類型,對應的傳回資料必須嚴格按照以下格式:
 * <ul>
 * <li>請求成功:
 * {
 *     "success":"true",
 *     "message":"請求響應資訊"
 * }
 * <li>請求失敗或錯誤:
 * {
 *     "success":"false",
 *     "message":"失敗或錯誤資訊"
 * }
 * </ul>
 */
public static final int RESULT_PARSE_TYPE = 4;
           

針對不同的解析類型處理如下:

/**
 * 解析響應資料
 *
 * @param response
 * @throws Exception
 */
public void parseRespData(String response) throws JSONException,
        IllegalAccessException, InstantiationException {
    Message msg = new Message();
    if (!TextUtils.isEmpty(response)) {
        JSONObject jsonObject = new JSONObject(response);
        boolean result = JSONParseUtil.getRequestResult(jsonObject);
        if (result) {
            if (mParseType == LIST_PARSE_TYPE) {// 清單解析類型資料解析
                mResp.pageSum = JSONParseUtil.getPageTotal(jsonObject);
                mResp.itemTotal = JSONParseUtil.getItemTotal(jsonObject);
                JSONArray array = JSONParseUtil.getDatas(jsonObject);
                mResp.setList(mEntityCls, array);
                // 如果傳回資料中無字段,則傳回“請求成功無資料”結果
                if (mResp.list.size() <= 0) {
                    msg.what = MessageType.REQ_NODATA;
                    msg.obj = mContext
                            .getString(R.string.request_nodata_msg);
                    // 請求無資料
                    mHandler.sendMessage(msg);
                    return;
                }
            } else if (mParseType == OBJECT_PARSE_TYPE) {// 對象解析類型資料解析
                // 資料辨別
                String respId = mResp.respId;
                // 資料類型
                DataType dataType = mResp.dataType;
                JSONObject objectMessage = JSONParseUtil
                        .getMessage(jsonObject);
                // 如果傳回資料中無字段,則傳回“請求成功無資料”結果
                if (objectMessage.length() <= 0) {
                    msg.what = MessageType.REQ_NODATA;
                    msg.obj = mContext
                            .getString(R.string.request_nodata_msg);
                    // 請求無資料
                    mHandler.sendMessage(msg);
                    return;
                }
                mResp = mResp.getClass().cast(
                        JSONParseUtil.reflectObject(mResp.getClass(),
                                objectMessage));
                mResp.respId = respId;
                mResp.dataType = dataType;
            } else if (mParseType == JSON_PARSE_TYPE) {// JSON解析類型資料解析
                msg.what = MessageType.REQ_SUCCESS;
                msg.obj = jsonObject;
                mHandler.sendMessage(msg);
                return;
            } else if (mParseType == RESULT_PARSE_TYPE) {// 結果解析類型資料解析
                mResp.resultInfo = JSONParseUtil.getResultMsg(jsonObject)
                        .toString();
            }
            msg.what = MessageType.REQ_SUCCESS;
            msg.obj = mResp;
        } else {
            msg.what = MessageType.REQ_FAILED;
            msg.obj = JSONParseUtil.getResultMsg(jsonObject);
        }
    } else {
        msg.what = MessageType.REQ_FAILED;
        msg.obj = mContext.getString(R.string.request_server_error_msg);
    }
    mHandler.sendMessage(msg);
}
           

最終通過反射指派到對象:

/**
 * 解析後通過反射指派到對象
 *
 * @param clazz
 * @param jsonObj
 * @return
 * @throws Exception
 */
public static Object reflectObject(Class clazz, JSONObject jsonObj)
        throws JSONException, IllegalAccessException,
        InstantiationException {
    Object instance = clazz.newInstance();
    Field[] fields = clazz.getDeclaredFields();
    String attribute = null;
    for (Field field : fields) {
        attribute = field.getName();
        // 判斷傳回資料中是否含有相對字段并且不為“null”
        if (jsonObj.has(attribute) && !jsonObj.isNull(attribute)) {
            String value = jsonObj.get(attribute).toString();
            if (TextUtils.isEmpty(value)) {
                field.set(instance, "");
                continue;
            }
            if (field.getType() == int.class) {
                field.setInt(instance, Integer.parseInt(value));
            } else if (field.getType() == float.class) {
                field.setFloat(instance, Float.parseFloat(value));
            } else if (field.getType() == double.class) {
                field.setDouble(instance, Double.parseDouble(value));
            } else if (field.getType() == long.class) {
                field.setLong(instance, Long.parseLong(value));
            } else if (field.getType() == boolean.class) {
                field.setBoolean(instance, Boolean.parseBoolean(value));
            } else if (field.getType() == JSONArray.class) {
                field.set(instance, new JSONArray(value));
            } else {
                field.set(instance, field.getType().cast(value));
            }
        } else {
            if (!attribute.equals("serialVersionUID")) {// 過濾序列号類中的serialVersionUID字段
                if (field.getType() == int.class) {
                    field.setInt(instance, 0);
                } else if (field.getType() == float.class) {
                    field.setFloat(instance, 0);
                } else if (field.getType() == double.class) {
                    field.setDouble(instance, 0);
                } else if (field.getType() == long.class) {
                    field.setLong(instance, 0);
                } else if (field.getType() == boolean.class) {
                    field.setBoolean(instance, false);
                } else if (field.getType() == String.class) {
                    field.set(instance, "");
                } else {
                    field.set(instance, null);
                }
            }
        }
    }
    return instance;
}
           

使用說明

在實作請求時,可以繼承請求基類再進行封裝,有一定的擴充性。

舉例,接口定義如下:

接口名 login
接口說明 登入請求
參數序号 參數名 參數類型 參數說明
1 userAccount String 賬号
2 password String 密碼
傳回資訊格式例子
success字段為true
{
    "success":"true",
    "message":{
        "uid":"0000000001",
        "userName":"使用者姓名",
        "phoneNo":"手機号",
        "gender":"性别:男or女",
        "headUrl":"頭像url"
    }
}
           
success字段為false
{
    "message":"登入失敗,賬号或密碼錯誤",
    "success":"false"
}
           

實作代碼如下:

public class MainActivity extends AppCompatActivity {

    private Button btn_start_request;
    /**
     * webservice請求方法名
     */
    private String methodName = "login";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_start_request = (Button) findViewById(R.id.btn_start_request);
        btn_start_request.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 請求登入接口,接口定義參見login.interface檔案
                LoginParams params = new LoginParams();
                params.userAccount = "admin";
                params.password = "123456";
                sendLoginReqest(params);
            }
        });
    }

    /**
     * 發起請求
     *
     * @param params
     */
    private void sendLoginReqest(BaseReqParams params) {
        // 使用自定義請求
//        LoginRequest loginRequest = new LoginRequest(this, methodName, loginHandler, params, new LoginResp(), null, BasicRequest.JSON_PARSE_TYPE);
//        loginRequest.sendRequest();
        // 使用基類請求
        BaseWebserviceRequest loginRequest = new BaseWebserviceRequest(this, methodName, loginHandler, params, new LoginResp(), null, BasicRequest.OBJECT_PARSE_TYPE);
        loginRequest.sendRequest();
    }

    /**
     * 資料傳回以後在主線程進行業務處理
     */
    private Handler loginHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            int what = msg.what;
            switch (what) {
                case MessageType.REQ_SUCCESS:
                    if (msg.obj instanceof LoginResp) {
                        LoginResp resp = (LoginResp) msg.obj;
                        String uid = resp.uid;
                        String userName = resp.userName;
                        String phoneNo = resp.phoneNo;
                        String gender = resp.gender;
                        String headUrl = resp.headUrl;
                    }
                    break;
                default:
                    Toast.makeText(MainActivity.this, msg.obj.toString(), Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    };
}