OKHttp从入门到放弃(自己编写测试服务端)

OKHttp是一款高效的HTTP客户端,支持连接同一地址的链接共享同一个socket,通过连接池来减小响应延迟,还有透明的GZIP压缩,请求缓存等优势 。今天就记录以下使用Okhttp的心得和体会。


首先先列出我的参考资料:


下面正式开始来入门。

配置环境

客户端配置

下载最新的jar包或者通过Maven仓库构建。

<dependency>
  <groupId>com.squareup.okhttp3</groupId>
  <artifactId>okhttp</artifactId>
  <version>3.7.0</version>
</dependency>

或者,Gradle添加如下依赖

compile 'com.squareup.okhttp3:okhttp:3.7.0'

最好去官网上查看当前的最新版本。写这篇博客的最新版本为3.7.0。

服务端配置

这里我们采用eclipse搭配Tomcat作为我们的测试服务器,使用structs2的框架以节省代码的编写。具体的配置参考这篇博客

开始使用

get请求

okHttp的是非常非常简单的,如果我们要发送一个get请求,四步就可以解决了。

  1. 构建一个OkHttpClient,简单的示例代码如下
  OkHttpClient client=new OkHttpClient();
  1. 构建一个Request,简单的示例代码如下
     Request request = new Request.Builder().url("http://baidu.com") .build();
  1. 获得Call对象,简单的实例代码如下:
     okhttp3.Call call = okHttpClient.newCall(request);
  1. 执行get请求,两种执行方式,同步和异步执行
//同步执行
call.execute();

//异步执行
 call.enqueue(new Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {
              //发生错误时执行的回调
            }
            @Override
            public void onResponse(okhttp3.Call call, Response response) throws IOException {
              //正确执行,获取返回数据的回调
            }
        });

一个简单的异步Get请求,我们完全就可以这么写。

         OkHttpClient client=new OkHttpClient();
        Request request = new Request.Builder().url("http://baidu.com").build();
        client.newCall(request).enqueue(new Callback() {
                    @Override
                    public void onFailure(Request request, IOException e) {
                    }
                    @Override
                    public void onResponse(Response response) throws IOException {
                    }
         });

上面我们还是用了用git访问了baidu的首页,现在我们实现用get请求我们搭建的测试服务器。
在eclipse中新建一个UserAction类,继承自ActionSupport,添加一个方法,login()用来模拟登录过程。在UserAction中添加username和Passwd属性。
整个UserAction的代码如下:

public class UserAction extends ActionSupport{
    private static final long serialVersionUID = 1L;
    private String username;
    private String passwd;

    public String login() throws IOException{       
        System.out.println(" ---:"+username+" "+passwd);    
        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 getPasswd() {
        return passwd;
    }
    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }
    public static long getSerialversionuid() {
        return serialVersionUID;
    }
}

然后在structs.xml中配置文件添加
<action name="login" class="action.UserAction" method="login"></action>
通过浏览器访问http://127.0.0.1:8080/OkhttpTestService/login,OkhttpTestService是我的项目名称,后面的login是我们在structs.xml中配置的名称。如果我们访问后返回login success!即代表我们的服务端写好了。
将Get请求中的地址变成如下的形式:

Request request = builder.get().url(BASE_URL+"login?username=roger&passwd=123456").build();

添加对应的点击事件,并且在获得结果后在TextView显示返回的对象。主要代码如下:

 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 {

                final String repo = response.body().string();
                Log.d("TAG",repo+" ");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mText.setText(repo);
                    }
                });
            }
        });

点击按钮,发现服务端能正常接收到我们的get请求,并且能正常解析url中附带的参数信息:username和passwd。

Post 请求。

与get请求很类似,我们也可以将post请求看做是四个步骤。

//1. 构造OkHttpClient对象
OkHttpClient client=new OkHttpClient();

//2 构建一个Request

//2.1 与get请求不同,post中的参数需要通过RequestBody 来进行传递
  RequestBody requestBody = new FormBody.Builder().add("username","reoger").add("passwd","123").build();
