1. 程式人生 > >Retrofit掉坑記錄(一)

Retrofit掉坑記錄(一)

隨著專案的深入,Retrofit會深入學習,本系列應該當入坑先驅了,不說了,都是淚【累】

[email protected] AND @FieldMap 錯用將會導致HTTP 414錯誤
首先借用HTTP 414的某百科解釋 :
您的 Web 伺服器認為,客戶端(如您的瀏覽器或我們的 CheckUpDown 機器人)傳送的 HTTP 資料流包含一個過長網址, 即位元組太多。
相信瞭解了414解釋的,就知道我錯誤在哪裡吧,專案例項介紹如下:
有如下表單:

<H4>使用者資訊編輯</H4>
    <form action="action.do.php"
method="post" name="form_useredit" enctype="multipart/form-data"> 使用者ID:<input type="text" name="uid" value='2'"><br> <input type="hidden" name="Platformtype" value="Android"> <input type="hidden" name="enctype" value="post"> 使用者名稱:<input type="text" name="
username" value=''"><br> 性別:<input type="radio" name="sex" value="1" checked>男 <input type="radio" name="sex" value="0">女<br> 出生日期:<input type="date" name="birthday" value="1980-11-1"><br> 所在地(省):<input type="text" name="province" value
=""><br> 所在地(市):<input type="text" name="location" value=""><br> 簽名<input type="text" name="signature" value=""><br> <input type="file" name="headico"> <input type="text" name="action" value="user_user_editdo"> <input type="submit" name="submit1" value="修改" /> </form>

這是一個簡單的HTTP表單請求,Retrofit與RxJava結合如下:
1.RetrofitUtils構建:

 public static <T> T createService(Class<T> clazz) {
        if (retrofit == null) {
            synchronized (RetrofitUtil.class) {
                Retrofit.Builder builder = new Retrofit.Builder();
                retrofit = builder.baseUrl(Constant.BASE_IP)
                        .client(okHttpClient)
                        .addConverterFactory(gsonConverterFactory)
                        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                        .build();
            }
        }
        return retrofit.create(clazz);
    }

建立請求介面:

/**
     * 使用者資訊上傳
     * @param options
     * @param externalFileParameters
     * @return
     */
    @POST(Constant.BASE_ACTION)
    @Multipart
    Observable<ResponseBody> uploadProduct1(@QueryMap Map<String,RequestBody> options ,
                                      @PartMap Map<String, RequestBody> externalFileParameters) ;

/**
     * 使用者資訊上傳
     * @param options
     * @param externalFileParameters
     * @return
     */
    @POST(Constant.BASE_ACTION)
    @Multipart
    Observable<ResponseBody> uploadProduct2(@PartMap Map<String,RequestBody> options,
                                      @PartMap Map<String, RequestBody> externalFileParameters) ;

剛開始使用方式1上傳,Map構建如下:

Map<String,String> optionMap = new HashMap<String,String>();
optionMap.put("username",username);
optionMap.put("sex",sex);
optionMap.put("age",age);
.....//省略

剛開始沒發現什麼問題,但是後來在極限測試的情況下(使用者的自我介紹是一個非常長的字串,到底有多長,我得無問問測試的小MM了),會出現HTTP414 的問題,是bug就要改的啊,所以就抓包看來一下,發現如果是@QueryMap封裝的引數,這個Map中的值最終都會拼接在URL後面,就像這樣:http://www.exmaple.com?username=zhangsan&age=18&desc=shejklfajfkljeflkejfkejfejf&location=shanghai&gendar=1 …… 所以這樣就不對了,在查看了相關的HTTP資料,發現網路請求的URI的長度有長度的限制,這個與瀏覽器的有關係,不同的瀏覽器限制的長度會不一致 。最後發現了Retrofit2.0中還有一個@FieldMap,這個引數就是給我們的表單提交所使用的,我們是使用的是POST請求,使用FieldMap會將請求的引數封裝在Request的Body中,理論上POST請求是沒有長度限制的。
使用FieldMap的請求如下:

