搜尋此網誌

2012年10月30日 星期二

善用 HttpResponseCache


之前寫 Android App 都要自己實作 Cache, 不管是圖片或者是API資料;
比如說: 打開程式後, 如果Local有Cache就先讀取Cache; 然後在暗地裡發送Request去更新圖片/API資料等… 當有新的, 直接複寫本地端的緩存, 然後 notify refresh. 而且還要防止Cache太多, 要定時刪除舊的資料….

太累了

使用的好處

- 節省用戶端的電力 (因為可以少掉很多Internet Connection)
- 省下龐大的頻寬費用 (因為對Server來說, 當收到 If-Modified-Since, 如果沒更新, Server端只要回應 304即可)
- 開發者不用自己再做 Cache 機制了.
- 最好的事!! 如果你本身不是用 HttpClient, HttpDefaultClient..., 而是用 HttpURLConnection的話, 你根本不用改本來的 Code.
接下來, 實作吧!! 其實很簡單, 你不必改寫你的任何Code, 你只要 Application層, 把它啓用就好了; 剩下的一切 HttpURLConnection 會幫你處理

(ResponseApplication.java)download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.example.testresponsecache;

import java.io.File;

import android.app.Application;
import android.util.Log;

public class ResponseApplication extends Application {
  public void onCreate() {
    super.onCreate();
    new Thread(){
      @Override
      public void run() {
        enableHttpResponseCache();
      }
    }.start();
  }
  private void enableHttpResponseCache(){
    try {
      long httpCacheSize = 10 * 1024 * 1024;
      File httpCacheDir = new File(getCacheDir(), "http");
      Class.forName("android.net.http.HttpResponseCache")
        .getMethod("install", File.class, long.class)
        .invoke(null, httpCacheDir, httpCacheSize);
    } catch (Exception e) {
      Log.e("===>", e.getMessage(), e);
    }
  }
}
(MainActivity.java)download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.example.testresponsecache;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

