Okhttp&Retrofit【附赠服务器】

HttpURLConnection

HttpURLConnection详解

okhttp3和HttpURLConnection对比

HttpURLConnction在Android4.4之后底层实现采用的是OkHttp
因为是学习Okhttp和Retrofit的,对比HttpURLConnction的时候,是吧,你懂得,不能总是夸他好,不提他的坏处无法衬托出okhttp3的好处,,通过贬低一个人来烘托出另一个人,大人的世界还真是肮脏啊。。。言归正传:
HttpURLConnction缺点:
1.首先我觉得最重要的就是耦合性很高,尤其是配合handler,asynctask的时候,如果后期需要替换为其他的网络请求方案,处理起来将会十分棘手
2.使用繁琐,缺乏连接池管理功能
OkHttp3的优点:
使用连接池复用连接,效率很高
提供了Http响应的缓存机制,可以减少不必要的网络请求
自动重试功能,当网络出现问题时,Okhttp会自动重试一个主机的多个地址
底层使用socket连接,更加高效
缺点:
回调处理并不在主线程,封装起来比较麻烦,不过年轻人怎么能怕麻烦呢,用handler处理一下呗。

为什么我们要用Retrofit?

一、HttpURLConnctin
1.首先对于url构造和请求实体构造等过程,HttpURLConnection或OkHttpClient还需要手动拼接url,还需要手动对上传或返回的数据流进行操作
2.如果想进行解析,还需要自己再去手动引入一些解析框架如Gson等。此外,还需要用异步控制的方案或者框架如AyncTask/handler/Rxjava等,手动再与网络请求进行耦合。如果设计不当,会导致这几个框架之间耦合较大,导致如果有一天想替换掉某一部分(比如想用Rxjava替换Call或Gson替换Jackson)会很困难
二、Retrofit:
Retrofit在这几个方面都比较有优势:首先在构造HTTP请求时,我们只需要去构造接口的方法,框架会帮我们自动去实现这些方法。按规则去构造url,指定请求参数。可以直接用解析框架生成请求实体或解析结果。得到想要的异步请求的对象(Call/RxJava/RxJava2/guava/CompletableFuture)。请求构造更方便,同时与解析框架和异步请求框架解耦(通过Retrofit.addxxxFactory指定用不同的框架),可以更便捷的替换不同的解析框架或者异步框架
重点:
如果后台api是restful风格的,就建议使用retrofit,否则还是用okhttp

OkHttp

----搭建服务器—:

别的先不说,先搭建一个服务器,因为我们需要用okhttp下载文件和上传文件,没有服务器的话,我们没法测!
环境:
Eclipse+Tomcat9.0+struts2.5
步骤:
1.解压struts包之后,找到apps–>struts-blank.war解压
2.将WEB-INF-》lib下的jar包拷到项目对应目录下
3.将WEB-INF-》classes文件夹下的struts.xml文件拷到项目src文件夹下
4.将WEB-INF-》web.xml里的:


        struts2
        org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
    

    
        struts2
        /*
    

拷到对应的web.xml中。
5.新建操作类,实现Action方法:

package com.zzg.actions;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionSupport;

public class UserAction extends ActionSupport{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private String userName;
	private String password;
	/**
	 * 模仿用户登录
	 * @return
	 * @throws IOException 
	 */
	public String login() throws IOException {
		System.out.println(userName+","+password);
		//获取请求头
		HttpServletRequest  request=ServletActionContext.getRequest();
		System.out.println("请求头:--->S-ZZG-TOKEN:"+request.getHeader("S-ZZG-TOKEN"));
		//返回给客户端信息
		HttpServletResponse response= ServletActionContext.getResponse();
		PrintWriter writer=response.getWriter();
		writer.write("login success!");
		writer.flush();
		return null;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String username) {
		this.userName = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	
}

struts.xml里的配置:






    
    

    
     
      
     
    



这里method方法名对应上,name倒是随意,不过还是保持一致的比较好。

-----客户端-----

