一、現象
一般通過Android webview進行下載文件的方法是
1.重寫DownloadListener的onDownloadStart方法,在onDownloadStart方法中彈出對話框提示用戶有新的文件需要下載
2.用戶點擊確定之後,通過http get下載文件
由於Android webview的實現,以上的下載文件步驟涉及到了兩次get的操作。第一次是用戶在webview中點擊下載鏈接時,webview自動發送http get請求,這個時候服務器除了將文件信息發送過來之外,會同時將文件的內容發送給webview。第二次是在步驟2,由自己設計的程序發起的。
為了驗證如上結論,我在Android 4.4系統中的自帶浏覽器通過訪問並下載這個測試鏈接,並用wireshark進行抓包查看結果。通過如下三張圖,我覺得可以驗證同一份文件確實被傳了兩次。因為兩個不同http get請求之後都可以看到服務器向客戶端發送的連續的TCP數據包。
二、解決方法
針對這個問題,我覺得比較完美的解決辦法是webview在進行get的時候能夠緩存相應的文件內容,並且開放相應的接口給應用進行緩存的讀取。但是我並沒有發現相應的api。
於是便有了如下的解決辦法:在webview加載url之前,通過鏈接中的一些特定字段發現進行下載操作。然後通過http head請求獲取文件名和大小等相關信息。由於http head只是請求文件相關信息,所以服務器只是通過http response將文件信息返回而不通過tcp發送文件具體內容。
2.1 在繼承自WebViewClient的類的shouldOverrideUrlLoading方法中檢查鏈接類型,並通過http head獲取文件大小,名稱信息。在以下代碼基礎上需要增加合適的獲取文件名稱代碼和handler的代碼。
if (url.contains("可能會進行下載的鏈接字段")) {
new Thread() {
public void run() {
HttpHead httpGet = new HttpHead(mUrl);
boolean success = true;
String filename="";
Integer fileLength=100;
try {
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, 8000);
HttpConnectionParams.setSoTimeout(httpParameters, 8000);
DefaultHttpClient httpClient = new DefaultHttpClient(httpParameters);<br>
//需要的話可能得增加cookie<br>
HttpResponse httpResponse = httpClient.execute(httpGet);
if (httpResponse.getStatusLine().getStatusCode() == 200) { //如果文件名保存在Content-Disposition中,並且用的是gb2312編碼方式可以參考這段代碼
String nameString=httpResponse.getFirstHeader("Content-Disposition").getValue();
char [] nameChar=new char[nameString.length()];
byte [] nameBytes=new byte[nameString.length()];
nameString.getChars(0, nameString.length(), nameChar, 0);
for (int i = 0; i < nameChar.length; i++) {
nameBytes[i]=(byte)nameChar[i];
}
filename=EncodingUtils.getString(nameBytes, "gb2312");
Log.d(TAG, "file name is "+filename);
String contentLength=httpResponse.getFirstHeader("Content-Length").getValue();
fileLength=Integer.parseInt(contentLength);
success=true;
}else {
success=false;
}
} catch (Exception e) {
success = false;
e.printStackTrace();
}
Message msg = Message.obtain();
if(success) {
msg.what = GET_FILE_INFO_FINISHED;
Bundle mBundle=new Bundle();
mBundle.putString("fileName", filename);
mBundle.putInt("fileLength", fileLength);
msg.setData(mBundle);
}
else {
msg.what = GET_FILE_INFO_FAILED;
}
mHandler.sendMessage(msg);
}
}.start();
}else {
view.loadUrl(url);
}
2.2 適用情況
在Android 4.4中,根據網頁的特點,可能會出現點擊下載鏈接,而shouldOverrideUrlLoading不被調用的情況,這時2.1中方法則失效了。不過對於4.4之前的系統應該是適用的。