Retrofit使用入門

Retrofit是Square開發的一個用于網絡請求的開源庫,內部封裝了okhttp,并且和RxAndroid完美的兼容,使得Android的開發效率增加不少的同時也使代碼變得清晰易讀。

本次的學習建立在上次okhttp學習的基礎之上,service端的程序也是通過自己搭建并完成的。服務端的程序比較簡單,本次的retrofit學習不對服務端的程序進行過多的講解。如果有疑問,可以參考上次okhttp的相關內容。首先還是先列舉出本次學習用到的資源。


搭建使用環境

下載最新的jar或者構建Maven倉庫:

<dependency>
  <groupId>com.squareup.retrofit2</groupId>
  <artifactId>retrofit</artifactId>
  <version>2.2.0</version>
</dependency>

或者直接在項目的build.gradle中添加如下的依賴

compile 'com.squareup.retrofit2:retrofit:2.2.0'

ps,在添加依賴之前,最好先去github上看當前的最新版本。
在使用之前需要先添加網絡訪問權限。

<uses-permission android:name="android.permission.INTERNET"/>

使用示例

下面通過使用retrofit實現get、post、文件上傳、下載來演示retrofit的使用。

get請求

retrofit在使用的過程中,需要定一個接口對象,我們將它命名為IUserService:

public interface IUserService {
    @GET("rlogin")
    Call<ResponseBody> loginByGet(@Query("user") String user, @Query("passwd") String passwd);
}

然后在需要MainActivity中構建Retrofit,并生成一個實現接口的方法。

  retrofit = new Retrofit.Builder()
                .baseUrl("http://172.18.9.31:8080/OkhttpTestService/")
                .build();//構建一個retrofit 對象
 IUserService repo = retrofit.create(IUserService.class);
  Call<ResponseBody> call =   repo.loginByGet("reoger","123456");//實例loginByGet對象
 call.enqueue(new Callback<ResponseBody>() {//異步執行
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

                try {
                    mImageView.setText(response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                mImageView.setText("file to get");
            }
        });

簡要的說明一下上后面的代碼。首先,在IUserSevice接口中,通過@GET("rlogin")注釋指定了路徑,然后通過后面的loginByGet具體化了url。結合BaseUrl,實例化出來的Url實際上是:http://172.18.9.31:8080/OkhttpTestService/rlogin?user=reoger&passwd=123456,后面的user和passwd是在實例化時傳入的。
可能這么講會有點難懂,先看一個簡單的例子。
注解中的參數為請求的相對URL路徑@GET("users/list")
就相當與在BaseUrl后加上/users/list。在本例中就相當于:

http://172.18.9.31:8080/OkhttpTestService/users/list

當然,我們可以會遇到URL并不是固定的那種情況。這個時候我們就可以這么寫:

@GET("group/{id}/users") //注意 字符串id
List<ResponseBody> groupList(@Path("id") int groupId); //注意 Path注解的參數要和前面的字符串一樣 id

這個時候我們構造groupList時會傳入id,而這個id的值會代替傳入的groupId值代替。
{}用于表示是可以替換的區域,而函數參數必須用@Path注解聲明。參見上例。
然后,當需要用我們的請求含有參數的時候,這個時候就需要使用@Query注解來完成。
例如訪問:http://baseurl/users?user=username
就需要:

 @GET("users")
    Call<ResponseBody> getUsersBySort(@Query("user") String sort);

點擊之后,發現服務端能正確接收來自客服端的請求,并且客服端也能正確接收來自服務端的反饋信息。
這里有一點還需要提出來一下,Call<T> 中的T可以是返回的數據對象,如果返回的是Json數據,我們可以將其解析成一個Java對象的話,可以直接使用該Bean作為返回值,在構建retrofit的時候加上轉換方法即可。

為了后面后面的代碼比較簡潔,我們直接在這里先將后面用到的retrofit和repo對象,已經實現方法executeByEn()統一聲明。

   retrofit = new Retrofit.Builder()
                .baseUrl("http://172.18.9.31:8080/OkhttpTestService/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        repo = retrofit.create(IUserService.class);
private void executeByEn(Call<ResponseBody> call) {
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

                try {
                    mImageView.setText(response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                mImageView.setText("file to get");
            }
        });
    }

Post請求

還是先在IUserService中進行申明:

@POST("rlogin")
    @FormUrlEncoded
    Call<ResponseBody> loginByPost(@Field("user")String user,@Field("passwd") String passwd);

可以看到,這里我們使用Post作為注解,說明這是一個Post請求。添加FormUrlEncoded,然后通過@Field添加參數即可。
訪問的代碼:

  Call<ResponseBody> call =   repo.loginByPost("reoger","12346");
        executeByEn(call);

此時,我們應該就能同get請求一樣將數據傳遞到服務端,并接收來及服務端的消息。

Post向服務端傳遞String對象。

首先還是在IUserService中進行聲明:

  @POST("rpostString")
    Call<ResponseBody>  postString(@Body RequestBody user);

通過 @Body 注解可以聲明一個對象作為請求體發送到服務器。
訪問代碼:

  RequestBody body=RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),"Here is my server to pass the data, can be a string, but of course, can also be JSON data");
        Call<ResponseBody> call = repo.postString(body);
        executeByEn(call);

