2.3.1 (下)WebView 檔案下載、快取、記憶體洩露
前言:
本篇給大家介紹的是 WebView 下載檔案的知識點,當我們在使用普通瀏覽器的時候,比如UC, 當我們點選到一個可供下載連結的時候,就會進行下載。WebView 作為一個瀏覽器般的元件, 當然也是支援下載的,我們可以自己來寫下載的流程,設定下載後的檔名稱以及儲存位置,當然也可以呼叫其它內建的瀏覽器來進行下載,比如Chrome、UC等,下面給大家演示下用法。
本節例程下載地址:WillFlowWebViewDowmload
一、WebView檔案下載
(1)呼叫其它瀏覽器下載檔案
這個很簡單,我們只需為 WebView 設定 setDownloadListener,然後重寫 DownloadListener 的 onDownloadStart,然後在裡面寫個Intent,然後startActivity對應的Activity即可!
- 關鍵程式碼如下:
mWebView.setDownloadListener(new DownloadListener(){
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition,
String mimetype, long contentLength) {
Log.i(TAG,"onDownloadStart");
Log.i(TAG,"url : " + url);
Log.i(TAG,"userAgent : " + userAgent);
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW,uri);
startActivity(intent);
}
});
如果你手機記憶體在多個瀏覽器的話,會開啟一個對話方塊供你選擇其中一個瀏覽器進行下載。
(2)自己寫執行緒下載檔案
當然,你可能不想把下載檔案放到預設路徑下,或者想自己定義檔名等等,你都可以自己來寫 一個執行緒來下載檔案,實現示例程式碼如下:
- DownLoadThread.java
/**
* Created by : WGH.
*/
public class DownLoadThread implements Runnable {
private static final String TAG = DownLoadThread.class.getSimpleName();
private String dUrl;
public DownLoadThread(String dUrl) {
this.dUrl = dUrl;
}
@Override
public void run() {
Log.i(TAG, "開始下載!");
InputStream in = null;
FileOutputStream fout = null;
try {
URL httpUrl = new URL(dUrl);
HttpURLConnection conn = (HttpURLConnection) httpUrl.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
in = conn.getInputStream();
File downloadFile, sdFile;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
Log.i(TAG,"SD卡可寫");
downloadFile = Environment.getExternalStorageDirectory();
sdFile = new File(downloadFile, "testDownload.apk");
fout = new FileOutputStream(sdFile);
}else{
Log.e(TAG,"SD卡不存在或者不可讀寫!");
}
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
assert fout != null;
fout.write(buffer, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (fout != null) {
try {
fout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Log.i(TAG, "下載完畢!");
}
}
- 然後MainActivity.java中建立並啟動該執行緒:
wView.setDownloadListener(new DownloadListener(){
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition,
String mimetype, long contentLength) {
Log.e("HEHE","onDownloadStart被呼叫:下載連結:" + url);
new Thread(new DownLoadThread(url)).start();
}
});
- 另外,別忘了寫SD卡的讀寫許可權以及Internet訪問網路的許可權:
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 在SDCard中建立與刪除檔案許可權 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 往SDCard寫入資料許可權 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
後續我們還會用Java實現多執行緒斷點續傳,用Kotlin實現非同步下載檔案等功能,保持關注就好!
二、WebView快取問題
現在很多門戶類資訊網站,比如虎嗅、鈦媒體等等的APP,簡單點說是資訊閱讀類的APP,很多都是直接巢狀一個WebView用來顯示相關資訊的,這可能就涉及到了WebView的快取了!
所謂的頁面快取就是指:
儲存載入一個網頁時所需的HTML,JS,CSS等頁面相關的資料以及其他資源,當沒網的時候或者網路狀態較差的時候,我們會優先載入本地儲存好的相關資料,而這個相關資料就是之前在請求成功後儲存好的資料!
實現這個快取的方式有兩種:
一種是後臺寫一個下載的Service,將相關的資料按自己的需求下載到資料庫或者儲存到相應資料夾中,然後下次載入對應URL前先判斷是否存在本地快取,如果存在優先載入本地快取,不存在則執行聯網請求,成功後再次快取相關資源。典型的如舊版本的36Kr,在進去後會先離線文章,然後再顯示!
當然本篇要講解的不是這種自己寫邏輯的方式,而是通過WebView本身自帶的快取功能來快取頁面,這種方式很常用而且使用起來非常簡單,我們只需為WebView設定開啟相關功能,以及設定資料庫的快取路徑即可完成快取。
(1)快取的分類
首先要說的是快取的分類,我們快取的資料分為:頁面快取 和 資料快取。
頁面快取:
載入一個網頁時的html、JS、CSS等頁面或者資源資料,這些快取資源是由於瀏覽器的行為而產生,開發者只能通過配置HTTP響應頭影響瀏覽器的行為才能間接地影響到這些快取資料,而快取的索引放在:/data/data/<包名>/databases,對應的檔案放在:/data/data/package_name/cache/webviewCacheChromunm下。-
資料快取:
分為AppCache和DOM Storage兩種,我們開發者可以自行控制的就是這些快取資源。-
AppCache:
我們能夠有選擇的緩衝web瀏覽器中所有的東西,從頁面、圖片到指令碼、css等,尤其在涉及到應用於網站的多個頁面上的CSS和JavaScript檔案的時候非常有用,其大小目前通常是5M。 在Android上需要手動開啟(setAppCacheEnabled),並設定路徑(setAppCachePath)和容量 (setAppCacheMaxSize),而Android中使用ApplicationCache.db來儲存AppCache資料! -
DOM Storage:
儲存一些簡單的用key/value對即可解決的資料,根據作用範圍的不同,有Session Storage和Local Storage兩種,分別用於會話級別的儲存(頁面關閉即消失)和本地化儲存(除非主動刪除,否則資料永遠不會過期),在Android中可以手動開啟DOM Storage(setDomStorageEnabled),設定儲存路徑(setDatabasePath)Android中Webkit會為DOMStorage產生兩個檔案:my_path/localstorage/xxx.localstorage和my_path/Databases.db
-
AppCache:
(2)快取的模式
- LOAD_CACHE_ONLY:不使用網路,只讀取本地快取資料
- LOAD_DEFAULT:根據cache-control決定是否從網路上取資料
- LOAD_CACHE_NORMAL:API level 17中已經廢棄,從API level 11開始作用同LOAD_DEFAULT模式
- LOAD_NO_CACHE:不使用快取,只從網路獲取資料
- LOAD_CACHE_ELSE_NETWORK:只要本地有,無論是否過期,或者no-cache,都使用快取中的資料
總結:根據以上兩種模式,建議快取策略為,判斷是否有網路,有的話,使用LOAD_DEFAULT, 無網路時,使用LOAD_CACHE_ELSE_NETWORK。
(3)為WebView開啟快取功能
下面我們就來為WebView開啟快取功能,先來看下實現的效果圖:
流程解析:
1.進入頁面後預設載入url,然後隨便點選一個連結跳到第二個頁面,退出APP。
2.關閉wifi以及行動網路,然後重新進入,發現無網路的情況下,頁面還是載入了, 開啟第一個連結也可以載入,開啟其他連結就發現找不到網頁!
3.點選清除快取,把應用關閉,重新進入,發現頁面已經打不開!
- 接下來是程式碼實現:MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private WebView mWebView;
private Button btn_clear_cache;
private Button btn_refresh;
private static final String APP_CACHE_DIRNAME = "/webviewcache";
private static final String URL = "http://blog.csdn.net/comwill";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.mWebView);
btn_clear_cache = (Button) findViewById(R.id.btn_clear_cache);
btn_refresh = (Button) findViewById(R.id.btn_refresh);
mWebView.loadUrl(URL);
mWebView.setWebViewClient(new WebViewClient() {
// 設定在webView點選開啟的新網頁在當前介面顯示,而不跳轉到新的瀏覽器中
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.i(TAG, "shouldOverrideUrlLoading url : " + url);
view.loadUrl(url);
return true;
}
});
WebSettings settings = mWebView.getSettings();
settings.setJavaScriptEnabled(true);
// 設定快取模式
settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
// 開啟DOM storage API 功能
settings.setDomStorageEnabled(true);
// 開啟database storage API功能
settings.setDatabaseEnabled(true);
String cacheDirPath = getFilesDir().getAbsolutePath() + APP_CACHE_DIRNAME;
Log.i(TAG, "cacheDirPath : " + cacheDirPath);
// 設定資料庫快取路徑
settings.setAppCachePath(cacheDirPath);
settings.setAppCacheEnabled(true);
Log.i(TAG, "DatabasePath : " + settings.getDatabasePath());
btn_clear_cache.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mWebView.clearCache(true);
}
});
btn_refresh.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mWebView.reload();
}
});
}
// 重寫回退按鈕的點選事件
@Override
public void onBackPressed() {
if(mWebView.canGoBack()){
mWebView.goBack();
}else{
super.onBackPressed();
}
}
}
程式碼很簡單,我們做的僅僅是開啟快取的功能,以及設定快取模式以及快取的資料的路徑。
(4)刪除WebView的快取資料
上面的示例中,我們通過呼叫WebView的clearCache(true)方法,已經實現了對快取的刪除!除了這種方法外,還有這樣的方法:
setting.setCacheMode(WebSettings.LOAD_NO_CACHE);
deleteDatabase("WebView.db");和deleteDatabase("WebViewCache.db");
webView.clearHistory();
webView.clearFormData();
getCacheDir().delete();
這就是手動寫delete方法,然後迴圈迭代刪除快取資料夾!
當然,正如前面所說,我們能直接操作的只是部分資料而已,而頁面快取是由於瀏覽器的行為而產生的,我們只能通過配置HTTP響應頭影響瀏覽器的行為才能間接地影響到這些快取資料,所以上述的方法僅僅是刪除的資料部分的快取,這一點還請注意。
三、WebView處理網頁返回的錯誤碼資訊
假如你們公司是做HTML5端的移動APP的,即通過WebView來顯示網頁。那麼假如你訪問的網頁不存在或者其他錯誤,比如:404、401、403等錯誤的狀態碼,如果直接彈出WebView預設的錯誤提示頁面,可能顯得不那麼友好,這時我們就可以通過重寫WebViewClient的onReceivedError()方法來實現我們想要的效果。
一般的做法有兩種:
一種是我們自己在assets目錄下建立一個用於顯示錯誤資訊的HTML頁面,當發生錯誤即onReceivedError()被呼叫的時候我們呼叫webView的loadUrl跳到我們的錯誤頁面。
另外一種是單獨寫一個佈局或者直接放一個大大的圖片,平時設定這個佈局或者圖片為不可見,當頁面錯誤時,讓該佈局或者圖片可見,下面我們來寫個簡單的示例。
(1)頁面錯誤,載入自定義網頁
mWebView.setWebViewClient(new WebViewClient() {
//設定在webView點選開啟的新網頁在當前介面顯示,而不跳轉到新的瀏覽器中
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
@Override
public void onReceivedError(WebView view, int errorCode, String description,
String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
mWebView.loadUrl("file:///android_asset/error.html");
}
});
(2)頁面錯誤,顯示相應的View
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private WebView mWebView;
private ImageView img_error_back;
private Button btn_refresh;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.wView);
img_error_back = (ImageView) findViewById(R.id.img_error_back);
btn_refresh = (Button) findViewById(R.id.btn_refresh);
mWebView.loadUrl("http://www.baidu.com");
mWebView.setWebViewClient(new WebViewClient() {
// 設定在webView點選開啟的新網頁在當前介面顯示,而不跳轉到新的瀏覽器中
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
mWebView.setVisibility(View.GONE);
img_error_back.setVisibility(View.VISIBLE);
}
});
btn_refresh.setOnClickListener(this);
}
@Override
public void onClick(View v) {
mWebView.loadUrl("http://www.baidu.com");
img_error_back.setVisibility(View.GONE);
mWebView.setVisibility(View.VISIBLE);
}
}
接下來我麼就可以編寫自己的程式碼進行驗證啦!
四、WebView 如何避免記憶體洩露?
(1)動態生成
要使用WebView不造成記憶體洩漏,首先應該做的就是不能在xml中定義webview節點,而是在需要的時候動態生成,即:可以在使用WebView的地方放置一個LinearLayout類似ViewGroup的節點,然後在要使用WebView的時候動態生成:
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mWebView = new WebView(getApplicationContext());
mWebView.setLayoutParams(params);
mLayout.addView(mWebView);
(2)注意銷燬的次序
在呼叫 webview.destroy()的時候,必須確保webview已經從view tree中被刪除,否則這個函式不會執行的。如果是在xml中靜態定義的webview,只有在整個view退出後呼叫 webview.destroy( )才會被正確執行,但整個view退出後又找不到webview了,這個是很矛盾的。所以正確的銷燬順序是:在 Activity 銷燬 WebView 的時候,先讓 WebView 載入null內容,然後移除 WebView,再銷燬 WebView,最後置空。
@Override
protected void onDestroy() {
if (mWebView != null) {
mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
mWebView.clearHistory();
((ViewGroup) mWebView.getParent()).removeView(mWebView);
mWebView.destroy();
mWebView = null;
}
super.onDestroy();
}
通過以上方法即可消除大部分的記憶體溢位問題,當然還有一種方法是給巢狀webview的activity另開一個程式,作為一個獨立程式展示,感興趣的同學可以自己嘗試下。
結語:
到此為止,我們學會了使用 WebView 進行檔案下載的兩種方式:呼叫其它瀏覽器下載檔案、自己寫執行緒下載檔案;學會了使用WebView設定快取和清除快取;學會了WebView處理網頁返回的錯誤碼資訊的兩種方式:頁面錯誤載入自定義網頁、頁面錯誤顯示相應的View。
我會在將來寫一篇來介紹 WebViewJavascriptBridge 以及如何使用 WebViewJavascriptBridge 進行Java和Js的互動。有志共同進步的同學請頭像右側點選“(+)”保持對我的關注,後續好文第一時間推送給你。
相關文章
- SHBrowseForFolder 記憶體洩露記憶體洩露
- ASP.NET Core - 快取之記憶體快取(下)ASP.NET快取記憶體
- WebView引起的記憶體洩漏WebView記憶體
- 記憶體溢位和記憶體洩露記憶體溢位記憶體洩露
- Lowmemorykiller記憶體洩露分析記憶體洩露
- 使用 mtrace 分析 “記憶體洩露”記憶體洩露
- 實戰Go記憶體洩露Go記憶體洩露
- Android 記憶體洩露詳解Android記憶體洩露
- Linux記憶體洩露案例分析和記憶體管理分享Linux記憶體洩露
- ArkTS 的記憶體快照與記憶體洩露除錯記憶體洩露除錯
- nodejs爬蟲記憶體洩露排查NodeJS爬蟲記憶體洩露
- Pprof定位Go程式記憶體洩露Go記憶體洩露
- 轉載 ]查詢Windows記憶體洩露的幾種方法Windows記憶體洩露
- 6.1檔案下載、讀取
- win10驅動記憶體洩露如何解決_win10記憶體洩露處理方法Win10記憶體洩露
- android Handler導致的記憶體洩露Android記憶體洩露
- netty 堆外記憶體洩露排查盛宴Netty記憶體洩露
- 乾貨分享:淺談記憶體洩露記憶體洩露
- 解決git記憶體洩露問題Git記憶體洩露
- Spring Boot heapdump洩露記憶體分析方法Spring Boot記憶體
- 線上記憶體洩露定位--memleak工具記憶體洩露
- java中如何檢視記憶體洩露Java記憶體洩露
- CPU快取記憶體快取記憶體
- 記一次"記憶體洩露"排查過程記憶體洩露
- 如何在 Linux 下檢測記憶體洩漏Linux記憶體
- vue 打包專案時因node記憶體洩露而報錯Vue記憶體洩露
- 簡單的記憶體“洩露”和“溢位”記憶體
- JAVA記憶體洩露的原因及解決Java記憶體洩露
- 一個 Vue 頁面的記憶體洩露分析Vue記憶體洩露
- 一個Vue頁面的記憶體洩露分析Vue記憶體洩露
- C程式記憶體洩露檢測工具——ValgrindC程式記憶體洩露
- Android效能最佳化之記憶體洩露Android記憶體洩露
- Python實現記憶體洩露排查的示例Python記憶體洩露
- 小題大做 | Handler記憶體洩露全面分析記憶體洩露
- 多核cpu、cpu快取記憶體、快取一致性協議、快取行、記憶體快取記憶體協議
- 記憶體快取選型記憶體快取
- 記一次 .NET 某工控軟體 記憶體洩露分析記憶體洩露
- ThreadLocal原始碼解讀和記憶體洩露分析thread原始碼記憶體洩露