//2.2 构建一个builder
 Request.Builder builder = new Request.Builder();
//2.3 完成Request的构建
 Request request = builder.post(requestBody).url(BASE_URL+"login").build();

//3 获得Call对象
 okhttp3.Call call = okHttpClient.newCall(request);

  //4 异步执行
        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 {

                final String repo = response.body().string();
                Log.d("TAG",repo+" ");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mText.setText(repo);
                    }
                });
            }
        });

服务端的代码无须改变。测试一下,正常访问,且服务端也能正确识别传递过去的userName和passwd

Post上传String或者json数据

通过OkHttp来上传json或者String数据也是一件非常简单的事情。工欲善其事,必先利其器,我们首先来编写服务端的程序,用于接收来自客户端的json或者String数据,并简单的将收到的数据打印出来。服务端的代码如下(还是直接添加到UserAction类中):

public String postString() throws IOException{
         System.out.println("*********");
        HttpServletRequest request = ServletActionContext.getRequest();
         ServletInputStream is = request.getInputStream();
        
         System.out.println("session Id ="+request.getSession().getId());
         
         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());
        return null;
    }

然后在strusts2.xml中添加如下的配置信息:

 <action name="postString" class="action.UserAction" method="postString"></action>

到此,我们的服务端已经编写完成。接下来就是我们的客户端代码的编写。

//1.构建OkHttpClient对象
OkHttpClient client=new OkHttpClient();

//2.构建一个Request对象
  RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain;charset=utf-8"),"this is json data or string data");
        Request.Builder builder = new Request.Builder();
        Request request = builder.post(requestBody).url(BASE_URL+"postString").build();
//3、4 获取Call并执行,这里我们将他们封装成一个方法。   代码前面的代码  
   execute(request);

这里值得注意的就是RequestBody.create(MediaType contentType, String content)里面的参数,contentType表示请求编码的方式,提交json数据格式时,可以设置为:MediaType.parse("application/json; charset=utf-8")更多的数据格式,可以参考这里,后面的String就是我们要上传的字符串或者json数据。

代码编写完毕,测试,服务端能正常打印客户端发送的数据,通过post上传json或者String数据成功完成。

Post上传文件

app中,文件上传的需求也比较常见,如用户设置头像等场景。下面介绍使用okhttp通过post上传文件。
首先还是先编写我们的服务端,(为简单起见,还是直接在UserAction类中添加)

public String postFile() throws IOException{
         System.out.println("*****file****");
            HttpServletRequest request = ServletActionContext.getRequest();
             ServletInputStream is = request.getInputStream();
             
             System.out.println("session Id ="+request.getSession().getId());
            
             String dir = ServletActionContext.getServletContext().getRealPath("files");
             File file = new File(dir,"test.jpg");
             FileOutputStream fos = new FileOutputStream(file);
             
             int len = 0;
             byte[] buf = new byte[1024];
             while((len=is.read(buf))!=-1){
                fos.write(buf, 0, len);
             }

             fos.flush();
             fos.close();
             System.out.println("----end");
        
        return null;
    }

简要说明一下这里的代码,服务端其实也就做了两件事情。首先创建一个名为test.jpg的文件,创建的位置为项目发布位置的根目录 / files的文件下或者,你也可以自己文件的位置。然后将收到的数据写入到test.jpg的文件中。注意,这里只是简单的示例代码,并没做异常处理,正常的服务端这里肯定是需要进行try-catch处理的。

同样,在struts.xml中还是需要添加申明。

 <action name="postFile" class="action.UserAction" method="postFile"></action>

服务端写好了,接下来就是我们的客服端了。通过okhttp来实现post文件上传也是非常简单的。示例代码如下:

OkHttpClient client=new OkHttpClient();
  File file = new File(Environment.getExternalStorageDirectory(),"test.jpg");
        if(!file.exists()){
            Log.e("TAG","file is not exit!");
            return ;
        }
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"),file);
        Request.Builder builder = new Request.Builder();
        Request request = builder.post(requestBody).url(BASE_URL+"postFile").build();
        execute(request);

