編輯:關於Android編程
從上至下我們先從Request類開始閱讀
一.Request
每一次網絡請求都是一個Request,Request是對url,method,header,body的封裝,也是對Http協議中請求行,請求頭,實體內容的封裝
Request request = new Request .Builder() .url(url) .post(body) .addHeader("Accept","*/*") .cacheContro() .build();
private final HttpUrl url;//請求url封裝 private final String method;//請求方法 private final Headers headers;//請求頭 private final RequestBody body;//請求體,也就是http協議的實體內容 private final Object tag;//被請求的標簽 private volatile URL javaNetUrl; // Lazily initialized. private volatile URI javaNetUri; // Lazily initialized. private volatile CacheControl cacheControl; // 緩存控制的封裝
HttpUrl主要用來規范普通的url連接,並且解析url的組成部分
我們來看一下url的構成;scheme://username:password@host:port/pathSegment/pathSegment?queryParameter#fragment;
現通過下面的例子來示例httpUrl的使用
https://www.google.com/search?q=maplejaw
使用parse解析url字符串:
HttpUrl url = HttpUrl.parse("https://www.google.com/search?q=maplejaw");
通過構造者模式來常見:
HttpUrl url = new HttpUrl.Builder() .scheme("https") .host("www.google.com") .addPathSegment("search") .addQueryParameter("q", "maplejaw") .build();
2.Headers
Headers用於配置請求頭,對於請求頭配置大家一定不陌生吧,比如Content-Type,User-Agent和Cache-Control等等。
創建Headers也有兩種方式。如下:
(1)of()創建:傳入的數組必須是偶數對,否則會拋出異常。
Headers.of("name1","value1","name2","value2",.....);還可以使用它的重載方法of(Map
(2)構建者模式創建:
Headers mHeaders=new Headers.Builder() .set("name1","value1")//set表示name1是唯一的,會覆蓋掉已經存在的 .add("name2","value2")//add不會覆蓋已經存在的頭,可以存在多個 .build();
另外,數組方便取數組嗎?很方便,我們來看著兩個方法
/** Returns the field at {@code position} or null if that is out of range. */ public String name(int index) { int nameIndex = index * 2; if (nameIndex < 0 || nameIndex >= namesAndValues.length) { return null; } return namesAndValues[nameIndex]; } /** Returns the value at {@code index} or null if that is out of range. */ public String value(int index) { int valueIndex = index * 2 + 1; if (valueIndex < 0 || valueIndex >= namesAndValues.length) { return null; } return namesAndValues[valueIndex]; }
@Override public String toString() { StringBuilder result = new StringBuilder(); for (int i = 0, size = size(); i < size; i++) { result.append(name(i)).append(": ").append(value(i)).append("\n"); } return result.toString(); }
2.CacheControl
Cache-Control對應請求頭中Cache-Control中的值,我們先來看一下Http協議中Cache-Control
Cache-Control指定請求和響應遵循的緩存機制。在請求消息或響應消息中設置Cache-Control並不會修改另一個消息處理過程中的緩存處理過程。請求時的緩存指令有下幾種:
Public:所有內容都將被緩存(客戶端和代理服務器都可緩存)。Private:內容只緩存到私有緩存中(僅客戶端可以緩存,代理服務器不可緩存)no-cache:請求或者響應消息不能緩存no-store:不使用緩存,也不存儲緩存max-age:緩存的內容將在指定時間(秒)後失效, 這個選項只在HTTP 1.1可用, 並如果和Last-Modified一起使用時, 優先級較高①常用的函數
final CacheControl.Builder builder = new CacheControl.Builder(); builder.noCache();//不使用緩存,全部走網絡 builder.noStore();//不使用緩存,也不存儲緩存 builder.onlyIfCached();//只使用緩存 builder.noTransform();//禁止轉碼 builder.maxAge(10, TimeUnit.MILLISECONDS);//指示客戶機可以接收生存期不大於指定時間的響應。 builder.maxStale(10, TimeUnit.SECONDS);//指示客戶機可以接收超出超時期間的響應消息 builder.minFresh(10, TimeUnit.SECONDS);//指示客戶機可以接收響應時間小於當前時間加上指定時間的響應。 CacheControl cache = builder.build();//cacheControl
②CacheControl的兩個常量:
public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();//不使用緩存 public static final CacheControl FORCE_CACHE = new Builder() .onlyIfCached() .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS) .build();//只使用緩存③請求時如何使用:
final CacheControl.Builder builder = new CacheControl.Builder(); builder.maxAge(10, TimeUnit.MILLISECONDS); CacheControl cache = builder.build(); final Request request = new Request.Builder().cacheControl(cache).url(requestUrl).build(); final Call call = mOkHttpClient.newCall(request);// call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { failedCallBack("訪問失敗", callBack); Log.e(TAG, e.toString()); } @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { String string = response.body().string(); Log.e(TAG, "response ----->" + string); successCallBack((T) string, callBack); } else { failedCallBack("服務器錯誤", callBack); } } }); return call; } catch (Exception e) { Log.e(TAG, e.toString()); }
//判斷網絡是否連接 boolean connected = NetworkUtil.isConnected(context); if (!connected) { request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build(); }以上這些就是CacheControl類的學習,源碼沒必要看了,雖然很長但是比較簡單,也就是通過方法來選擇使用哪種緩存模式而已
3.RequestBody
requestBody也就是請求實體內容,我們先來看一下如何來構建一個RequestBody
(1)Request.create()方法創建
public static final MediaType TEXT = MediaType.parse("text/plain; charset=utf-8"); public static final MediaType STREAM = MediaType.parse("application/octet-stream"); public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); //構建字符串請求體 RequestBody body1 = RequestBody.create(TEXT, string); //構建字節請求體 RequestBody body2 = RequestBody.create(STREAM, byte); //構建文件請求體 RequestBody body3 = RequestBody.create(STREAM, file); //post上傳json RequestBody body4 = RequestBody.create(JSON, json);//json為String類型的 //將請求體設置給請求方法內 Request request = new Request.Builder() .url(url) .post(xx)// xx表示body1,body2,body3,body4中的某一個 .build();
//構建表單RequestBody RequestBody formBody=new FormBody.Builder() .add("name","maplejaw") .add("age","18") ... .build();
public static final MediaType STREAM = MediaType.parse("application/octet-stream"); //構建表單RequestBody RequestBody multipartBody=new MultipartBody.Builder() .setType(MultipartBody.FORM)//指明為 multipart/form-data 類型 .addFormDataPart("age","20") //添加表單數據 .addFormDataPart("avatar","111.jpg",RequestBody.create(STREAM,file)) //添加文件,其中avatar為表單名,111.jpg為文件名。 .addPart(..)//該方法用於添加RequestBody,Headers和添加自定義Part,一般來說以上已經夠用 .build();
RequestBody也就是請求實體內容,對於一個Get請求時沒有實體內容的,Post提交才有,而且浏覽器與服務器通信時基本上只有表單上傳才會用到POST提交,所以RequestBody其實也就是封裝了浏覽器表單上傳時對應的實體內容
OkHttp3中RequestBody有三種創建方式
①方式一:
public static RequestBody create(MediaType contentType, String content) { Charset charset = Util.UTF_8; if (contentType != null) { charset = contentType.charset();//MediaType的為請求頭中的ContentType創建方式:public static final MediaType TEXT = //MediaType.parse("text/plain; charset=utf-8") if (charset == null) { charset = Util.UTF_8;//如果contentType中沒有指定charset,默認使用UTF-8 contentType = MediaType.parse(contentType + "; charset=utf-8"); } } byte[] bytes = content.getBytes(charset); return create(contentType, bytes); }最終會調用下面的方法
/** Returns a new request body that transmits {@code content}. */ public static RequestBody create(final MediaType contentType, final byte[] content, final int offset, final int byteCount) { if (content == null) throw new NullPointerException("content == null"); Util.checkOffsetAndCount(content.length, offset, byteCount); return new RequestBody() { @Override public MediaType contentType() { return contentType; } @Override public long contentLength() { return byteCount; } @Override public void writeTo(BufferedSink sink) throws IOException { sink.write(content, offset, byteCount); } }; }②方式二:FormBody表單創建,我們來看一下
FormBody用於普通post表單上傳鍵值對,我們先來看一下創建的方法,再看源碼
RequestBody formBody=new FormBody.Builder() .add("name","maplejaw") .add("age","18") ... .build();
private static final MediaType CONTENT_TYPE = MediaType.parse("application/x-www-form-urlencoded");//ContentType,請求頭中 private final List我們主要來看一下方法writeOrCountBytes,通過writeOrCountBytes來計算請求體大小和將請求體寫入BufferedSink。encodedNames; private final List encodedValues; private FormBody(List encodedNames, List encodedValues) { this.encodedNames = Util.immutableList(encodedNames); this.encodedValues = Util.immutableList(encodedValues); } /** The number of key-value pairs in this form-encoded body. */ public int size() { return encodedNames.size(); } public String encodedName(int index) { return encodedNames.get(index); } public String name(int index) { return percentDecode(encodedName(index), true); } public String encodedValue(int index) { return encodedValues.get(index); } public String value(int index) { return percentDecode(encodedValue(index), true); } @Override public MediaType contentType() { return CONTENT_TYPE; } @Override public long contentLength() { return writeOrCountBytes(null, true); } @Override public void writeTo(BufferedSink sink) throws IOException { writeOrCountBytes(sink, false); } private long writeOrCountBytes(BufferedSink sink, boolean countBytes) { long byteCount = 0L; Buffer buffer; if (countBytes) { buffer = new Buffer(); } else { buffer = sink.buffer(); } for (int i = 0, size = encodedNames.size(); i < size; i++) { if (i > 0) buffer.writeByte('&'); buffer.writeUtf8(encodedNames.get(i)); buffer.writeByte('='); buffer.writeUtf8(encodedValues.get(i)); } if (countBytes) { byteCount = buffer.size(); buffer.clear(); } return byteCount; }
至於BufferSink和Buffer類,這兩個類是Okio中的類,Buffer相當於一個緩存區,BufferedSink相當於OutputStream,它擴展了
OutputStream的功能,Okio的完整源碼我後續也會寫博客
③方式三:MultipartBody分塊表單創建
MultipartBody, 既可以添加表單,又可以也可以添加文件等二進制數據,我們就看幾個重要的方法
public static Part createFormData(String name, String filename, RequestBody body) { if (name == null) { throw new NullPointerException("name == null"); } StringBuilder disposition = new StringBuilder("form-data; name="); appendQuotedString(disposition, name); if (filename != null) { disposition.append("; filename="); appendQuotedString(disposition, filename); } return create(Headers.of("Content-Disposition", disposition.toString()), body); }
的Content-Disposition跟文件二進制流或者鍵值對的值
MultipartBody和FormBody大體上相同,主要區別在於writeOrCountBytes方法,分塊表單主要是將每個塊的大小進行累加來求出請求體大小,如果其中有一個塊沒有指定大小,就會返回-1。所以分塊表單中如果包含文件,默認是無法計算出大小的,除非你自己給文件的RequestBody指定contentLength。
private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException { long byteCount = 0L; Buffer byteCountBuffer = null; if (countBytes) { //如果是計算大小的話,就new個 sink = byteCountBuffer = new Buffer(); } //循環塊 for (int p = 0, partCount = parts.size(); p < partCount; p++) { Part part = parts.get(p); //獲取每個塊的頭 Headers headers = part.headers; //獲取每個塊的請求體 RequestBody body = part.body; //寫 --xxxxxxxxxx 邊界 sink.write(DASHDASH); sink.write(boundary); sink.write(CRLF); //寫塊的頭 if (headers != null) { for (int h = 0, headerCount = headers.size(); h < headerCount; h++) { sink.writeUtf8(headers.name(h)) .write(COLONSPACE) .writeUtf8(headers.value(h)) .write(CRLF); } } //寫塊的Content_Type MediaType contentType = body.contentType(); if (contentType != null) { sink.writeUtf8("Content-Type: ") .writeUtf8(contentType.toString()) .write(CRLF); } //寫塊的大小 long contentLength = body.contentLength(); if (contentLength != -1) { sink.writeUtf8("Content-Length: ") .writeDecimalLong(contentLength) .write(CRLF); } else if (countBytes) { // We can't measure the body's size without the sizes of its components. //如果有個塊沒有這名大小,就返回-1. byteCountBuffer.clear(); return -1L; } sink.write(CRLF); //如果是計算大小就累加,否則寫入BufferedSink if (countBytes) { byteCount += contentLength; } else { body.writeTo(sink); } sink.write(CRLF); } //寫 --xxxxxxxxxx-- 結束邊界 sink.write(DASHDASH); sink.write(boundary); sink.write(DASHDASH); sink.write(CRLF); if (countBytes) { byteCount += byteCountBuffer.size(); byteCountBuffer.clear(); } return byteCount; }
好久沒更新博客了,最近在做公司的項目,這也算是我接觸的第一個正式項目。通過項目的檢驗,發現自己積累了一年的知識還是遠遠不夠,想要提高,好的方法是 :項目+書+視頻+博客。
Android構架1. Application 應用程序層:用Java語言編寫的運行在虛擬機的程序用戶直接使用的功能,其基本應用比如:電話、聯系人、短信、郵件等,還有無數
生命周期一.正常情況下生命周期如圖正常生命周期 開起activity調用onCreate() onStart() onResume(),按下返回鍵 onPause() o
簽名工具的使用Android源碼編譯出來的signapk.jar既可給apk簽名,也可給rom簽名的。使用格式:java –jar signapk.jar [-w] pu