Okio & OkHttp
课程目标
- 掌握I/O操作的方法
- 掌握传输数据的方法
学习内容
- Okio简介
- Okio的核心类
- OkHttp简介
- OkHttp核心类
- 代码实践
一、Okio简介
什么叫IO?
比如说你的硬盘上有一个文件in.txt
你想把它拷贝到另一个文件里去
然后你要写一段程序,运行这段程序
计算机把这段程序加载带内存当中
程序当中申请一个buffer把输入文件的内容,输入到buffer里面来,这叫输入(站在程序自身的角度来看是数据的流入,叫读取)
把buffer里面的数据写到目标文件,这叫输出(叫写入)
历史
- java.io
- java.nio
- okio :okio-2.1.0.jar okio的依赖包
作用
- 读取数据(Buffer)
- 存储数据(Buffer)
- 处理数据(ByteString) Easier(okio使这几个操作实现更简单)
ByteString(处理数据)
String:字符串,它的背后是字符数组,将字符串起来输出
ByteString:字节串,(它的背后是字节数组。把一些字节集结在一起)计算机底层是利用字节来处理数据的
创建ByteString对象
-
okio.ByteString #of(byte... data) 输入是字节,输出ByteString对象
-
okio.ByteString #encodeUtf8(String str) 输入是字符串(String类型)
-
okio.ByteString #decodeHex(String hex) 输入是16进制串
-
okio.ByteString #decodeBase64(String base64) 输入base64串
-
okio.ByteString #read(InputStream in,int byteCount)
ByteString的常用方法
-
base64( ) 返回编码值
-
hex() 返回16进制
-
md5() 返回校验值
-
sha1() 返回校验值
-
write(java.io.OutputStream) 把自己直接写的输出流中
Base64编码和解码:(常见的网络操作之一)
base64可以完成和二进制数据之间的转换
可以把二进制(图片文件,音频文件)通过base64编码转换成文本数据
然后得到的文本数据又可以通过base64解码转化成对应的二进制数据
为什么要这样的转换?
计算机底层以字节为单位使用二进制来存储数据,一个字节是8个比特位,除去一个符号位,还有7个有效位
2的7次方是128,正好对应Uscall编码,Uscall编码有些字符是不可打印的,如何把所有字符都表示成可以打印呢?
简单的来说,是可以放到记事本里,base64就提供了这样的方案
有base64技术我们就可以把图片的二进制放到放到json这样的文本格式里面
import okio.ByteString;
public class OkioDemo {
public static void main(String[] args)
{
//字符串
String str ="This is a String ";
System.out.println(str.length());
//字节串
/**
* 使用ByteString之前要导入okio的依赖包
*/
ByteString byteString=ByteString.encodeUtf8(str);
System.out.println(byteString);
//获取 str 的base64的编码值
String base64= byteString.base64();
System.out.println(base64);
//str的sha1的校验值
String sha1=byteString.sha1().hex();
System.out.println("sha1的校验值"+sha1);
}
}
可以看到str的value是一个字符数组(长度为17)
可以看到字节串的值data是一个字节数组长度也为17
获取str的base64编码值
获取str的MD5值
MD5(校验值)
MD5是一种算法,它能够获取原始内容的信息校验和,输入可以是文件也可以是一个字符串
输出是一个128比特的校验和
它有什么作用?
MD5可以理解为人的指纹(不同的指纹是不同的,不同文件的MD5值也是不同的)
如果我们把一个文件放到网上给别人下载,怎么保证别人下载的文件和我们提供的一致呢?
我们就可以同时在这个网站上提供这个文件的MD5值,当他下载完成之后,它可以获取下载后的文件的MD5
然后两个MD5值比对,指纹一样(MD5值),才代表文件一致,没别篡改过
除此之外还有sha1校验
MD5校验可以用的密码存储领域
比如在网上上登录的时候,用户输入的密码不能再数据库进行明文存储的
就可以存储输入输入这个密码的校验和(MD5值,即使别人拿到这个MD5值也是不可以逆的)
//获取 str的md5值
ByteString md5=byteString.md5();
System.out.println("md5:"+md5);
System.out.println("md5值: "+md5.hex());//md5值放到16进制里面的
//str的sha1的校验值
String sha1=byteString.sha1().hex();
System.out.println("sha1的校验值"+sha1);
包括上面的,输出结果
str:This is a String
byString:[text=This is a String ]
base64:VGhpcyBpcyBhIFN0cmluZyA=
md5:[hex=b3a6107c01927c0cf967787368139c9e]
md5值: b3a6107c01927c0cf967787368139c9e
sha1的校验值f64c1c31a099b3dfb3104951d55d900606ed4e0c
解码上面的base64
//解码base64
ByteString decodeBase64 = ByteString.decodeBase64(base64);
System.out.println("base64的值是:"+base64);
System.out.println("解码base64的值:"+decodeBase64);
输出结果
base64的值是:VGhpcyBpcyBhIFN0cmluZyA=
解码base64的值:[text=This is a String ]
ByteString通过InputStream来创建
//通inputstream来创建ByteString对象 图片绝对路径,放在同一个文件夹里面
FileInputStream in = new FileInputStream("/Users/mac/Desktop/Android2Step3/okhttpdemo/src/main/java/com/demo/okhttpdemo/okio/in.png");
ByteString read = ByteString.read(in,in.available());
System.out.println("读取图片:"+read);
//通过输出流来将数据写入到另外一个文件中 out.png
FileOutputStream out =new FileOutputStream("/Users/mac/Desktop/Android2Step3/okhttpdemo/src/main/java/com/demo/okhttpdemo/okio/out.png");
read.write(out);
//关闭流
in.close();
out.close();
读取图片:[size=2469 hex=89504e470d0a1a0a0000000d49484452000000c8000000c80806000000ad58ae9e0000096c49444154789cedddeb55e4ba1286e12f0487e0103a837106430647…]
Okio-Buffer(读取和存储数据)
Source接口(数据源(输入流)水龙头)提供的方法 和InputStream一一对应
- read(Buffer sink,long byteCount)
- timeout()//超时处理
- close()//关闭流
Sink接口(数据接收方(输出流)排水口)和OutputStream一一对应
- write(Buffer source,long byteCount)//写数据的方法
- flush()//把缓存数据写到目的地
- timeout()//超时处理
- close()//关闭
Buffer(像移动电源,可以充电,可以放电)
水龙头可能连着一个热水器
排水口可能连着一个小水槽
自带buffer的Source和Sink 目的是提供读写的效率
- okio.BufferedSource
- okio.BufferedSink
Buffer可以是数据的供应方,也可以数据的接收方
(像我们使用的 移动电源)
- 可以提供电量
- 也可以接收电量(充电)
Buffer和source和sink的用法
/**
* Buffer类似移动电源 可以提供电也可以放电(缓存的作用)
*/
public class BufferDemo {
public static void main(String[] args) throws EOFException {
//新建Buffer对象
Buffer buffer=new Buffer();
System.out.println("移动电源开始的状态:"+ buffer);
//往Buffer里面添加数据(充电)
buffer.writeUtf8("abc");
System.out.println("充电后:"+buffer);
//读取buffer里面的数据
while (!buffer.exhausted()){//只有buffer没有枯竭(还有电)
//每次读1比特,每读一个buffer里就少一个数据
System.out.println(buffer.readUtf8(1));
}
}
}
输出结果
移动电源开始的状态:[size=0]
充电后:[text=abc]
a
b
c
往Buffer里面读整数也行
//写入整数
for (int i = 0; i <10 ; i++) {
buffer.writeInt(i);
}
//读
while (!buffer.exhausted()){
System.out.println(buffer.readInt());
}
Buffer读图片
//Buffer读图片
System.out.println("没读取数据前:" + buffer);
FileInputStream in = new FileInputStream("/Users/mac/Desktop/Android2Step3/okhttpdemo/src/main/java/com/demo/okhttpdemo/okio/in.png");
buffer.readFrom(in);
System.out.println("读取数据后" + buffer);
//Buffer读到的数据写到一个新的文件里 out2.png
FileOutputStream out = new FileOutputStream("/Users/mac/Desktop/Android2Step3/okhttpdemo/src/main/java/com/demo/okhttpdemo/okio/out2.png");
buffer.writeTo(out);
//写完数据后应该为空了(充电宝没电了)
System.out.println("写完完数据后" + buffer);
没读取数据前:[size=0]
读取数据后[size=2469 hex=89504e470d0a1a0a0000000d49484452000000c8000000c80806000000ad58ae9e0000096c49444154789cedddeb55e4ba1286e12f0487e0103a837106430647…]
写完完数据后[size=0]
source和sink的用法
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import okio.BufferedSink;
import okio.BufferedSource;
import okio.Okio;
/**
* ok里面的读取数据的方法
* Source相当于 FileInputStream 数据源(连着数据的输入方)读数据(对程序本身而言)
* Source和FileInputStream一一对应
* Sink相当于FileOutputStream 数据接收(连着数据的接收方)(输出)写数据(对程序本身而言)
* Sink和FileOutPutStream一一对应
*/
public class BufferSourceAndSink {
public static final String InPath = "/Users/mac/Desktop/Android2Step3/okhttpdemo/src/main/java/com/demo/okhttpdemo/okio/in.txt";
public static final String OutPath = "/Users/mac/Desktop/Android2Step3/okhttpdemo/src/main/java/com/demo/okhttpdemo/okio/out.txt";
public static void main(String[] args) throws IOException {
//将in.txt 拷贝到out.txt
//数据供应方
BufferedSource source = Okio.buffer(Okio.source(new FileInputStream(InPath)));
//数据的接收方
BufferedSink sink = Okio.buffer(Okio.sink(new File(OutPath)));
//将数据源的数据读到数据的接收方里
source.readAll(sink);
//记得关闭源(水龙头)等
source.close();
sink.close();
}
}
OkHttp简介
Http Client的作用 它直接与服务器打交道
1.处理app请求
2.给app响应
历史
Apache HttpClient
HttpURLConnection android原生的
OkHttp 更方便
添加OkHttp依赖包
Request(创建请求)的组成
URL:请求资源地址
method :请求的方法GET(从服务器获取数据) POST(想服务器提交数据)
headers :请求头
body:请求体(放具体资源的)
Get方法
//1基于构建者模式
Request.Builder builder = new Request.Builder();
//请求的url地址
builder.url("http://httpbin.org/get");
//构建request对象
Request request = builder.build();
Call
//想客户端提交请求
Call call = client.newCall(request);
//执行后客户端给app做出响应
// 这是一个同步阻塞的过程
Response response = call.execute();
Response 响应的组成
code:响应码
headers:响应头
body:响应体
Call
//创建呼叫对象
Call call = client.newCall(request);
//把请求加入到队列里面去,加完之后程序继续执行,不在这里阻塞
//传入一个callback对象
call.enqueue(callback);
Callback
Callback callback = new Callback() {
@Override
public void onFailure(Call call, IOException e) { }
@Override
public void onResponse(Call call, Response response) throws IOException {
} };
代码演示进行okHttp的get请求(同步请求(阻塞))
首先要进行网络请求要申请网络权限
<uses-permission android:name="android.permission.INTERNET"/>
如果不懂线程池的可以看这篇文章:https://blog.csdn.net/qq_17846019/article/details/83420239
拿到我上面那个线程池文章的内容显示出来
首先新建一个Menu文件夹 新建一个actions.xml文件 就是上面gif里面的那三个小点里的get菜单
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="Get" android:id="@+id/menuGet"/>
</menu>
OkHttpDemo.java
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import com.demo.okhttpdemo.R;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class OkHttpDemo extends AppCompatActivity {
private static final String TAG = "OkHttpDemo";
//传进客户端对象
private final OkHttpClient mClient = new OkHttpClient();
private TextView tv_content;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_content = findViewById(R.id.tvContent);
}
//通过menu的方式添加方法
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.actions, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menuGet:
get();
}
return super.onOptionsItemSelected(item);
}
//拿数据的方法
private void get() {
//新建一个缓存线程池
ExecutorService exectuor = Executors.newCachedThreadPool();
//在线程池里面开一个线程
exectuor.submit(new Runnable() {
@Override
public void run() {
//都要在子线程进行网络操作
//利用建造者拿到新建请求对象
Request.Builder builder = new Request.Builder();
//添加url 这是我的博客线程池的文章
builder.url("https://blog.csdn.net/qq_17846019/article/details/83420239");
//建造完成后返回请求对象
Request request = builder.build();
Log.d(TAG, "request对象是: run:" + request);
//将请求传到客户端调用的newCall(请求)方法中得到呼叫对象
Call call = mClient.newCall(request);
//将执行呼叫,返回客户端响应
try {
Response response = call.execute();
//如果客户端返回的响应是成功的
if (response.isSuccessful()) {
//拿到响应的响应体,获取响应体的内容(拿到结果)
final String string = response.body().string();
//将结果显示到Ui的text里面
//返回到ui线程
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_content.setText(string);
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
exectuor.shutdown();
}
}
代码演示进行okHttp的Response(异步请求)
//response的异步请求获取数据
private void response() {
//创建一个ruquest的Bulider
Request.Builder builder = new Request.Builder();
//添加请求资源地址
builder.url("https://blog.csdn.net/qq_17846019/article/details/83420239");
//创建request对象
final Request request = builder.build();
//将请求给客户端后得到呼叫对象
Call call = mClient.newCall(request);
//异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "ononFailure() call with: call= [" + call + "]" + "e= [ " + e + " ]");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "ononFailure() call with: call= [" + call + "]" + "response= [ " + response + " ]");
//获得响应码
int code = response.code();
//获得响应头
//响应头里面是以key-value 形式的
Headers headers = response.headers();
//获得响应体
String content = response.body().string();
final StringBuilder buf = new StringBuilder();
buf.append("code: " + code);
buf.append("\nHeaders: " + headers);
buf.append("\nbody: " + content);
//显示到ui上
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_content.setText(buf.toString());
}
});
}
});
}
有没有疑惑?为啥这边的异步不开子线程呢
这个很好理解的啦,异步的意思就是个AsyncTask的感觉是一样的,也就是说写在这里面的代码已经是在别的线程中进行处理了,所以说我们不需要在单独创建一个线程池来进行处理,反而你可以注意到,当我们拿到response中的数据的时候,我们就得调用runOnUithread的方法回到ui线程中来对一部分控件进行处理。而同步的意思就是我们的起跑线都是一样的,当开始跑的时候我发现我的有一部分代码是需要做一些耗时操作的,所以这个时候我就需要创建线程池然后进行同样的耗时操作。
同步和异步是根据你的需求来决定选择,如果你需要得到返回的结果才能继续往下执行就选择同步execute(),如果不需要返回结果没有关联只要执行就可以采用异步方式enqueue()
OkHttp-post请求
向对方服务器提交一个他们能够接受的格式的数据,然后服务器返回响应(处理过后了的结果)显示出来
//okHttp的一个指定向服务器添加的数据类型
private static final MediaType MEDIA_TYPE_MARKDOWN
=MediaType.parse("text/x-markdown;charset=utf-8");
//提交数据的的链接
private static final String POST_URL="https://api.github.com/markdown/raw";
.....
上面的代码
//Post方法提交数据
private void post() {
//创建一个request的Builder
Request.Builder builder = new Request.Builder();
builder.url(POST_URL);
//参数1:向服务器提交的数据类型上面指定好了
//参数2:提交的内容
builder.post(RequestBody.create(MEDIA_TYPE_MARKDOWN,
"Hello world github/linguist#1 **cool**,and #1!"));
//得到request对象
Request request = builder.build();
//将request传给客户端得到一个呼叫对象
Call call = mClient.newCall(request);
//异步提交
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//如果提交数据客户端响应成功
if(response.isSuccessful()){
//拿到数据
final String content = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_content.setText(content);
}
});
}
}
});
}