首先在复习一下Get和Post方式的区别
1.Post或者Get只是Http协议中向后台服务器发送数据的一种机制,是Http协议的一个组成部分。
2.Post请求是将要提交到后台服务器的数据放在Http包的包体中。
3.Get请求是将数据放在URL之后,比如http://androidxx.cn/forum.php?mo … d=11&extra=page%3D1,可以看到此URL由2部分组成,分别是http://androidxx.cn/forum.php和?后面的参数。这就是典型的Get请求方式。
因为Get请求时直接将参数放在URL后面,而URL的长度是有一定的限制,所以当传递的数据特别大的时候,Get请求就不能完成。
4.相比较,Post请求的参数是放在Http包的内部,不能轻易被看到;Get请求的参数直接是跟在URL之后,可以很容易被用户获取。所以,相对而言,Post的请求方式更安全。
Get和Post的使用场景:
Get(简单查询):
下载图片、获取一些安全性不高的文件
Post(注重安全的或者请求参数很大的):
1.登录 2.注册 3.上传 4.支付 5.下单 6.获取用户信息…
言归正传,Okhttp中get和post的最大区别在于,post的时候需要传递一个RequestBody参数,因为get参数是暴露在url里的,post为了安全起见是将参数放在RequestBody对象里的,而RequestBody又有两个子类:FormBody、MultipartBody。
一、简单get、post请求如下

    /**
     * 同步GET请求execute
     * @param view
     * @throws IOException
     */
    public void doGet(View view)  {
        new Thread(()->{
            Request request = new Request.Builder()
                    .get()
                    .url(URL + "login?userName=zzg&password=1234")
                    .addHeader("S-ZZG-TOKEN", ServiceContext.getUUID())
                    .build();
            Response response= null;
            try {
                response = okHttpClient.newCall(request).execute();
                if (!response.isSuccessful()){
                    Log.e(TAG, "doGet: "+response.code());
                    Response finalResponse = response;
                    getActivity().runOnUiThread(()->{
                        ToastUtils.shortMsg("连接服务器失败!错误码:--"+String.valueOf(finalResponse.code()));
                    });
                }else{
                    String resp=response.body().string();
                    getActivity().runOnUiThread(()->{
                        tv1.setText(resp);
                    });
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
  /**
     * 异步Post请求
     * @param view
     */
    public void doPost(View view) {
        new Thread(()->{
            RequestBody requestBody = new FormBody.Builder()
                    .add("userName", "空山鸟语")
                    .add("password", String.valueOf(12132))
                    .build();
            Request request = new Request.Builder()
                    .url(URL + "login")
                    .post(requestBody)
                    .build();
            okHttpClient.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    //e.getMessage获得错误信息,e.printStackTrace打印出错误路径
                    Log.i(TAG, "onFailure: ....." + e.getMessage());
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(getActivity(), "连接服务器失败", Toast.LENGTH_SHORT).show();
                        }
                    });

                    e.printStackTrace();
                }

                @Override
                public void onResponse(Call call, final Response response) throws IOException {
                    final String s = response.body().string();
                    Log.i(TAG, "onResponse: " + s);
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                tv1.setText("服务器返回数据:"+s);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            });
        }).start();

    }

上面两个是最基本的get和post请求,get就不多说了,post中构造了一个RequestBody参数,将两个参数userName,password封装在其内部,实现是其子类FormBody。
二、传递字符串给服务器
客户端代码:

  //向服务器传递字符串
    private static final  String  jsonString="{\"username\":\"zzg\",\"job\":\"coder\"}";
    /**
     * 上传json字符串
     * @param view
     */
    public void doPostString(View view) {
        new  Thread(()->{
            RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain;chaset=utf-8"), jsonString);
            Request request = new Request.Builder()
                    .url(URL + "postString")
                    .post(requestBody)
                    .build();
            okHttpClient.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    //e.getMessage获得错误信息,e.printStackTrace打印出错误路径
                    Log.i(TAG, "onFailure: ....." + e.getMessage());
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(getActivity(), "连接服务器失败", Toast.LENGTH_SHORT).show();
                        }
                    });

                    e.printStackTrace();
                }

                @Override
                public void onResponse(Call call, final Response response) throws IOException {
                    final String s = response.body().string();
                    Log.i(TAG, "onResponse: " + s);
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                tv1.setText(s);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            });
        }).start();

    }

服务端代码:

	/**
	 * 从客户端接收jsonString
	 * @return
	 * @throws IOException
	 */
	public String postString() throws IOException {
		StringBuilder result=new StringBuilder();
		HttpServletRequest request=ServletActionContext.getRequest();
		InputStream inputStream=request.getInputStream();
		BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
		String buf;
		while((buf=reader.readLine())!=null) {
			result.append(buf);
		}
		//返回给客户端信息
		HttpServletResponse response= ServletActionContext.getResponse();
		PrintWriter writer=response.getWriter();
		writer.write("服务端接收数据:"+result);
		writer.flush();
		System.out.println("从客户端接收的数据为:\n"+result);
		return null;
	}
    
     
     

