Android:使用OkHttp框架与后台通信、获取数据

摘要:本文介绍了Android应用如何与服务器通信的一些机制,并分享了笔者的一些开发经验与技巧,通过具体的代码描述OkHttp框架的使用,适合初学者。

对于初学者来说,前后端一手抓很常见,如果你想要了解一点后台开发的话,那么建议看一下我的另一篇文章:
Java后台开发,轻量级框架Spring+SpringMVC+MyBatis (SSM)的使用


1 - 引言

不太严谨地说,接入网络的终端(手机、主机等)都是通过网络层(IP层)的报文/数据报进行沟通的。具体可以参考百度百科-五层因特网协议栈的一些介绍,我们只需要了解一些基本原理就好了。(不过做网络应用开发还是应该学一点计算机网络的~)

狭义的理解,前后端通信的媒介就是Http报文,我们要做的就是 Android 前端发送Http报文,后端服务器接收并处理,然后返回一个结果报文。

Android:使用OkHttp框架与后台通信、获取数据_第1张图片

而我接下来介绍的OkHttp框架就是一个比较流行的HTTP框架,它提供封装好的方法来帮助打包我们要提交或者请求的数据,并帮助我们发送到服务器。

2 - 初尝 OkHttp

2.1 一些准备

使用Android Studio的同学先添加依赖implementation com.squareup.okhttp3:okhttp:3.11.0' ,使用Eclipse的同学只能自己找包了(不过新手还是建议使用Android Studio,比较智能)。

在写博文的时候OkHttp还是3.11.0版本,最新版还是请查看OkHttp-GitHub。

此外还要在 AndroidManifest 文件中添加网络权限App才能联网:

2.2 Http 请求类型

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对应着网络资源的增加和获取,如上传一张图片对应着服务器文件资源的增加,加载图片对应着资源的获取。

2.3 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();
        }
    }
});

2.4 POST 请求实例

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相同
 */

3 - Http 同步和异步(重点)

3.1 区别

对于同步的 Http 请求来说,“发送请求”和“接受结果”是一个有序的过程,程序需要等待结果的返回并处理。一旦请求发出,线程必须处理结果,如果结果没有返回,线程一直等待就有可能被阻塞。线程与Http请求/响应是同步的,线程不能抛开Http请求/响应去做其他事情

对于异步的 Http 请求来说,程序“发送请求”之后不需要等待结果,可以去做其他事情。等 Http 响应返回的时候,再调用相应的回调方法处理报文就行。线程与Http请求/响应是异步,线程实际上已经和 Http 请求/响应分开了

3.2 选择

既然同步和异步都能实现Http请求,那么我们选择同步还是异步呢?

这就引出一个机制:在Android的主线程(也叫UI线程)里不允许有耗时操作,因为这会有阻塞线程的风险,而 Http 请求恰好就是耗时操作。(重点)

Http 是耗时操作还是很好理解的,因为网络的不确定性,当网络拥塞的时候报文有可能被阻挡在某个网络节点上,一直得不到响应也很正常。

我们绝大多数请求都是在Activity里写的,比如加载某个人的资料,那么在开启查看资料的Activity的时候就应该向服务器发出一个Http请求。如果我们直接在onCreate()onStart(),onResume() 这些方法里添加上面的 同步请求 的代码,妥妥的崩溃。

那是不是我们就不能使用 Http 同步请求了呢?当然不是,我们的程序不止只有UI线程。我们还有多线程这个大利器。

3.3 解决方案

  1. 异步Http请求,异步方法前面已经说了。因为异步请求实际和主活动并不在同一个线程,也就不怕阻塞主线程了。
  2. 为同步 Http 请求开启一个新线程,将同步代码放在新线程中,那么就会将请求与UI线程分离了。

不懂 Java 多线程的朋友也不用担心,代码十分简单,如下:在[1]区域中替换为相应的同步Http请求即可。

public void networkTask(){
    new Thread(){
        @Override
        public void run(){
            /* 
             * [1] 同步请求加到这里
             */
        }
    }.start();
}

看了同步Http的开启线程的操作,其实我们也明白了,在Anrdoid中,同步请求并不能真的和Activity同步,否则就崩了。(吐槽同步请求还有何用?)

3.5 补充

总结前面的知识,实际上我们的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,往往这种方式都会出现空指针异常。 (重要)


正文结束,欢迎留言讨论。

你可能感兴趣的:(Android,学习笔记)