3.1.3 WebView 文件下载、缓存、内存泄露
2017-08-20 09:07
471 查看
前言:
本篇给大家介绍的是 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
(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的交互。有志共同进步的同学请头像右侧点击“(+)”保持对我的关注,后续好文第一时间推送给你。
联系方式:
简书:WillFlowCSDN:WillFlow
微信公众号:WillFlow
相关文章推荐
- WebView使用(内存泄露+获取网页标题+js交互+调用浏览器下载文件+网页加载失败+清缓存)
- WebView中实现文件下载功能
- Android Webview实现文件下载功能
- WebView中实现文件下载功能
- org.springframework.web.servlet.view.InternalResourceViewResolver 内存泄露问题
- android webview downloadManager文件下载管理
- WebView中实现文件下载功能
- webView内存泄露
- Android Webview实现文件下载功能
- 内存泄露--contentView缓存使用与ListView优化
- [Android]异步加载图片,内存缓存,文件缓存,imageview显示图片时增加淡入淡出动画
- Android 浏览器 —— 使用 WebView 实现文件下载
- Android 在webview中下载pdf文件,并用自定义界面阅读
- WebView下载文件
- 下载文件使用缓存(一次性读取到内存),优化性能(注意静态对象修改需要加锁)
- android WebView(六)下载和缓存
- WebView不能下载apk文件
- android webview 内存泄露
- 深入探究Android的WebView下载网络文件的盗链问题
- WebView实现文件下载功能