三、上传文件
客户端逻辑:

/**
     * 上传文件
     * @param view
     */
    public void doPostFile(View view) {
        //本地必须得有这个图片
        File file = new File(SDHelper.getImageFolder(), "portrait.png");
        if (!file.exists()) {
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(getActivity(), "文件不存在", Toast.LENGTH_SHORT).show();
                }
            });
        }

        RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
        Request request = new Request.Builder().url(URL + "postFile").post(requestBody).build();//拿到号码
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getActivity(), "连接服务器失败", Toast.LENGTH_SHORT).show();
                    }
                });
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getActivity(), "上传成功", Toast.LENGTH_SHORT).show();
                    }
                });
            }
        });

    }

服务端逻辑:

	/**
	 * 接收客户端发送过来的文件
	 * @throws IOException
	 */
	public void postFile() throws IOException {
	 HttpServletRequest request=ServletActionContext.getRequest();
	 InputStream inputStream=request.getInputStream();
	 //File
//	 String fileName="D:/okhttpServer/zzg.jpg";
//	 
//	 File file = new  File(fileName);
//	 if (!file.exists()) {
//	   //  File dirs=new File(fileName.substring(0,fileName.lastIndexOf("/")));
//		 File dirs=file.getParentFile();
//		if (!dirs.isDirectory()||!dirs.exists()) {
//			dirs.mkdirs();
//		}
//		file.createNewFile();
//	}
	 String dir=ServletActionContext.getServletContext().getRealPath("files");
	 File file=new File(dir,"zzzz.jpg");
	 FileOutputStream fos = new FileOutputStream(file);
	 int len=0;
	 byte[] buf=new byte[1024*2];
	 while((len=inputStream.read(buf))!=-1) {
		 fos.write(buf, 0, len);
	 }
	 inputStream.close();
	 fos.flush();
	 fos.close();
	}

 

这里需要提示一下:
1.String dir=ServletActionContext.getServletContext().getRealPath(“files”);,这个目录是Tomcat加载项目的路径,指定这个目录是为了方便我们通过拼接url从服务器下载文件。在底下的下载文件中可以看出来。
2.RequestBody.create(MediaType.parse("application/octet-stream"), file);这里解释一下,create方法第一个参数需要传入的是MediaType。
MediaType指的是要传递的数据的MIME类型,MediaType对象包含了三种信息:type 、subtype以及charset,一般将这些信息传入parse()方法中,这样就可以解析出MediaType对象,比如 “text/x-markdown; charset=utf-8” ,type值是text,表示是文本这一大类;/后面的x-markdown是subtype,表示是文本这一大类下的markdown这一小类; charset=utf-8 则表示采用UTF-8编码 。
一些常用的MIME类型:
json : application/json
xml : application/xml
png : image/png
jpg : image/jpeg
gif : imge/gif

详细的请戳:
MIME参考手册

四、上传文件附带其他信息
客户端:


    /**
     * 上传文件...跟上面差在可能是也传参数过去吧
     * @param view
     */
    public void doUpLoad(View view) {
        File file = new File(SDHelper.getImageFolder(), "portrait.png");
        MultipartBody multipartBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
                .addFormDataPart("userName", "SprrowZ")
                .addFormDataPart("password", "0517")
                .addFormDataPart("mPhoto", "SprrowZ.png", RequestBody.create(//mPhoto是Key,服务器端通过这个取内容
                        //上传成功后,文件名还是为yy.jpg
                        MediaType.parse("application/octet-stream"), file))
                .build();
        Request request = new Request.Builder().url(URL + "uploadInfo").post(multipartBody).build();
       okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure: ....." + e.getMessage());
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getActivity(), "连接服务器失败", Toast.LENGTH_SHORT).show();
                    }
                });

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getActivity(), "上传成功", Toast.LENGTH_SHORT).show();
                    }
                });

            }
        });
    }

