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阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,098评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,654评论 18 139
  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful2、Retrofit解析2...
    隔壁老李头阅读 15,077评论 4 39
  • 又是一年中秋佳节,祝各位中秋节快乐。 今天我们来聊聊这个最近很火的网络请求库retrofit,在此基础上会延伸出一...
    涅槃1992阅读 7,785评论 13 133
  • 花开花落花满园, 鸟鸣鸟默鸟不甘。 雨欺桃花花含笑, 风嘲飞鸟鸟依然。 新芽方露一点眉, 风雨何必阻青山?
    子天666阅读 173评论 0 1