也简要说明一下客户端的程序,首先我们拿到在手机根目录下的一个叫做test.jpg的文件,(在这之前我们需要确保手机根目录下有这么一个文件,我们将测试将这个文件传上服务端)。然后我们构建我们的RequestBody,更多的数据格式请参考这里,在create方法中,有一个重载的方法可以添加文件,我们将之前拿到的文件传入就Ok了。
在运行之前,不要忘记在AndroidManifest.xml添加相应的权限。

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

运行程序之后,发现图片能上传到我们指定的位置。

上传文件附带认证信息

首先还是编写我们服务端的代码:(继续添加在UserAction中)

    public File mPhoto;
    public String mPhotoFileName;
    public String mPhotoContentType;
    //上传图片
    public String upLoadInfo() throws IOException{
        
        System.out.println(username+" ," +passwd+"  "+mPhotoContentType);
        
        if(mPhoto == null){
            System.out.println("mPhoto == null "+mPhotoFileName);
        }
        
         String dir = ServletActionContext.getServletContext().getRealPath("files");
         File file = new File(dir,mPhotoFileName);
         
         FileUtils.copyFile(mPhoto, file);
        return null;
    }

简要说明一下。mPhone是服务端和客服端协商好的文件名,客户端也只有使用这个文件名时才能被正确识别。前面的username和passwd也是如此(这里写成public是为了省略set和get方法)。
至于后面的代码就更简单了,首先创建一个文件,然后将客服端传过来的文件直接复制到创建的文件中。不要忘记在strusts2.xml中声明:

 <action name="uploadImg" class="action.UserAction" method="upLoadInfo"></action>

到此,客户端就已经编写完成了。接下来就来编写客服端的代码:完整代码如下:

  File file = new File(Environment.getExternalStorageDirectory(),"test.jpg");
        if(!file.exists()){
            Log.e("TAG","file is not exit!");
            return ;
        }

        RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"),file);

        MultipartBody multipartBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("username","123465")
                .addFormDataPart("passwd","pppp")
                .addFormDataPart("mPhoto","test2.jpg",requestBody)
                .build();
        Request.Builder builder = new Request.Builder();
        Request request = builder.post(multipartBody ).url(BASE_URL+"uploadImg").build();
        execute(request);

是不是感觉很熟悉?没错,就是我们在我们基本的Post请求上使用功能更加强大的MultipartBody来代替RequestBody而已。我们在RequestBody添加来了用户信息,密码和文件等等。服务端收到post请求会将post的文件一并收取,并将其储存在指定的目录下。
到此,我们的文件上传功能做的差不多了,接下来我们来试试我们去下载文件。

文件下载

利用okHttp下载文件也是SoEasy。接下来我们来看怎么实现吧。
文件的下载就不要去编写服务器了,我们可以用之前上传的图片来做测试。或者直接在网上找一张图片来做测试。
下载文件其实也就四步,整体来说,Okhttp实现请求都是四个步骤:直接上代码,不在赘述。

      OkHttpClient client=new OkHttpClient();//1

     Request.Builder builder = new Request.Builder();//2
        Request request = builder.get().url(BASE_URL+"files/test2.jpg").build();//2

        okhttp3.Call call = okHttpClient.newCall(request);//3

        call.enqueue(new Callback() {//4
            @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 {
                //显示到imageView上
               // showImgInImageView(response);
                //保存到本地,
                downSavetoFile(response);
            }
        });

可以看到,下载文件的步骤同前面的步骤简直一模一样。下载的文件会通过onResponse回传给我们,我们只需要在这里进行简单的处理就好了。例如,这里我直接将图片显示到ImageView上,代码如下:

 //显示图片到imageView上
    private void showImgInImageView(Response response) {
    InputStream is = response.body().byteStream();
        final Bitmap bitmap = BitmapFactory.decodeStream(is);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mDownImage.setImageBitmap(bitmap);
            }
        });
    }

