原 Android进阶:步骤三:Android常用框架:OkHttp网络操作框架

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

                }
            }
        });
    }

猜你喜欢

转载自blog.csdn.net/qq_17846019/article/details/83417326
今日推荐