转载于:https://www.jianshu.com/p/9aa969dd1b4d 如果你已经掌握了上面的两种基本的步骤,那下面的内容就比较简单了
上面我们的post的参数是通过构造一个FormBody通过键值对的方式来添加进去的,其实post方法需要传入的是一个RequestBody对象,FormBody是RequestBody的子类,但有时候我们常常会遇到要传入一个字符串的需求,比如客户端给服务器发送一个json字符串,那这种时候就需要用到另一种方式来构造一个RequestBody如下所示:
RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain;charset=utf-8"), "{username:admin;password:admin}");
上面的MediaType我们指定传输的是纯文本,而且编码方式是utf-8,通过上面的方式我们就可以向服务端发送json字符串啦。
注:关于MidiaType的类型你可以百度搜索mime type查看相关的内容,这里不再赘述
理解了上面一个,下面这个就更简单了,这里我们以上传一张图片为例,当然你也可以上传一个txt什么的文件,都是可以的
其实最主要的还是构架我们自己的RequestBody,如下图构建
File file = new File(Environment.getExternalStorageDirectory(), "1.png"); if (!file.exists()){ Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show(); }else{ RequestBody requestBody2 = RequestBody.create(MediaType.parse("application/octet-stream"), file); }
这里我们将手机SD卡根目录下的1.png图片进行上传。代码中的application/octet-stream表示我们的文件是任意二进制数据流,当然你也可以换成更具体的image/png
注:最后记得最重要的一点:添加存储卡写权限,在AndroidManifest.xml文件中添加如下代码:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
我们在网页上经常会遇到用户注册的情况,需要你输入用户名,密码,还有上传头像,这其实就是一个表单,那么接下来我们看看如何利用OkHttp来进行表单提交。经过上面的学习,大家肯定也懂,主要的区别就在于构造不同的RequestBody传递给post方法即可.
由于我们使用的是OkHttp3所以我们还需要再导入一个包okio.jar才能继续下面的内容,我们需要在模块的Gradle文件中添加如下代码,然后同步一下项目即可
compile 'com.squareup.okio:okio:1.11.0'
这里我们会用到一个MuiltipartBody,这是RequestBody的一个子类,我们提交表单就是利用这个类来构建一个RequestBody,下面的代码我们会发送一个包含用户民、密码、头像的表单到服务端
File file = new File(Environment.getExternalStorageDirectory(), "1.png"); if (!file.exists()){ Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show(); return; } RequestBody muiltipartBody = new MultipartBody.Builder() //一定要设置这句 .setType(MultipartBody.FORM) .addFormDataPart("username", "admin")// .addFormDataPart("password", "admin")// .addFormDataPart("myfile", "1.png", RequestBody.create(MediaType.parse("application/octet-stream"), file)) .build();
上面添加用户民和密码的部分和我们上面学习的提交键值对的方法很像,我们关键要注意以下几点:
(1)如果提交的是表单,一定要设置setType(MultipartBody.FORM)这一句
(2)提交的文件addFormDataPart()的第一个参数,就上面代码中的myfile就是类似于键值对的键,是供服务端使用的,就类似于网页表单里面的name属性,例如下面:
<input type="file" name="myfile">(3)提交的文件addFormDataPart()的第二个参数文件的本地的名字,第三个参数是RequestBody,里面包含了我们要上传的文件的路径以及MidiaType
(4)记得在AndroidManifest.xml文件中添加存储卡读写权限
除了上面的功能,我们最常用的功能该有从网路上下载文件,我们下面的例子将演示下载一个文件存放在存储卡根目录,从网络下载一张图片并显示到ImageView中
1 . 从网络下载一个文件(此处我们以下载一张图片为例)
public void downloadImg(View view){ OkHttpClient client = new OkHttpClient(); final Request request = new Request.Builder() .get() .url("https://www.baidu.com/img/bd_logo1.png") .build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e("moer", "onFailure: ");; } @Override public void onResponse(Call call, Response response) throws IOException { //拿到字节流 InputStream is = response.body().byteStream(); int len = 0; File file = new File(Environment.getExternalStorageDirectory(), "n.png"); FileOutputStream fos = new FileOutputStream(file); byte[] buf = new byte[128]; while ((len = is.read(buf)) != -1){ fos.write(buf, 0, len); } fos.flush(); //关闭流 fos.close(); is.close(); } }); }你会发现步骤与进行一般的Get请求差别不大,唯一的区别在于我们在回调函数中所做的事,我们拿到了图片的字节流,然后保存为了本地的一张图片
2 . 从网络下载一张图片并设置到ImageView中
其实学会了上面的步骤你完全可以将图片下载到本地后再设置到ImageView中,当然下面是另一种方法 这里我们使用BitmapFactory的decodeStream将图片的输入流直接转换为Bitmap,然后设置到ImageView中,下面只给出onResponse()中的代码.
@Override public void onResponse(Call call, Response response) throws IOException { InputStream is = response.body().byteStream(); final Bitmap bitmap = BitmapFactory.decodeStream(is); runOnUiThread(new Runnable() { @Override public void run() { imageView.setImageBitmap(bitmap); } }); is.close(); }
我们一直都说,用户体验很重要,当我们下载的文件比较大,而网速又比较慢的时候,如果我们只是在后台下载或上传,没有给用户显示一个进度,那将是非常差的用户体验,下面我们就将简单做一下进度的显示,其实非常简单的
1 . 显示文件下载进度
这里只是演示,我只是把进度显示在一个TextView中,至于进度的获取当然是在我们的回调函数onResponse()中去获取
(1)使用response.body().contentLength()拿到文件总大小
(2)在while循环中每次递增我们读取的buf的长度
@Override public void onResponse(Call call, Response response) throws IOException { InputStream is = response.body().byteStream(); long sum = 0L; //文件总大小 final long total = response.body().contentLength(); int len = 0; File file = new File(Environment.getExternalStorageDirectory(), "n.png"); FileOutputStream fos = new FileOutputStream(file); byte[] buf = new byte[128]; while ((len = is.read(buf)) != -1){ fos.write(buf, 0, len); //每次递增 sum += len; final long finalSum = sum; Log.d("pyh1", "onResponse: " + finalSum + "/" + total); runOnUiThread(new Runnable() { @Override public void run() { //将进度设置到TextView中 contentTv.setText(finalSum + "/" + total); } }); } fos.flush(); fos.close(); is.close(); }2 . 显示文件上传进度
对于上传的进度的处理会比较麻烦,因为具体的上传过程是在RequestBody中由OkHttp帮我们处理上传,而且OkHttp并没有给我们提供上传进度的接口,这里我们的做法是自定义类继承RequestBody,然后重写其中的方法,将其中的上传进度通过接口回调暴露出来供我们使用。
public class CountingRequestBody extends RequestBody { //实际起作用的RequestBody private RequestBody delegate; //回调监听 private Listener listener; private CountingSink countingSink; /** * 构造函数初始化成员变量 * @param delegate * @param listener */ public CountingRequestBody(RequestBody delegate, Listener listener){ this.delegate = delegate; this.listener = listener; } @Override public MediaType contentType() { return delegate.contentType(); } @Override public void writeTo(BufferedSink sink) throws IOException { countingSink = new CountingSink(sink); //将CountingSink转化为BufferedSink供writeTo()使用 BufferedSink bufferedSink = Okio.buffer(countingSink); delegate.writeTo(bufferedSink); bufferedSink.flush(); } protected final class CountingSink extends ForwardingSink{ private long byteWritten; public CountingSink(Sink delegate) { super(delegate); } /** * 上传时调用该方法,在其中调用回调函数将上传进度暴露出去,该方法提供了缓冲区的自己大小 * @param source * @param byteCount * @throws IOException */ @Override public void write(Buffer source, long byteCount) throws IOException { super.write(source, byteCount); byteWritten += byteCount; listener.onRequestProgress(byteWritten, contentLength()); } } /** * 返回文件总的字节大小 * 如果文件大小获取失败则返回-1 * @return */ @Override public long contentLength(){ try { return delegate.contentLength(); } catch (IOException e) { return -1; } } /** * 回调监听接口 */ public static interface Listener{ /** * 暴露出上传进度 * @param byteWritted 已经上传的字节大小 * @param contentLength 文件的总字节大小 */ void onRequestProgress(long byteWritted, long contentLength); } }
上面的代码注释非常详细,这里不再解释,然后我们在写具体的请求时还需要做如下变化
File file = new File(Environment.getExternalStorageDirectory(), "1.png"); if (!file.exists()){ Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show(); }else{ RequestBody requestBody2 = RequestBody.create(MediaType.parse("application/octet-stream"), file); } //使用我们自己封装的类 CountingRequestBody countingRequestBody = new CountingRequestBody(requestBody2, new CountingRequestBody.Listener() { @Override public void onRequestProgress(long byteWritted, long contentLength) { //打印进度 Log.d("pyh", "进度 :" + byteWritted + "/" + contentLength); } });上面其实就是在原有的RequestBody上包装了一层,最后在我们的使用中在post()方法中传入我们的CountingRequestBody对象即可。