服务端:

	/**
	 * 上传文件和其他信息
	 * @return
	 * @throws IOException
	 */
	public String uploadInfo() throws IOException{
		if (mPhoto==null) {
			System.out.println(mPhotoFileName+"is null!");
		}
		String dir="D:/okhttpServer/"+mPhotoFileName;
		File file=new File(dir);
		 if (!file.exists()) {
			   //  File dirs=new File(fileName.substring(0,fileName.lastIndexOf("/")));
				 File dirs=file.getParentFile();
				if (!dirs.isDirectory()||!dirs.exists()) {
					dirs.mkdirs();
				}
				file.createNewFile();
			}
		 
		FileUtils.copyFile(mPhoto, file);
		System.out.println("fileName"+mPhotoFileName+"||userName:"+userName
				+"||password:"+password);
		return null;
	}
 
     
     

五、下载文件:

    /**
     *下载文件
     * @param view
     */
    public void doDownLoad(View view) {
        final Request request = new Request.Builder().url(URL + "files/zzzz.jpg").build();//电脑上这个路径下要有test.jpg
        //输出路径
        Log.i(TAG, "doDownLoad: " + URL + "files/zzg.jpg");
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                getActivity().runOnUiThread(()->{
                    Toast.makeText(getActivity(), "连接服务器失败", Toast.LENGTH_SHORT).show();
                });

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                getActivity().runOnUiThread(()->{
                    Toast.makeText(getActivity(), "下载成功!已保存到"+SDHelper.getImageFolder()+"目录下!",
                            Toast.LENGTH_SHORT).show();
                });
                //******************追踪进度*********************
                long total = request.body().contentLength();//文件的大小
                InputStream is = response.body().byteStream();
                monitorProgress(is,total);

            }
        });
    }


    /**
     * 监控文件下载进度
     * @param is
     * @param total
     */
   private void monitorProgress(InputStream is,long total){
       long sum = 0L;
       int len = 0;
       File file = new File(SDHelper.getImageFolder(), "fromServer.png");//这个名字本地不能有
       byte[] buf = new byte[128];
       FileOutputStream fos = null;
       try {
           fos = new FileOutputStream(file);
           while ((len = is.read(buf)) != -1) {
               fos.write(buf, 0, len);
               //输出进度
               sum += len;
               Log.e(TAG, sum + "/" + total);//输出下载进度
           }
           fos.flush();
           fos.close();
           is.close();
       } catch (FileNotFoundException e) {
           e.printStackTrace();
       } catch (IOException e) {
           e.printStackTrace();
       }

   }

这里,就可以看出来下载的路径URL + "files/zzzz.jpg",如果下载到d盘,struts要改配置,目前能力不足,只能出此下计,在存的时候对应上那个路径就好了。


Retrofit

快速入门
本文主要是参考慕课网Jennynick老师视频所做出的总结。

一、首先明白一点:
Retrofit是基于Okhttp网络框架进行的二次封装,其本质仍是Okhttp。(类似乌尔奇奥拉的二段归刃)
另外,科普一下,Android5.0之后不再使用HttpClient了。本来还想着看看httpclient的高阶用法呢,你看,多学习就可以少学习。

二、同Volley对比
Volley基于HttpUrlConnction,Google官方推出,只适用于轻量级网络交互,不适合大文件下载上传场景。
没有对比就没有伤害,伤害了Volley突出了Retrofit如下优点:
API设计简洁易用、注解化配置高度解耦、支持多种解析器、支持Rxjava
实际操作:
一、依赖包导入

    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.okhttp3:okhttp:3.10.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.0.2'

网络权限别忘了申请~

二、创建接口设置请求类型与参数
ex:新建UserInfoModel类和UserMgrService接口:

@GET("login")
public Call login(@Query("username") String username,@Query("pwd") String pwd);

(login是UserMgrService里的一个方法)


三、创建Retrofit对象、设置数据解析器
Retrofit retrofit=new Retrofit.Builder().baseUrl(Constants.BASE_URL).addConverterFactory(GsonConverterFactory.create())).build();
数据解析器支持类型有:
Gson、Jackson、Simple XML、 Protobuf、Moshi、Wire、Scalars
四、生成接口对象
UserMgrService service=retrofit.create(UserMgrService.class);
五、调用接口方法返回Call对象
Call call=service.login(“zhangsan”,“2345”);
六、发送请求(同步、异步)
同步:调用Call对象的execute(),返回结果的响应体(如果是耗时操作,可能导致ANR,需要处理)
异步:调用Call对象的enqueue(),参数是一个回调


