Android 攔截WebView加載URL,控制其加載CSS、JS資源
來自: http://blog.csdn.net//lyhhj/article/details/49517537
緒論
最近在項目中有了這樣一個需求,我們都知道WebView加載網頁可以緩存,但是web端想讓客服端根據需求來緩存網頁,也就是說web端在設置了http響應頭,我根據這個頭來攔截WebView加載網頁,去執行網絡加載還是本地緩存加載。這個需求之前一直沒聽說過,在網上搜了一下,發現有攔截WebView加載網頁這個方法,研究了一下,最終實現了,今天小編分享給大家這個開發經驗:
WebView緩存機制
1.緩存模式
Android的WebView有五種緩存模式
1.LOAD_CACHE_ONLY //不使用網絡,只讀取本地緩存數據
2.LOAD_DEFAULT //根據cache-control決定是否從網絡上取數據。
3.LOAD_CACHE_NORMAL //API level 17中已經廢棄, 從API level 11開始作用同LOAD_DEFAULT模式
4.LOAD_NO_CACHE //不使用緩存,只從網絡獲取數據
5.LOAD_CACHE_ELSE_NETWORK //只要本地有,無論是否過期,或者no-cache,都使用緩存中的數據
2.緩存路徑
/data/data/包名/cache/
/data/data/包名/database/webview.db
/data/data/包名/database/webviewCache.db
3.設置緩存模式
mWebSetting.setLoadWithOverviewMode(true); mWebSetting.setDomStorageEnabled(true); mWebSetting.setAppCacheMaxSize(1024 * 1024 * 8);//設置緩存大小 //設置緩存路徑 appCacheDir = Environment.getExternalStorageDirectory().getPath() + "/xxx/cache"; File fileSD = new File(appCacheDir); if (!fileSD.exists()) { fileSD.mkdir(); } mWebSetting.setAppCachePath(appCacheDir); mWebSetting.setAllowContentAccess(true); mWebSetting.setAppCacheEnabled(true); if (CheckHasNet.isNetWorkOk(context)) { //有網絡網絡加載 mWebSetting.setCacheMode(WebSettings.LOAD_DEFAULT); } else { //無網時本地緩存加載 mWebSetting.setCacheMode(WebSettings.LOAD_CACHE_ONLY); }
4.清除緩存
/* 清除WebView緩存 */ public void clearWebViewCache(){//清理Webview緩存數據庫 try { deleteDatabase("webview.db"); deleteDatabase("webviewCache.db"); } catch (Exception e) { e.printStackTrace(); } //WebView 緩存文件 File appCacheDir = new File(getFilesDir().getAbsolutePath()+APP_CACAHE_DIRNAME); Log.e(TAG, "appCacheDir path="+appCacheDir.getAbsolutePath()); File webviewCacheDir = new File(getCacheDir().getAbsolutePath()+"/webviewCache"); Log.e(TAG, "webviewCacheDir path="+webviewCacheDir.getAbsolutePath()); //刪除webview 緩存目錄 if(webviewCacheDir.exists()){ deleteFile(webviewCacheDir); } //刪除webview 緩存 緩存目錄 if(appCacheDir.exists()){ deleteFile(appCacheDir); } }</pre> <hr />
好了,我們了解了WebView的緩存緩存機制了之后來看看到底怎么攔截WebView加載網頁:實現原理
1.要想攔截WebView加載網頁我們必須重寫WebViewClient類,在WebViewClient類中我們重寫shouldInterceptRequest()方法,看方法名一目了然,攔截http請求,肯定是這個方法。
2.獲取http請求的頭,看是否包含所設置的flag,如果包含這個flag說明web端想讓我們保存這個html,那么我們改怎么手動保存這個html呢?
1)獲取url的connection
2)利用connection.getHeaderField(“flag”)獲取http請求頭信息
3)得到請求的內容區數據的類型String contentType = connection.getContentType();
4)獲取html的編碼格式
5)將html的內容寫入文件(具體代碼下面會介紹)
*3.注意:因為控制WebView加載網頁的方法需要三個參數
public WebResourceResponse(String mimeType, String encoding, InputStream data)
mimeType:也就是我們第3步獲取的內容區數據的類型
encoding:就是html的編碼格式
data:本地寫入的html文件*
那么問題來了,我們可以把html代碼寫到本地緩存文件中,而這個html所對應的mimeType和encoding我們存到哪里呢?因為http的頭信息是http請求的屬性,我們存到SP中?存到數據庫中?好像都不行,無法對應關系啊。這塊小編想了好久,因為小編沒怎么寫過文件讀取這一塊,最后想到把這兩個參數一起存到html文件開始的幾個字節,每次加載先讀取這兩個參數就OK了,不過這樣讀寫比較麻煩,也比較費時,但是卻給后臺減少了不小的壓力。看一下代碼具體怎么實現的吧。
class MyWebClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; }@TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { if (!"get".equals(request.getMethod().toLowerCase())) { return super.shouldInterceptRequest(view, request); } String url = request.getUrl().toString(); //todo:計算url的hash String md5URL = YUtils.md5(url); //讀取緩存的html頁面 File file = new File(appCacheDir + File.separator + md5URL); if (file.exists()) { FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(file); Log.e(">>>>>>>>>", "讀緩存"); return new WebResourceResponse(YUtils.readBlock(fileInputStream), YUtils.readBlock(fileInputStream), fileInputStream); } catch (FileNotFoundException e) { e.printStackTrace(); } return null; } try { URL uri = new URL(url); URLConnection connection = uri.openConnection(); InputStream uristream = connection.getInputStream(); String cache = connection.getHeaderField("Ddbuild-Cache"); String contentType = connection.getContentType(); //text/html; charset=utf-8 String mimeType = ""; String encoding = ""; if (contentType != null && !"".equals(contentType)) { if (contentType.indexOf(";") != -1) { String[] args = contentType.split(";"); mimeType = args[0]; String[] args2 = args[1].trim().split("="); if (args.length == 2 && args2[0].trim().toLowerCase().equals("charset")) { encoding = args2[1].trim(); } else { encoding = "utf-8"; } } else { mimeType = contentType; encoding = "utf-8"; } } if ("1".equals(cache)) { //todo:緩存uristream FileOutputStream output = new FileOutputStream(file); int read_len; byte[] buffer = new byte[1024]; YUtils.writeBlock(output, mimeType); YUtils.writeBlock(output, encoding); while ((read_len = uristream.read(buffer)) > 0) { output.write(buffer, 0, read_len); } output.close(); uristream.close(); FileInputStream fileInputStream = new FileInputStream(file); YUtils.readBlock(fileInputStream); YUtils.readBlock(fileInputStream); Log.e(">>>>>>>>>", "讀緩存"); return new WebResourceResponse(mimeType, encoding, fileInputStream); } else { Log.e(">>>>>>>>>", "網絡加載"); return new WebResourceResponse(mimeType, encoding, uristream); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } }
//這里面讀寫操作比較多,還有截取那兩個屬性的字符串稍微有點麻煩 /* int轉byte by黃海杰 at:2015-10-29 16:15:06 @param iSource @param iArrayLen @return / public static byte[] toByteArray(int iSource, int iArrayLen) { byte[] bLocalArr = new byte[iArrayLen]; for (int i = 0; (i < 4) && (i < iArrayLen); i++) { bLocalArr[i] = (byte) (iSource >> 8 i & 0xFF); } return bLocalArr; }
/** * byte轉int * by黃海杰 at:2015-10-29 16:14:37 * @param bRefArr * @return */ // 將byte數組bRefArr轉為一個整數,字節數組的低位是整型的低字節位 public static int toInt(byte[] bRefArr) { int iOutcome = 0; byte bLoop; for (int i = 0; i < bRefArr.length; i++) { bLoop = bRefArr[i]; iOutcome += (bLoop & 0xFF) << (8 * i); } return iOutcome; } /** * 寫入JS相關文件 * by黃海杰 at:2015-10-29 16:14:01 * @param output * @param str */ public static void writeBlock(OutputStream output, String str) { try { byte[] buffer = str.getBytes("utf-8"); int len = buffer.length; byte[] len_buffer = toByteArray(len, 4); output.write(len_buffer); output.write(buffer); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 讀取JS相關文件 * by黃海杰 at:2015-10-29 16:14:19 * @param input * @return */ public static String readBlock(InputStream input) { try { byte[] len_buffer = new byte[4]; input.read(len_buffer); int len = toInt(len_buffer); ByteArrayOutputStream output = new ByteArrayOutputStream(); int read_len = 0; byte[] buffer = new byte[len]; while ((read_len = input.read(buffer)) > 0) { len -= read_len; output.write(buffer, 0, read_len); if (len <= 0) { break; } } buffer = output.toByteArray(); output.close(); return new String(buffer,"utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; }
//為了加密我們的html我們把url轉成md5 /* 字符串轉MD5 by黃海杰 at:2015-10-29 16:15:32 @param string @return / public static String md5(String string) {
byte[] hash; try { hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8")); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Huh, MD5 should be supported?", e); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Huh, UTF-8 should be supported?", e); } StringBuilder hex = new StringBuilder(hash.length * 2); for (byte b : hash) { if ((b & 0xFF) < 0x10) hex.append("0"); hex.append(Integer.toHexString(b & 0xFF)); } return hex.toString(); }</pre> <hr />
注意
功能雖然實現了,但是發現一個比較棘手的問題,就是shouldInterceptRequest()方法有兩個:
</div>
1.public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
return super.shouldInterceptRequest(view, url);
}
2.public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {}
重載的方法,第一個是已經廢棄了的,SDK 20以下的會執行1,SDK20以上的會執行2,那么問題又來了,因為我們在獲取http請求的時候要判斷是post()請求還是get()請求,如果是post請求我們就網絡加載,而get請求才去加載本地緩存,因為post請求需要參數。所以大家可以看到我上面僅僅實現了SDK20以上的新方法,而沒有去關SDK20以下廢棄的那個函數,因為廢棄的那個函數根本獲取不到請求方式,不知道是不是因為這個原因才將這個方法廢棄的。這一塊小編會繼續研究的,一定要解決這個問題,小編已經有了思路不知道能不能實現,接下來小編會去研究一下2014年新出的CrossWalk這個瀏覽器插件,據說重寫了底層,比webview能更好的兼容h5新特性,更穩定,屏蔽安卓不同版本的webview的兼容性問題
生命就在于折騰,小編就喜歡折騰,將Android折騰到底O(∩_∩)O~~