Android 网络框架之 OkHttp 基础使用

以下代码基于 OkHttp 3.6.0 版本且后台为 Java,基本使用 OkHttp 的场景如下:

先说下一般的请求编写的步骤:
1. 构造 Request 实例,其中 url 为必须的,其他选项在链式操作中添加。
2. 构造 RequestBody ( get 请求时则无)
3. 获得 OkHttpClient 实例,然后根据 Request 实例 构造出 Call 实例
4. 提交执行 Call 任务(可异步、同步),然后在回调中处理自己的逻辑

直接来看代码把,需要注意是这边为了方便,就使用了全局的 OkHttpClient 实例。另外服务端代码会在最后贴出。

public static final String BASE_URL = "http://xx.xx.xx.xx:8080/OkHttpServer/";
    // cookieJar 等后面的操作是为了 session 保持
    // 这边仅是保存在内存中,当然也可以持久化
    private OkHttpClient okHttpClient = new OkHttpClient.Builder().cookieJar(new CookieJar() {
        private final HashMap> cookieStore = new HashMap<>();

        @Override
        public void saveFromResponse(HttpUrl url, List cookies) {
            cookieStore.put(url.host(), cookies);
        }

        @Override
        public List loadForRequest(HttpUrl url) {
            List cookies = cookieStore.get(url.host());
            return cookies != null ? cookies : new ArrayList();
        }
    }).build();

get 请求

客户端代码:

private void doGet() {
        // 构造的请求
        Request request = new Request.Builder()
                .url(BASE_URL + "login?username=ss&password=123")
                .build();
        final Call call = okHttpClient.newCall(request);
        // 异步请求方式
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println("response body = " + response.body().string());
            }
        });

        // 同步请求
        /*// 由于同步,将不可在主线程中调用,耗时操作将阻塞主线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Response response = call.execute();
                    System.out.println("response body = " + response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();*/

    }

客户端接收的 Response

I/System.out: response body = login success!username=ss  password = 123

说明:

同步与异步请求的方式均已写出,但异步请求中的匿名类 CallBack 中的 onResponse 回调方法,仍然不是处于主线程中,这边为了偷懒而直接将信息打印在控制台中。因此在 onResponse 中若有操作 UI ,记得要切回 UI 线程!

post 请求

private void doPost() {
        // 表单构造,携带参数
        FormBody body = new FormBody.Builder()
                .add("username", "Jack")
                .add("password", "123")
                .build();
        Request request = new Request.Builder()
                .url(BASE_URL + "login") // 对比 get 请求
                .post(body)
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println("response body = " + response.body().string());
            }
        });
    }

客户端接收的 Response

I/System.out: response body = login success!username=Jack  password = 123

post 上传文件

private void doPostFile() {
        File file = new File(Environment.getExternalStorageDirectory(), "testupload.apk");
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);

        Request request = new Request.Builder()
                .url(BASE_URL + "postFile")
                .post(requestBody)
                .build();

        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println("response body = " + response.body().string());
            }
        });

说明:这边的 RequestBody 的构造通过 RequestBody.create(MediaType.parse("application/octet-stream"), file);由抽象类 RequestBody 中多个重载的 create 方法可知,比如想要仅仅上传给服务端一个字符串,可以写成 RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), "server!can you see me?"); 具体类型可查看 create 源码。

表单上传,且获得进度反馈

经常性的需求是上传给服务端普通文件/图片也好,需要反馈给用户实时的上传进度。