将图片保存到手机中,实例代码如下:

 /**
     * 保存到本地
     * @param response
     * @throws IOException
     */
    private void downSavetoFile(Response response) throws IOException {

        long total = response.body().contentLength();
        long sum = 0L;

        Log.d("TAG","downFile...");
        InputStream is = response.body().byteStream();
        int len;
        File file = new File(Environment.getExternalStorageDirectory(),"12346.jpg");
        FileOutputStream fos = new FileOutputStream(file);
        byte[] buf = new byte[128];
        while( (len=is.read(buf)) != -1) {
            fos.write(buf, 0, len);
            sum+= len;
            Log.d("TAG","--------进度"+sum+" / "+total);
        }
        fos.flush();
        fos.close();
        is.close();
        Log.d("TAG","down success!");
    }

到此,基本的Okhttp用法就了解的差不多了。,对了,这里再补充一点,下载的进度okhttp直接给了我们文件的总长度和当前下载的进度两个值,通过这两个值我们就可以计算出当前的进度。但是上传文件的进度okhttp并没有直接给我们相关的数据,我i门可以通过重写RequestBody来实现监听进度。代码如下:

public class CountingRequestBody extends RequestBody{

    private RequestBody delegate;
    private Listener listener;

    private CountingSink countingSink;

    public CountingRequestBody(RequestBody delegate, Listener listener) {
        this.delegate = delegate;
        this.listener = listener;
    }

    @Override
    public MediaType contentType() {
        return delegate.contentType();
    }

    @Override
    public long contentLength()  {
        try {
            return delegate.contentLength();
        } catch (IOException e) {
            return -1;
        }
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        countingSink = new CountingSink(sink);

        BufferedSink bufferedSink = Okio.buffer(countingSink);
        delegate.writeTo(bufferedSink);

        bufferedSink.flush();
    }

    protected  final class CountingSink extends ForwardingSink{

        private long byteWriten;


        public CountingSink(Sink delegate) {
            super(delegate);
        }

        @Override
        public void write(Buffer source, long byteCount) throws IOException {
            super.write(source, byteCount);
            byteWriten += byteCount;
            listener.onRequestProgress(byteWriten,contentLength());
        }
    }

    public interface Listener {
        void onRequestProgress(long byteWrited,long contentLength);
    }
}

在上传文件添加监听,将前面的代码改造如下:

private void doUpload() {
        File file = new File(Environment.getExternalStorageDirectory(),"test.jpg");
        if(!file.exists()){
            Log.e("TAG","file is not exit!");
            return ;
        }

        RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"),file);

        MultipartBody multipartBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("username","123465")
                .addFormDataPart("passwd","pppp")
                .addFormDataPart("mPhoto","test2.jpg",requestBody)
                .build();
        CountingRequestBody countingRequestBody = new CountingRequestBody(multipartBody, new CountingRequestBody.Listener() {
            @Override
            public void onRequestProgress(long byteWrited, long contentLength) {
                    Log.d("上传进度",byteWrited+" / "+contentLength);
            }
        });

        Request.Builder builder = new Request.Builder();
        Request request = builder.post(countingRequestBody).url(BASE_URL+"uploadImg").build();
        execute(request);
    }

封装篇

可以看出来,使用OkHttp来进行网络请求的时候,基本就是四个步骤,我们可以将这四个方法写到一个类中,进行简单的封装,就能实现一句话实现一个网络请求。但是这不是重点,重点还是得知道怎么使用。在了解了怎么使用之后,我们就可以从自己的使用角度进行更加合适的封装。

细节篇

可能你会有疑问,okhttp就这么简单?如果我需要设置一些参数(譬如设置缓存、超时设置等等)该怎么办。哈哈,这并不是这篇文件的主要内容,本篇旨在了解怎么使用okhttp的使用。如果需要了解,可以参考这篇文章或者这篇.


最后,源码下载

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容