這里還是給出服務端的代碼吧,服務端接收到數據后會給客戶端返回一個收到的信息。

            HttpServletRequest request = ServletActionContext.getRequest();
             ServletInputStream is = request.getInputStream();

             StringBuilder sb = new StringBuilder();
             int len = 0;
             byte[] buf = new byte[1024];
             while((len=is.read(buf))!=-1){
                 sb.append(new String(buf,0,len));
             }
             System.out.println(sb.toString());
             
             HttpServletResponse response = ServletActionContext.getResponse();
             PrintWriter writer = response.getWriter();
              writer.write("reces: "+sb.toString());
              writer.flush();
          
            return null;

訪問之后,會發現我們的服務端正確接收到了我們發送的string數據,客戶端也成功接收到了來自服務端的數據。

Post 上傳Json格式數據

如果你只是單獨想上傳Json格式的數據到服務器,你完全可以寫的更加簡單。

@POST("rpostString")
    Call<ResponseBody> postJson(@Body User user);

訪問接口:

  Call<ResponseBody> call = repo.postJson(new User("reoger","love"));
        executeByEn(call);

是吧。兩三行代碼就解決了。那為什么我們傳入User這樣一個Bean對象傳到服務端的時候就變成了Json數據呢。主要原因我我們在構造retrofit的時候添加的轉換方法、

 .addConverterFactory(GsonConverterFactory.create())

為此,我們還需要添加額外的依賴:

    compile 'com.squareup.retrofit2:converter-gson:2.0.2'

具體的轉換過程就完成不需要我們來實現了。是不是很方便。

Post上傳單個文件

還是先聲明:

  @Multipart
    @POST("rpostSingerFile")
    Call<ResponseBody> uploadSingerFile(@Part MultipartBody.Part mPhoto, @Part("user")RequestBody user,@Part("passwd") RequestBody passwd);

這里@MultiPart的意思就是允許多個@Part了,我們這里使用了3個@Part,第一個我們準備上傳個文件,使用了MultipartBody.Part類型,其余兩個均為簡單的鍵值對。
使用:

  File file = new File(Environment.getExternalStorageDirectory(),"test.jpg");
        if(!file.exists()){
            Log.e("TAG","file is not exit!");
            return ;
        }
        RequestBody photoRequestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
        MultipartBody.Part photo = MultipartBody.Part.createFormData("mPhoto", "test.jpg", photoRequestBody);

        Call<ResponseBody> call = repo.uploadSingerFile(photo, RequestBody.create(null, "abc"), RequestBody.create(null, "123"));
       executeByEn(call);

代碼形式和前面的Okhttp基本差不多。

Post上傳多個文件:

這里可能和服務端實際接收的代碼有關,我這里是用了一種簡單的方法來接收多個文件。服務端的代碼如下:

 public List<File> image; // 上傳的文件
     public List<String> imageFileName; // 文件名稱
     public List<String> imageContentType; // 文件類型
    
    //上傳圖片
    public String upLoadMulitFile() throws IOException{
         ServletActionContext.getRequest().setCharacterEncoding("UTF-8");
        System.out.println("開始接收文件");
         String dir = ServletActionContext.getServletContext().getRealPath("files");
            // 取得需要上傳的文件數組
            List<File> files = image;
            
            if (files != null && files.size() > 0) {
                System.out.println("image ="+image.get(0).getName());
                for (int i = 0; i < files.size(); i++) {
                    FileOutputStream fos = new FileOutputStream(dir + "\\" + imageFileName.get(i));
                    FileInputStream fis = new FileInputStream(files.get(i));
                    byte[] buffer = new byte[1024];
                    int len = 0;
                    while ((len = fis.read(buffer)) > 0) {
                        fos.write(buffer, 0, len);
                    }
                    fis.close();
                    fos.close();
                }
            }
        return null;
    }