请求方法有如下几个:
1.@GET 表示这个一个get请求
2.@POST 表示这是一个post请求
3.@PUT …
4.@DELETE …
5.@HEAD …
6.@OPTIONS …
7.@PATCH …
请求参数如下:
8.@Headers 添加请求头
9.@Path 替换路径
10.@Query :形成单个查询参数,将接口url中追加类似于"page=1"的字符串,形成提交给服务器端的参数,主要用于Get请求数据,用于拼接在拼接在url路径后面的查询参数,一个 @Query相当于拼接一个参数
11.@QueryMap:包含多个@Query注解参数(传递较多参数时使用)
12.@Field、@FieldMap
@Field的用法类似于@Query,就不在重复列举了,主要不同的是@Field主要用于Post请求数据。
@FieldMap的用法类似于@QueryMap。
两者主要区别是:如果请求为post实现,那么最好传递参数时使用@Field、@FieldMap和@FormUrlEncoded。因为@Query和或QueryMap都是将参数拼接在url后面的,而@Field或@FieldMap传递的参数时放在请求体的

13.@FormUrlEncoded: 以form表单方式上传,用于修饰Field注解和FieldMap注解, 如果去掉@FromUrlEncoded在post请求中使用@Field和@FieldMap,那么程序会抛出Java.lang.IllegalArgumentException: @Field parameters can only be used with form encoding. 的错误异常。
所以如果平时公司如果是Post请求的话,千万别忘记了加这@FromUrlEncoded注解。
@Body 可以用来提交 Json 数据[可以是实体类,retrofit会处理为json字符串]、上传文件
14.@Multipart
表示请求发送multipart数据,需要配合使用@Part @PartMap
15.@Part
用于上传文件,与@MultiPart注解结合使用

@Multipart
@POST("/url")
Call uploadFlie(
      @Part("description") RequestBody description,
      @Part("files") MultipartBody.Part file);

16.@PartMap
用于上传文件,与@MultiPart注解结合使用,默认接受的类型是Map,可用于实现多文件上传

@Multipart
@POST("{url}")
Call uploadFiles(
      @Path("url") String url,
      @PartMap() Map maps);

GET请求

