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