編輯:關於Android編程
http Cache
指的是web浏覽器所具有的復用本地已緩存的文檔”副本”的能力。我們知道,通過網絡獲取內容有時候成本很高,因而
緩存和重用以前獲取的資源的能力成為優化性能很關鍵的一個方面。http協議本身提供了緩存的支持。
1. 減少冗余數據傳輸
2. 緩解網絡帶寬瓶頸
3. 降低距離時延
4. 減少服務器負擔
Expires & Cache-Control
Expires
響應首部給出了響應失效的絕對時間,這樣,像浏覽器這樣的客戶端就可以緩存一份副本,在這個時間到期之前,
不用去詢問服務器它是否有效了。http1.0引入。 例如:Expires: Thu, 03 Oct 1997 17:15:00 GMT
Cache-Control
首部用於傳輸對象的緩存信息。http1.1引入。它的值是一個緩存指令,給出了與某個對象可緩存性有關的
緩存特有指令。這個首部可以出現在請求或者響應頭中。例如:Cache-Control: no-cache
Expires
和Cache-Control
作用一致,都是指當前資源的有效期,控制浏覽器是否直接從浏覽器緩存獲取數據還是重新發送
請求到服務器取數據。
Cache-Control
可以設置如下值:
(1)Request:
no-cache —- 不要讀取緩存中的文件,要求向WEB服務器重新請求,http1.0時代還有pragma: no-cache,與此效果一樣. no-store —- 請求和響應都禁止被緩存 max-age: —- 表示當訪問此網頁後的max-age秒內再次訪問不會去服務器請求,其功能與Expires類似,只是Expires是根據某個特定日期值做比較。 max-stale —- 允許讀取過期時間必須小於max-stale 值的緩存對象。 min-fresh —- 接受其max-age生命期大於其當前時間 跟 min-fresh 值之和的緩存對象 only-if-cached —- 告知緩存者,我希望內容來自緩存,我並不關心被緩存響應,是否是新鮮的. no-transform —- 告知代理,不要更改媒體類型,比如jpg,被你改成png.(2)Response:
public —- 數據內容皆被儲存起來,就連有密碼保護的網頁也儲存,安全性很低 private —- 數據內容只能被儲存到私有的cache,僅對某個用戶有效,不能共享 no-cache —- 可以緩存,但是只有在跟WEB服務器驗證了其有效後,才能返回給客戶端 no-store —- 請求和響應都禁止被緩存 max-age: —– 本響應包含的對象的過期時間 Must-revalidate —- 如果緩存過期了,會再次和原來的服務器確定是否為最新數據,而不是和中間的proxy max-stale —- 允許讀取過期時間必須小於max-stale 值的緩存對象。 s-maxage —- 與max-age的唯一區別是,s-maxage僅僅應用於共享緩存.而不應用於用戶代理的本地緩存等針對單用戶的緩存. 另外,s-maxage的優先級要高於max-age. no-transform —- 告知代理,不要更改媒體類型,比如jpg,被你改成png.最優的注意:
Expires
首部的值是絕對時間,容易受本地時鐘偏差影響,而Cache-Control
是相對時間。因而,Cache-Control
的
優先級高於Expires
Cache-Control
策略ETag & If-None-Match
ETag
告知浏覽器當前資源在服務器的唯一標識符,由響應頭攜帶
If-None-Match
檢查資源實體是否發生了改變,由請求頭攜帶
這兩個首部通常結合使用。首先浏覽器請求某個文檔資源,服務端會對這個資源進行計算,生成指紋(md5或者其他..),然後將指紋作為ETag
令牌
放到響應頭中作為ETag
的值發送給浏覽器,浏覽器收到響應後,將資源緩存。浏覽器第二次訪問此資源時,會先從緩存中尋找此資源,
並將資源的ETag
令牌作為If-None-Match
請求頭的值發往服務端,服務端拿到請求頭後,將If-None-Match
首部中包含的ETag
令牌與該資源的指紋進行校驗比對,如果沒有發生變化,則返回304,告訴浏覽器可以復用緩存,否則重新請求,並根據請求結果,
返回200或者4xx。
Last-Modified & if-Modified-Since
Last-Modified
首部提供資源最後一次被修改的日期,由響應頭攜帶
if-Modified-Since
首部檢查資源是否在此時間後被修改過,由請求頭攜帶
這兩個首部通常結合使用。首先浏覽器請求某個文檔資源,服務端找到該資源,並在響應頭中加上Last-Modified
標識該資源最近
一次改動的時間,浏覽器收到響應後,將資源緩存。浏覽器第二次訪問此資源時,會先從緩存中尋找此資源,並將資源的last-Modified
的日期值作為if-Modified-Since
首部的值發往服務器,服務端拿到請求頭後,將’if-Modified-Since’的日期值與該資源最近
一次改動時間進行比對,如果相同將直接返回304,說明可以復用該資源,否則重新請求,並根據請求結果,返回200或者4xx等。注:不是
所有的浏覽器都支持if-Modified-Since
字段
舉個栗子,比如我訪問這個圖片:
http://img.alicdn.com/bao/uploaded/i4/2433767071/TB2hL7dfXXXXXaeXXXXXXXXXXXX_!!2433767071-0-paimai.jpg
第一次請求和響應:
第二次請求和響應:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="第二次請求和響應" src="/uploadfile/Collfiles/20151110/2015111008315511.png" title="\" />
第一次請求因為本地沒有緩存副本,所以沒有攜帶if-Last-Modified
請求頭,而服務端會將資源最後修改時間通過Last-Modified
返回,第二次因為緩存了副本,所以會攜帶if-Last-Modified
.而且從響應碼也能看出來,第一次是200,第二次是304。
區分緩存命中還是未命中有點”困難”,因為http沒有為用戶提供一種手段來區分響應是緩存命中的,還是訪問原始服務器得到的。
在這兩種情況下,響應碼都是200 ok。客戶端有一種方法可以判斷響應是否來自緩存,就是使用Date首部,將響應中Date首部的值
和當前事件進行比較,如果響應中的日期值比較早,客戶端通常就可以認為這是一條緩存的響應,或者客戶端也可以通過Age首部來檢測
緩存的響應。
HttpResponseCache
類,android4.0以後.
HttpResponseCache
兼容版,https://github.com/candrews/HttpResponseCache.
直接使用Square推出的OkHttp
,google在4.0之後直接將Okhttp
項目集成到了android項目中,作為HttpUrlConnection
的實現引擎.
而方案一中的HttpResponseCache
也是依賴於Okhttp
的,然而我們無法直接使用Okhttp
因為它是hide
的,使用的話需要單獨依賴。
以android提供的HttpResponseCache
為例說明。
我的代碼演示了如何使用HttpResponseCache
緩存一張網絡圖片,NetworkUtils
類提供了getBitmap
和asyncGetBitmap
方法,用於獲取
網絡圖片:
代碼1:NetworkUtils#getBitmap()
public static HttpResponse getBitmap(Context context,Uri uri,Policy policy){
if(uri == null){
throw new IllegalArgumentException(uri is null);
}
installCacheIfNeeded(context);
try {
HttpURLConnection connection = (HttpURLConnection) new URL(MainActivity.URL).openConnection();
connection.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
connection.setReadTimeout(DEFAULT_READ_TIMEOUT);
if(policy == Policy.Cache){
connection.setUseCaches(true);
}else{
connection.setUseCaches(false);
connection.addRequestProperty(Cache-Control, no-cache);
connection.addRequestProperty(Cache-Control,max-age=0);
}
int contentLen = connection.getContentLength();
int responseCode = connection.getResponseCode();
HttpResponse response = new HttpResponse(responseCode,null,contentLen, BitmapFactory.decodeStream(connection.getInputStream()));
connection.getInputStream().close();
return response;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
這裡面我通過客戶端的設定Policy
來判斷是否需要緩存圖片,如果不需要就設置no-cache
頭。installCacheIfNeeded
方法
用於初始化緩存,內部實際上調用了HttpCache#install()
方法:
代碼2:HttpCache#install()
Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
try {
if ((cache = HttpResponseCache.getInstalled()) != null) {
return cache;
}
cache = HttpResponseCache.install(cacheDir, CACHE_SIZE);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, http response installation failed,code = 0);
}
} else {
try {
if ((cache = (HttpResponseCache) Class.forName(android.net.http.HttpResponseCache).getMethod(getInstalled).invoke(null)) != null) {
return cache;
}
Method method = Class.forName(android.net.http.HttpResponseCache).getMethod(install, File.class, long.class);
cache = (HttpResponseCache) method.invoke(null, cacheDir, CACHE_SIZE);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, http response installation failed,code = 1);
}
}
return cache;
} data-snippet-id=ext.2119c294a5428b71b1001e58f12468cb data-snippet-saved=false data-csrftoken=RvI0acnw-08Q46livGiVM05M4uTurSwGW5Pk data-codota-status=done>static HttpResponseCache install(Context context) {
if (context == null) {
throw new IllegalArgumentException(context must not be null);
}
File cacheDir = new File(context.getApplicationContext().getCacheDir(), CACHE_DIR);
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
HttpResponseCache cache = null;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
try {
if ((cache = HttpResponseCache.getInstalled()) != null) {
return cache;
}
cache = HttpResponseCache.install(cacheDir, CACHE_SIZE);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, http response installation failed,code = 0);
}
} else {
try {
if ((cache = (HttpResponseCache) Class.forName(android.net.http.HttpResponseCache).getMethod(getInstalled).invoke(null)) != null) {
return cache;
}
Method method = Class.forName(android.net.http.HttpResponseCache).getMethod(install, File.class, long.class);
cache = (HttpResponseCache) method.invoke(null, cacheDir, CACHE_SIZE);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, http response installation failed,code = 1);
}
}
return cache;
}
我在MainActivity中編寫了一個測試用例來分別測試了允許緩存和不允許緩存情況下的結果。加載的圖片來自阿裡cdn圖片服務器,
這裡的圖片的過期時間是一年,所以可以確保能夠緩存。
Cache-Control:max-age=31536000
Content-Type:image/jpeg
Date:Thu, 22 Oct 2015 05:41:18 GMT
Expires:Fri, 21 Oct 2016 05:41:18 GMT
測試結果很完美,在允許緩存的情況下,只需要第一次請求server,拿到數據後會緩存到本地預設的目錄,以後的請求都會直接走
緩存.
我們來觀察下緩存的數據格式,這裡我預先設定了緩存目錄位於包名/cache/http/
下(相關代碼參考HttpCache.java),ddms打開這個
目錄,pull出來,發現三個文件,其中有個叫journal的,很顯然,這是使用了DiskLruCache
類,另外兩個文件有一個大小是185458字節,
肯定是這張圖片,另一個文件用sublime打開後內容如下:
http://img.alicdn.com/bao/uploaded/i4/2433767071/TB2hL7dfXXXXXaeXXXXXXXXXXXX_!!2433767071-0-paimai.jpg
GET
0
HTTP/1.1 200 OK
18
Server: Tengine
Content-Type: image/jpeg
Content-Length: 185458
Connection: keep-alive
Date: Thu, 22 Oct 2015 05:41:18 GMT
Last-Modified: Sat, 12 Sep 2015 11:24:37 GMT
Expires: Fri, 21 Oct 2016 05:41:18 GMT
Cache-Control: max-age=31536000
Access-Control-Allow-Origin: *
Via: cache7.l2cm12[0,200-0,H], cache60.l2cm12[16,0], cache1.cn406[0,200-0,H], cache8.cn406[1,0]
Age: 1501380
X-Cache: HIT TCP_MEM_HIT dirn:2:377253066
X-Swift-SaveTime: Sun, 08 Nov 2015 03:15:26 GMT
X-Swift-CacheTime: 30075952
Timing-Allow-Origin: *
X-Android-Sent-Millis: 1446993858366
X-Android-Received-Millis: 1446993858403
X-Android-Response-Source: NETWORK 200
這其實是一個響應頭,X-Android-Response-Source
首部是由othttp追加的,可以判斷響應數據是從disk cache拿的還是
從網絡拿的。代碼如下:
代碼3:
//返回true說明從緩存拿的
static boolean parseResponseSourceHeader(String header) {
if (header == null) {
return false;
}
String[] parts = header.split( , 2);
if (CACHE.equals(parts[0])) {
return true;
}
if (parts.length == 1) {
return false;
}
try {
return CONDITIONAL_CACHE.equals(parts[0]) && Integer.parseInt(parts[1]) == 304;
} catch (NumberFormatException e) {
return false;
}
}
通過parseResponseSourceHeader(connection.getHeaderField(X-Android-Response-Source))
即可判定.
如果使用OkHttp
的話,緩存的控制更加方便,只需創建OkHttpClient
實例的時候設置緩存即可。具體代碼可以參考OkHttpUtils
類.
代碼4:
try {
mHttpClient.setCache(new Cache(new File(context.getApplicationContext().getCacheDir(), CACHE_DIR), CACHE_SIZE));
} catch (Exception e) {
}
緩存的策略選擇可通過CacheControl
來控制。
代碼5:
CacheControl cacheControl;
if (policy == Policy.Cache) {
cacheControl = CacheControl.FORCE_CACHE;
} else {
CacheControl.Builder builder = new CacheControl.Builder();
cacheControl = builder.noCache().build();
}
像volley、picasso等開源庫都利用了http緩存來作為DiskCache的方案。具體代碼可參見Volley#HttpHeaderParser類以及
Picasso#URLConnectionDownloader類等.
小編一直任務將web和android組件結合起來做應用可以事半功倍,html5一來就更有說服力了,特別是對於以前從事web開發的兄弟來說 1. webview加入布局文件
Android裡面的單選框和html中的其實是一樣的效果。這裡用到兩個控件:CheckBox和RadioGroup。直接上代碼:radio.xml布局文件:
Android的內存優化是性能優化中很重要的一部分,而避免OOM又是內存優化中比較核心的一點。這是一篇關於內存優化中如何避免OOM的總結性概要文章,內容大多都是和OOM有
前言Android的源碼公開策略豐富了手持設備的多樣性,但隨之而來的卻是較為嚴重的”碎片化”——版本繁多、尺寸多樣、功能定