private void postMultipart() {
        // 表单提交包括用户名、密码和一个文件
        MultipartBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM) // multipart/form-data 类型的表单提交
                .addFormDataPart("username", "Jack")
                .addFormDataPart("password", "123")
                .addFormDataPart("file", "multipar.apk", RequestBody.create(MediaType.parse("application/octet-stream")
                        , new File(Environment.getExternalStorageDirectory(), "testupload.apk")))
                .build();

        // 包装 RequestBody
        CountingRequestBody body = new CountingRequestBody(requestBody, new CountingRequestBody.Listener() {
            @Override
            public void onProcessUpload(long byteWritten, long contentLength) {
                // 已写出的 byte 长度除以文件总的 byte 数
                System.out.println(((float) byteWritten) / contentLength * 100 + "%");
            }
        });

        Request request = new Request.Builder()
                .url(BASE_URL + "postMultipart")
                .post(body)
                .build();

        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println("response body = " + response.body().string());
            }
        });
    }

public class CountingRequestBody extends RequestBody {

    // 真正的包含数据的 RequestBody
    private RequestBody delegate;
    private Listener mListener;

    private CountingSink countingSink;

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

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

    @Override
    public long contentLength() throws IOException {
        try {
            return delegate.contentLength();
        } catch (Exception 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();

    }

    interface Listener {
        void onProcessUpload(long byteWritten, long contentLength);
    }

    protected final class CountingSink extends ForwardingSink {

        private long byteWritten;


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

        @Override
        public void write(Buffer source, long byteCount) throws IOException {
            super.write(source, byteCount);
            byteWritten += byteCount;
            mListener.onProcessUpload(byteWritten, contentLength());
        }
    }

}

可参看 hongyang 大神的 Android网络框架-OkHttp使用

多文件上传

private void postMultiFile() {
        MultipartBody.Builder builder = new MultipartBody.Builder();
        builder.setType(MultipartBody.FORM);
        builder.addFormDataPart("username", "Jack");
        builder.addFormDataPart("password", "123");
        // 虽然上传的同一个文件,但后台确实收到了上传了 3 次不同命名的 apk 文件
        // 这里仅是为了方便起见,改了文件名 i + "multipart.apk"
        for (int i = 0; i < 3; i++) {
            builder.addFormDataPart("files", i + "multipart.apk", RequestBody.create(MediaType.parse("application/octet-stream")
                    , new File(Environment.getExternalStorageDirectory(), "testupload.apk")));
        }
        // 一个 xml
        builder.addFormDataPart("files", "jay.xml", RequestBody.create(MediaType.parse("application/octet-stream")
                , new File(Environment.getExternalStorageDirectory(), "jay.xml")));

        MultipartBody body = builder.build();
        Request request = new Request.Builder()
                .url(BASE_URL + "postMultipleFile")
                .post(body)
                .build();

        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println("response body = " + response.body().string());
            }
        });
    }

可参看 Upload multiple files at once to a Struts2 @Action

下载文件

// 为了简单方便,文件直接给的是 url 服务端文件路径,而不涉及接口调用
private void downloadImage() {
        Request request = new Request.Builder()
                .url(BASE_URL + "files/author_avatar.png")
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

                // 若对图片有其他的要求,再额外进行设置
                final Bitmap bitmap = BitmapFactory.decodeStream(response.body().byteStream());
                // 仅是展示出来
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        imageView.setImageBitmap(bitmap);
                    }
                });

                // 当然既然可以拿到 response.body().byteStream() 的输入流
                // 所以就将文件可以保存到手机上
//                FileOutputStream fos = new FileOutputStream(new File(
//                        Environment.getExternalStorageDirectory(), "author_avatar.png"));
//                InputStream inputStream = response.body().byteStream();
//                int len = 0;
//                byte[] buffer = new byte[1024];
//                while ((len = inputStream.read(buffer)) != -1) {
//                    fos.write(buffer, 0, len);
//                }
//                fos.flush();
//                fos.close();
//                inputStream.close();
            }
        });
    }

说明:这边是以将输入流转为 Bitmap 展示图片为例而已,实际上拿到了 response.body().byteStream() 的输入流要很容易通过 IO 保存起来了。

服务端代码:

public class UserAction extends ActionSupport {

    private String username;
    private String password;

    private File mPhoto;
    private String mPhotoFileName;
    private List files;
    private List filesFileName;

