摘要:本文介绍了Android应用如何与服务器通信的一些机制,并分享了笔者的一些开发经验与技巧,通过具体的代码描述OkHttp框架的使用,适合初学者。
对于初学者来说,前后端一手抓很常见,如果你想要了解一点后台开发的话,那么建议看一下我的另一篇文章:
Java后台开发,轻量级框架Spring+SpringMVC+MyBatis (SSM)的使用
不太严谨地说,接入网络的终端(手机、主机等)都是通过网络层(IP层)的报文/数据报进行沟通的。具体可以参考百度百科-五层因特网协议栈的一些介绍,我们只需要了解一些基本原理就好了。(不过做网络应用开发还是应该学一点计算机网络的~)
狭义的理解,前后端通信的媒介就是Http报文,我们要做的就是 Android 前端发送Http报文,后端服务器接收并处理,然后返回一个结果报文。
而我接下来介绍的OkHttp框架就是一个比较流行的HTTP框架,它提供封装好的方法来帮助打包我们要提交或者请求的数据,并帮助我们发送到服务器。
使用Android Studio的同学先添加依赖implementation com.squareup.okhttp3:okhttp:3.11.0'
,使用Eclipse的同学只能自己找包了(不过新手还是建议使用Android Studio,比较智能)。
在写博文的时候OkHttp还是3.11.0版本,最新版还是请查看OkHttp-GitHub。
此外还要在 AndroidManifest 文件中添加网络权限App才能联网:
Http请求有POST、GET、DELETE、PUT等方式,在Android里我们一般只会使用到两种,POST、GET。
POST:可以在报文内部(我们也可以称为body)添加参数,提交数据到服务器。
GET:也允许携带参数,不过参数要跟在指定的URL后面,如Http://localhost:8080?param=xxx & param=yyy
, 参数以键-值对的形式拼接,多个参数需要使用 & 符号。但这种形式安全性低,所以GET一般只用来请求一些数据,如请求百度的首页。一些把密码、个人信息等当作参数,明文发送的请求实在不是很明智。
那么怎么选择使用GET还是POST呢?
笔者分享一下自己在大学做开发的经历,希望能帮到大家。使用哪一种方法一般不是由前端开发人员决定的,而是后端开发人员根据前端的需求制作接口文档,里面详细记录了各种接口的URL、参数、返回值、注意事项等,前端开发人员对照文档使用即可。比如下面这个场景:
角色:前端技术人员小李,后端技术人员大明
小李:大明哥,我们前端组最近要实现登录的功能啦,你帮我们写个接口呗!
大明:哦哦,收到收到,有什么参数吗?
小李:传个用户名和密码就行了。
大明:行,那用户名用u_name表示,密码用u_password表示,行不?
小李:OK!对了,两个参数都是字符串类型的,字符集用UTF-8。
大明:没问题,迟点我把接口文档给你们组发过去。
小李:OK,辛苦辛苦~
(实际上并不会这么和谐~~ 需求一时爽,@&%#……)
如果你恰好参与了接口设计的讨论,那么不妨了解一下什么是REST风格,它是HTTP协议1.1版本制定的重要部分。简单来说就是POST、GET对应着网络资源的增加和获取,如上传一张图片对应着服务器文件资源的增加,加载图片对应着资源的获取。
使用OKHttp的同步GET方法:这里还是沿用《第一行代码》请求百度首页的栗子。
OkHttpClient client = new OkHttpClient(); //新建客户端处理请求
Request request = new Request.Builder()
.url("http://www.baidu.com") //添加要请求的URL
.build();
try{
response = client.newCall(request).execute(); //执行这个Http请求
String result = response.body().string(); //获取Http响应报文的结果
}catch(Execption e){
e.printStackTrace();
}
上面的栗子运行之后就会获取百度首页的HTML数据。很简单的同步请求数据栗子,当然我们还可以在URL后面添加一些参数给服务器处理。
OKHtttp的异步GET请求百度首页:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
//前面还是一样的,但是下面就是重点了
Call call = client.newCall(request);
//添加到消息队列里,传入Callback,当响应报文回来时执行相应的回调方法
call.enqueue(new Callback{
@Override
public void onFailure(Call call, IOException e){
/*
* 请求失败的一些操作
*/
}
@Override
public void onResponse(Call call, Response response){
try{
String result = response.body().string();
}catch(Exception e){
e.printStackTrace();
}
}
});
POST方法的同步、异步几乎和GET是一样的,只需要多构建一个RequestBody,用来保存参数。
OkHttpClient client = new OkHttpClient();
ReuquestBody body = FormBody.Builder() //构建body
.add("key","value") //添加参数的键值对,可多次add()
.build();
Request request = new Request.Builder()
.url("xxx")
.post(body) //将body加进来
.build();
/*
* 接下来的操作和之前的Get相同
*/
对于同步的 Http 请求来说,“发送请求”和“接受结果”是一个有序的过程,程序需要等待结果的返回并处理。一旦请求发出,线程必须处理结果,如果结果没有返回,线程一直等待就有可能被阻塞。线程与Http请求/响应是同步的,线程不能抛开Http请求/响应去做其他事情。
对于异步的 Http 请求来说,程序“发送请求”之后不需要等待结果,可以去做其他事情。等 Http 响应返回的时候,再调用相应的回调方法处理报文就行。线程与Http请求/响应是异步,线程实际上已经和 Http 请求/响应分开了。
既然同步和异步都能实现Http请求,那么我们选择同步还是异步呢?
这就引出一个机制:在Android的主线程(也叫UI线程)里不允许有耗时操作,因为这会有阻塞线程的风险,而 Http 请求恰好就是耗时操作。(重点)
Http 是耗时操作还是很好理解的,因为网络的不确定性,当网络拥塞的时候报文有可能被阻挡在某个网络节点上,一直得不到响应也很正常。
我们绝大多数请求都是在Activity里写的,比如加载某个人的资料,那么在开启查看资料的Activity的时候就应该向服务器发出一个Http请求。如果我们直接在onCreate()
,onStart()
,onResume()
这些方法里添加上面的 同步请求 的代码,妥妥的崩溃。
那是不是我们就不能使用 Http 同步请求了呢?当然不是,我们的程序不止只有UI线程。我们还有多线程这个大利器。
不懂 Java 多线程的朋友也不用担心,代码十分简单,如下:在[1]区域中替换为相应的同步Http请求即可。
public void networkTask(){
new Thread(){
@Override
public void run(){
/*
* [1] 同步请求加到这里
*/
}
}.start();
}
看了同步Http的开启线程的操作,其实我们也明白了,在Anrdoid中,同步请求并不能真的和Activity同步,否则就崩了。(吐槽同步请求还有何用?)
总结前面的知识,实际上我们的Http请求都是在另外一条线程执行的,是异步的。但即使代码都写在同一个 .java 文件中,数据在不同线程是不能直接传递的。但有时候我们需要使用 Http 请求返回的数据来更新UI,怎么在UI线程中使用这些数据呢?
对于异步请求来说,我们在需要在对应的回调方法,onFailure()
或者onResponse()
中添加下面的代码:
final String result = response.body().string();
MainActivity.this.runOnUIThread(new Runable(){
@Override
public void run(){
/*
* 这里写的东西都会在UI线程执行,我们可以将result传进来
* 也可以在这里获取主线程View的实例,更新UI
* 注意result要添加final修饰,否则会报错
*/
}
});
而对于开启新线程的同步请求来说,需要将上面这段代码添加到同步请求的返回结果之后,如下:
public void networkTask(){
new Thread(){
@Override
public void run(){
// 构建请求
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
try{
response = client.newCall(request).execute(); // 执行
final String result = response.body().string(); // 结果
/*
* runOnUIThread 部分加到这里
*/
}catch(Execption e){
e.printStackTrace();
}
}
}.start();
}
可以看出来,为了在其他线程通知主线程,我们用到了runOnUIThread(Runable)
这个方法。
特别注意:因为网络时延的原因,View的加载都会快于响应报文的数据回传。千万不要以为将Http请求写在View加载之前就能成功更新UI,往往这种方式都会出现空指针异常。 (重要)
正文结束,欢迎留言讨论。