客服端我們是這么寫的。:

    @Multipart
    @POST("rpostMulitFile")
    Call<ResponseBody> upload(@Part()List<MultipartBody.Part> parts);

使用:

   File file1 = new File(Environment.getExternalStorageDirectory(),"test.jpg");
        File file2 = new File(Environment.getExternalStorageDirectory(),"test.JPEG");

        RequestBody photoRequestBody1 = RequestBody.create(MediaType.parse("application/octet-stream"), file1);
        RequestBody photoRequestBody2 = RequestBody.create(MediaType.parse("application/octet-stream"), file2);
        MultipartBody.Part photo1 = MultipartBody.Part.createFormData("image", "test22.jpg", photoRequestBody1);
        MultipartBody.Part photo2 = MultipartBody.Part.createFormData("image", "test33.jpg", photoRequestBody2);

        List<MultipartBody.Part> parts = new ArrayList<>();
        parts.add(photo1);
        parts.add(photo2);
        Call<ResponseBody> call = repo.upload(parts);
        executeByEn(call);

其實還有很多可優化的余地,只是當作demo,學習,就并沒有使用跟好的寫法。比如鴻洋大神是這么干的。

 public interface IUserBiz
 {
     @Multipart
     @POST("register")
      Call<User> registerUser(@PartMap Map<String, RequestBody> params,  @Part("password") RequestBody password);
}

執行的代碼:

File file = new File(Environment.getExternalStorageDirectory(), "messenger_01.png");
        RequestBody photo = RequestBody.create(MediaType.parse("image/png", file);
Map<String,RequestBody> photos = new HashMap<>();
photos.put("photos\"; filename=\"icon.png", photo);
photos.put("username",  RequestBody.create(null, "abc"));
Call<User> call = userBiz.registerUser(photos, RequestBody.create(null, "123"));

但是我并沒有將其實現,可能是我服務端的代碼限制了吧 。

下載

還是在IUserService中添加聲明:

 @GET("files/test.jpg")
    Call<ResponseBody> download();

執行:

 Call<ResponseBody> call = repo.download();
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                //save file in here

                Log.d("TAG","downFile...");
                InputStream is = response.body().byteStream();
                int len;
                try {
                    File file = new File(Environment.getExternalStorageDirectory(),"download.jpg");
                    FileOutputStream fos = new FileOutputStream(file);
                    byte[] buf = new byte[128];
                    while( (len=is.read(buf)) != -1) {
                        fos.write(buf, 0, len);
                    }
                    fos.flush();
                    fos.close();
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                Log.d("TAG","down success!");
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {

            }
        });

也比較簡單吧。不過對象OkHttp好像也沒有特別大的優勢。
OkHttp的寫法是這樣的:

  Request.Builder builder = new Request.Builder();
        Request request = builder.get().url(BASE_URL+"files/test2.jpg").build();
        okhttp3.Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {
                Log.e("TAG","Error"+e);
            }
            @Override
            public void onResponse(okhttp3.Call call, Response response) throws IOException {

                //保存到本地,
                downSavetoFile(response);
            }
        });

當然,這些都只是一些比較簡單的用法,也算是比較核心的用法了。接下來我們要學的可能就是細節上的設置和一些優化、封裝等等了。暫時先告一段落吧~。明天看看還能能不能更一篇。

最后,有需要源代碼的,可以戳這里。

五一快樂~~~。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,734評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,826評論 18 139
  • 整體Retrofit內容如下: 1、Retrofit解析1之前哨站——理解RESTful2、Retrofit解析2...
    隔壁老李頭閱讀 15,129評論 4 39
  • 又是一年中秋佳節,祝各位中秋節快樂。 今天我們來聊聊這個最近很火的網絡請求庫retrofit,在此基礎上會延伸出一...
    涅槃1992閱讀 7,809評論 13 133
  • 花開花落花滿園, 鳥鳴鳥默鳥不甘。 雨欺桃花花含笑, 風嘲飛鳥鳥依然。 新芽方露一點眉, 風雨何必阻青山?
    子天666閱讀 182評論 0 1