    public void login() {
        System.out.println("username = " + username + "  password = "
                + password);
        HttpServletResponse response = ServletActionContext.getResponse();
        try {
            PrintWriter writer = response.getWriter();
            writer.write("login success!username=" + username + "  password = "
                    + password);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    /**
     * 获取客户端传来的字符串,再返回给客户端
     */
    public void postString() {

        try {
            System.out.println("postString is running");
            HttpServletResponse response = ServletActionContext.getResponse();
            HttpServletRequest request = ServletActionContext.getRequest();
            InputStream is = request.getInputStream();
            StringBuffer sb = new StringBuffer();
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = is.read(buffer)) != -1) {
                sb.append(new String(buffer, 0, len));
            }
            PrintWriter writer = response.getWriter();
            writer.write("your string is = " + sb.toString());

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 响应客户端上传的文件
     */
    public void postFile() {
        System.out.println("postFile is runnning");
        HttpServletResponse response = ServletActionContext.getResponse();
        try {
            HttpServletRequest request = ServletActionContext.getRequest();
            InputStream is = request.getInputStream();
            String dir = ServletActionContext.getServletContext().getRealPath(
                    "files");
            File path = new File(dir);
            if (!path.exists()) {
                path.mkdirs();
            }
            FileOutputStream fos = new FileOutputStream(new File(path,
                    "12306.apk"));

            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = is.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
            fos.flush();
            fos.close();
            is.close();
            PrintWriter writer = response.getWriter();
            writer.write("upload file success!");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 相应客户端多文件上传
     */
    public void postMultipleFile() {
        // 检验是否上传的是多个文件,size 为正确
        System.out.println("files size = " + files.size());
        String dir = ServletActionContext.getServletContext().getRealPath(
                "files");
        File path = new File(dir);
        if (!path.exists()) {
            path.mkdirs();
        }
        try {
            // 根据文件名保存文件
            for (int i = 0; i < files.size(); i++) {
                File file = new File(path, filesFileName.get(i));
                FileUtils.copyFile(files.get(i), file);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    /**
     * 响应客户端上传的用户名、密码和一个文件
     */
    public void postMultipart() {
        System.out.println("username = " + username + "  password = "
                + password);
        System.out.println("postMultipart is runnning");
        HttpServletResponse response = ServletActionContext.getResponse();
        try {
            String dir = ServletActionContext.getServletContext().getRealPath(
                    "files");
            File path = new File(dir);
            if (!path.exists()) {
                path.mkdirs();
            }
            File file = new File(path, mPhotoFileName);
            FileUtils.copyFile(mPhoto, file);
            PrintWriter writer = response.getWriter();
            writer.write("your username = " + username + "  password = "
                    + password);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public void downloadFile() {
        System.out.println("invoke downloadfile");
        HttpServletResponse response = ServletActionContext.getResponse();
        File file = new File(ServletActionContext.getServletContext()
                .getRealPath("files"), "12306.apk");
        try {
            FileInputStream fis = new FileInputStream(file);
            OutputStream os = response.getOutputStream();
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = fis.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            os.flush();
            os.close();
            fis.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    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;
    }

    public File getmPhoto() {
        return mPhoto;
    }

    public void setmPhoto(File mPhoto) {
        this.mPhoto = mPhoto;
    }

    public String getmPhotoFileName() {
        return mPhotoFileName;
    }

    public void setmPhotoFileName(String mPhotoFileName) {
        this.mPhotoFileName = mPhotoFileName;
    }

    public List getFiles() {
        return files;
    }

    public void setFiles(List files) {
        this.files = files;
    }

    public List getFilesFileName() {
        return filesFileName;
    }

    public void setFilesFileName(List filesFileName) {
        this.filesFileName = filesFileName;
    }

}

参考:

Android OkHttp完全解析 是时候来了解OkHttp了

Android网络框架-OkHttp使用

你可能感兴趣的:(Android基础)