//對於字串的key=value,需要封裝為:
RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), value)
//對於存在文的form表單
RequestBody requestBody = RequestBody.create(MediaType.parser("multipart/form-data",fileName);

Map<String,RequestBody > optionMap = new HashMap<String,RequestBody >() ;
optionMap.put("username",RequestBody.create(MediaType.parse("text/plain"), username);
optionMap.put("location",RequestBody.create(MediaType.parse("text/plain"), location);
optionMap.put("age",RequestBody.create(MediaType.parse("text/plain"), age);
optionMap.put("gendar",RequestBody.create(MediaType.parse("text/plain"), gendar);
//存在檔案時:
RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), tempFile);
optionMap.put("filename=\"" + tempFile.getName() + "", requestBody);

2.Retrofit請求亂碼
作為Android開發者,UTF-8字元編碼已經成為我們的共識,所以在server端+client配合的很好的情況下,一般不會出現這種問題,但是蛋疼的我,就還是如願的出現了。最近很閒的時候,在android訪問 泡網,打算寫個客戶端可以看看這個很好的一個學習網站,網路請求就是Retrofit+Ok3,真是當你用夠了aFinal/XUtils/Volley/asynchttp之後,我會發現Retrofit真好,簡單好用。。。
但是坑爹的是,這個網站居然是:
這裡寫圖片描述
我猜寫這個網站的傢伙非常愛國吧,其他的真沒什麼好說的,不過生活還是要繼續啊,按照平常的寫法,返回的資料是這樣的:
這裡寫圖片描述
是很標準的亂碼,思考了一下,寫了個比較古老的方法,我感覺肯定還有更簡單的只是沒想到罷了,由於時間關係,就沒有多想,方法如下:

 public static <T> T getGB2312Instance(String baseURL, Class<T> clazz) {
        Retrofit.Builder builder;
        if (null == (builder = mContainer.get(baseURL))) {
            synchronized (RetrofitUtils.class) {
                builder = new Retrofit.Builder();
                builder.client(getGB2312Client()).
                        addCallAdapterFactory(RxJavaCallAdapterFactory.create()).
                        addConverterFactory(InputStreamConvertFactory.create());

                mContainer.put(baseURL, builder);
            }
        }
        return builder.baseUrl(baseURL).build().create(clazz);
    }

這裡添加了一個InputStreamConvertFactory,程式碼如下:

public class InputStreamConvertFactory extends Converter.Factory {

    public static InputStreamConvertFactory create() {
        return new InputStreamConvertFactory();
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        return new InputStreamResponseConverter<String>();
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        return new InputStreamRequestConverter<String>();
    }
}

其中InputStreamResponseConverter如下:

public class InputStreamResponseConverter<T> implements Converter<ResponseBody,T> {

    InputStreamResponseConverter() {}

    /**
    主要思路為我需要Retrofit返回InputStream,然後使用GB2312進行轉碼得到我們想要的編碼字串
    */  
    @Override
    public T convert(ResponseBody value) throws IOException {
        return (T) (new String(value.bytes(),"GB2312"));
    }
}

而相對不是特別重要的InputStreamRequestConverter程式碼如下:

public class InputStreamRequestConverter<T> implements Converter<T, RequestBody> {

    private static final MediaType MEDIA_TYPE = MediaType.parse("text/html; charset=UTF-8");

    /**
     這是是抄StringConverterFactory中的原始碼,因為我們不需要對要請求的引數進行編碼
    */  
    @Override
    public RequestBody convert(T value) throws IOException {
        return RequestBody.create(MEDIA_TYPE, value.toString());
    }
}

今晚就記錄兩個坑了,以後還有更多,本菜鳥也是剛剛學Retrofit,有些不對的不好的,希望大家指出,共同進步。