import android.os.AsyncTask;
import android.os.Bundle;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends Activity {

  private final String TAG = getClass().getSimpleName();
  ImageView img;
  Button msg;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    img = (ImageView) this.findViewById(R.id.img);
    msg = (Button) this.findViewById(R.id.msg);
    msg.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        new InternetTask().execute();
      }
    });
  }

  @SuppressLint("NewApi")
  class InternetTask extends AsyncTask<String, String, Boolean> {
    Bitmap bitmap;
    String jsonStr;

    @SuppressLint("NewApi")
    @Override
    protected void onPostExecute(Boolean result) {
      super.onPostExecute(result);
      img.setImageBitmap(bitmap);
      msg.setText(jsonStr);
    }

    @Override
    protected Boolean doInBackground(String... params) {
      // Test download image
      try {
        URL url = new URL("http://jasoncheng.tw/1.png");
        HttpURLConnection conn = (HttpURLConnection) (url.openConnection());
        conn.connect();
        InputStream is = conn.getInputStream();
        BitmapFactory.Options ops = new BitmapFactory.Options();
        bitmap = BitmapFactory.decodeStream(is, null, ops);
        is.close();
        conn.disconnect();
      } catch (Exception e) {
        Log.e(TAG, e.getMessage(), e);
      }

      // Test download JSON data
      try {
        URL url = new URL("http://jasoncheng.tw/1.json");
        HttpURLConnection conn = (HttpURLConnection) (url.openConnection());
        conn.connect();
        BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
        jsonStr = reader.readLine();
        InputStream is = conn.getInputStream();
        is.close();
        conn.disconnect();
      } catch (Exception e) {
        Log.e(TAG, e.getMessage(), e);
      }
      return true;
    }

  }
}
接下來用 NodeJS 寫個簡單的Static File Server
1
2
3
4
5
var express = require("express");
app = express.createServer();
app.use(express.logger());
app.use(express.static(__dirname + '/public'));
app.listen(4000);
每個Request都會產生兩隻檔案, 一個是實體檔案, 一個是 HTTP Header 資料
1
2
3
4
5
6
7
# cd /data/data/com.example.testresponsecache/cache/http
# ls -l
-rw------- u0_a48   u0_a48        345 2012-07-17 23:07 20ebbdb944f2be7d9ea96466bafe98a5.0
-rw------- u0_a48   u0_a48         42 2012-07-17 23:07 20ebbdb944f2be7d9ea96466bafe98a5.1
-rw------- u0_a48   u0_a48        321 2012-07-17 23:07 7abaca174bffb497cea054db94961804.0
-rw------- u0_a48   u0_a48      11856 2012-07-17 23:07 7abaca174bffb497cea054db94961804.1
-rw------- u0_a48   u0_a48        163 2012-07-17 23:07 journal
整個的運作的關鍵就在 Last-Modified
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# cat 20ebbdb944f2be7d9ea96466bafe98a5.0
http://jasoncheng.tw/1.json
GET
0
HTTP/1.1 200 OK
9
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Date: Tue, 17 Jul 2012 15:01:31 GMT
Last-Modified: Tue, 17 Jul 2012 13:24:14 GMT
Server: nginx/0.7.65
Transfer-Encoding: chunked
X-Android-Received-Millis: 1342537658233
X-Android-Sent-Millis: 1342537658210
第1次執行
1
2
- [Tue, 17 Jul 2012 15:14:18 GMT] "GET /1.png HTTP/1.1" 200 11856 "-" "Dalvik/1.6.0 (Linux; U; Android 4.1; sdk Build/JRN83C)"
- [Tue, 17 Jul 2012 15:14:20 GMT] "GET /1.json HTTP/1.1" 200 22 "-" "Dalvik/1.6.0 (Linux; U; Android 4.1; sdk Build/JRN83C)"
第2次執行, Server 好輕鬆, 只要回304就好了, 省下多少頻寬阿
1
2
- [Tue, 17 Jul 2012 15:14:36 GMT] "GET /1.png HTTP/1.1" 304 - "-" "Dalvik/1.6.0 (Linux; U; Android 4.1; sdk Build/JRN83C)"
- [Tue, 17 Jul 2012 15:14:36 GMT] "GET /1.json HTTP/1.1" 304 - "-" "Dalvik/1.6.0 (Linux; U; Android 4.1; sdk Build/JRN83C)"
修改1.json檔案後, Last-modified 改變了, 所以重新抓一次, 所以 Status Code 變回 200 Okay!
1
2
3
root@ubuntu:/var/www/html/jasoncheng/static_test# node web.js | grep -v favicon
- [Tue, 17 Jul 2012 15:17:30 GMT] "GET /1.png HTTP/1.1" 304 - "-" "Dalvik/1.6.0 (Linux; U; Android 4.1; sdk Build/JRN83C)"
- [Tue, 17 Jul 2012 15:17:30 GMT] "GET /1.json HTTP/1.1" 200 17 "-" "Dalvik/1.6.0 (Linux; U; Android 4.1; sdk Build/JRN83C)"

測試結果

- Okay: 關閉網路後, 圖片/JSON 資料會自己返回 Local Cache 的資料 (所以用戶不會感覺網路斷線了..嘿)
- Okay: 資料更新後, JSON會自動更新
- Okay: 檔案沒改變的條件下, Server 只回應304

測試後的問題

- Client 端必須是 Android 4.0 以後版本才有支援 (真是XD...我的NexusOne 沒辦法升到4.0啦~~~)
- Server 上的圖片修改後, Client端並不會即時更新; 懶得翻 Android Source Code了..有興趣的朋友, 再去追 (不過也沒差吧, 應該沒有人大頭照常換的吧)
- 如果遇到特殊的 port number (非80 port), cache不會生效.

沒有留言:

張貼留言