七、请求实例:
① 新建一个接口,声明请求方法
② 在activity中声明retrofit,并调用接口里的方法。
测试调用github的api:(以下Demo均是在此Url基础上拼接

https://api.github.com/

无参:(最简单的get请求

   @GET("/")//不传参数
   Call getMessages();

@Path替换路径:(URL中有参数

@GET("repos/{owner}/{repo}")//路径为:https://api.github.com/repos/{owner}/{repo}
 Call getMessages3(@Path("owner") String owner,@Path("repo") String repo);//替换路径

@Path和@Query一块使用:(URL中有参数且URL问号之后有参数

  @GET("users/{user}/repos")//路径:https://api.github.com/users/{user}/repos{?type,page,per_page,sort}
    Call getMessages4(@Path("user") String user,
                                    @Query("page") int page,@Query("per_page")int per_page);

@QueryMap的使用(URL中问号之后有多个参数且个数不固定

 @GET("users/{user}/repos")
    Call getMessages5(@Path("user") String user, @QueryMap HashMap info);
   _________________________________
  HashMap params=new HashMap<>();
                params.put("page",1);
                params.put("per_page",3);
                Call call=api.getMessages5("SprrowZ",params);

好,今天先更到这里。
继续来个实例:

public interface GithubApi {
    /**
     * GET请求
     * @return
     */
    //访问文件地址:https://raw.githubusercontent.com/SprrowZ(用户名)/AndroidZex(仓库名)/master(分支)/.gitignore(路径)
    @GET("/")//不传参数
    Call getMessages();
    @GET("users/SprrowZ")//这个参数是用来拼路径的,https://api.github.com/users/{user},就是这里的{user}
    Call getMessages2();
    @GET("repos/{owner}/{repo}")//路径为:https://api.github.com/repos/{owner}/{repo},{owner}/{repo}
    Call getMessages3(@Path("owner") String owner,@Path("repo") String repo);//替换路径
    @GET("users/{user}/repos")//路径:https://api.github.com/users/{user}/repos{?type,page,per_page,sort}
    Call getMessages4(@Path("user") String user,
                                    @Query("page") int page,@Query("per_page")int per_page);
    @GET("users/{user}/repos")
    Call getMessages5(@Path("user") String user, @QueryMap HashMap info);




}

    private void retrofitGet1() {
        new Thread(()->{
                Retrofit retrofit=new Retrofit.Builder()
                        .baseUrl(Constant.GITHUB_BASE_URL)
                        .addConverterFactory(GsonConverterFactory.create())
                        .build();
                GithubApi service=retrofit.create(GithubApi.class);
                Call call=service.getMessages();
                call.enqueue(new Callback() {
                    @Override
                    public void onResponse(Call call, Response response) {
                        System.out.println(response.body().toString());
                        try {
                            content1.setText("get无参:"+"\n"+response.body().string());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onFailure(Call call, Throwable t) {
                        t.printStackTrace();
                    }
                });
        }).start();

    }

重点讲一下这个:.addConverterFactory(GsonConverterFactory.create())Converter就是Retrofit为我们提供用于将ResponseBody转换为我们想要的类型,看最上面我们接受返回类型的时候不是ResponseBody而是UserInfoModel,这个类是我们建的实体类,用来接受返回的数据,当数据不复杂时用这个更方便操作,如果复杂的话直接用ResponseBody比较好,个人感觉。。

FBI WARNING
一、response.body().string()只有第一次有取到值!!!!所以务必赋值给一个String ,然后操作这个对象!
如果不信的话,可以打两个log,都输出response.body().string()你就会发现第二次没有值,这个是正常的,看源码就晓得了,节省资源的策略。
二、可以看到请求路径中有大括号’{}’,这里我们需要在方法里声明这个路径【@Path】,传入值即可。@Path 路径中需要替换{}的情况,{}内要和@Path("")内对应一致!

POST请求

一、post请求上传json字符串

package com.rye.catcher.activity.fragment.orr.interfaces;

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.Query;

/**
 * Created at 2019/1/10.
 *
 * @author Zzg
 * @function:
 */
public interface zServerApi {
    /**
     * post提交jsonString给服务器
     * @param path
     * @param user
     * @param pass
     * @param postBean
     * @return
     */
    @POST("{path}")
    Call postMessage(@Path("path") String path,
                                   @Query("userName") String user,
                                   @Query("password") String pass,
                                   @Body PostBean postBean);
}

package com.rye.catcher.activity.fragment.orr.interfaces;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * Created at 2019/1/10.
 *
 * @author Zzg
 * @function:
 */
public class PostBean implements Parcelable {
    private String city;
    private String job;
    private boolean sex;

    public PostBean(){

    }

    protected PostBean(Parcel in) {
        city = in.readString();
        job = in.readString();
        sex = in.readByte() != 0;
    }

    public static final Creator CREATOR = new Creator() {
        @Override
        public PostBean createFromParcel(Parcel in) {
            return new PostBean(in);
        }

        @Override
        public PostBean[] newArray(int size) {
            return new PostBean[size];
        }
    };

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public boolean isSex() {
        return sex;
    }

    public void setSex(boolean sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "PostBean{" +
                "city='" + city + '\'' +
                ", job='" + job + '\'' +
                ", sex=" + sex +
                '}';
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(city);
        dest.writeString(job);
        dest.writeByte((byte) (sex ? 1 : 0));
    }
}

   private static final String BASE_URL="http://192.168.43.231:8088/OkhttpServer/";
    private void postString() {
        new Thread(()->{
            Retrofit retrofit =new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(OkHttpUtil.getInstance().getClient())
                    .build();
            zServerApi serverApi=retrofit.create(zServerApi.class);
            PostBean bean=new PostBean();
            bean.setCity("ShangHai");
            bean.setJob("程序员");
            bean.setSex(true);
            try {
                Response response= serverApi.postMessage("postString","zzg","0517",bean)
                        .execute();
                if (!response.isSuccessful()){
                    throw  new IOException("Unexpected result:"+response.code());
                }
                Log.i(TAG, "postString: "+response.body().string());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

上面就是给服务器上传一个实体类,retrofit会将实体类中的信息转换为json字符串,因为要在网络中传输,所以实体类中要实现parcelabel接口。

   @Body PostBean postBean

实体类是通过这句话传递的。这里:
@Body
非表单请求体;
使用 @Body 注解,指定一个对象作为 request body 。
作用:以 Post方式 传递 自定义数据类型 给服务器
注意:如果提交的是一个Map,那么作用相当于 @Field

就是用来向服务器传递对象用的。
Tips:
实际上,如果我们在post请求上加上 @FormUrlEncoded,连接服务器进行上传的时候,会报错:

 @Body parameters cannot be used with form or multi-part encoding. 

从提示语句中可以看出,@Body标签不可以和 @FormUrlEncoded或者
二、上传文件

你可能感兴趣的:(Okhttp&Retrofit【附赠服务器】)