前言
- 在Android在使用H5开发具有快速开发的特点,所以现在大多数APP都采用了原生Android+WebView的Hybrid开发模式。
- 缺点:Android下的WebView有一些痛点,由于受到Android手机的碎片化原因,性能低下,加载速度慢和浪费流量等缺点。
- 今天我们就针对WebView中的性能问题进行优化。
WebView首次启动优化-常用资源预加载
- 提前加载JS、CSS、图片资源,等H5页面加载完成后进行替换。
- 原理: WebView中引入了ShouldInterceptRequest方法可以实现,需要注意的是该方法API11-API21和API21之后不同,需要做兼容性处理。
例子
webview.loadUrl("http://ip.cn/");//加载http://ip.cn/
webview.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
//api21以下
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if(url.contains("logo.gif")){
InputStream is = null;
try {
is = getApplicationContext().getAssets().open("images/index.jpg");
} catch (IOException e) {
e.printStackTrace();
}
WebResourceResponse response = new WebResourceResponse("image/jpg","utf-8",is);
return response;
}
return super.shouldInterceptRequest(view, url);
}
//api21以上
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
if (request.getUrl().toString().contains("logo.gif")){
InputStream is = null;
try {
is = getApplicationContext().getAssets().open("images/index.jpg");
} catch (IOException e) {
e.printStackTrace();
}
WebResourceResponse response = new WebResourceResponse("image/jpg","utf-8",is);
return response;
}
return super.shouldInterceptRequest(view, request);
}
});
常用JS本地化及延迟加载
- WebView渲染H5页面性能差归结于两个原因:
1.JS解析过程比较复杂,解析速度比较慢导致。
2.Android设备碎片化导致设备性能参差不齐,导致Android手机硬件部分无法很好的支持WebView的渲染。
- 解决方案:将一些常用的JS脚步本地化处理,将JS文件编写好后放置在asserts文件夹中,直接打包到APK中。在WebView调用OnPageFinished()方法后进行加载。
WebView二次启动优化
- 我们可以为WebView加入缓存机制,在二次启动的时候,首先去缓存中进行命中,是否可以命中缓存,不可以的时候才进行网络情况,可以进一步的优化WebView的启动速度。
- 这里有几种比较常用的方案:
WebView的缓存模式
- LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据。
- LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
- LOAD_NO_CACHE: 不使用缓存,只从网络获取数据。
- LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
//优先使用缓存:
WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
// 不使用缓存:
WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
-
建议:
判断是否有网络,有的话,使用LOAD_DEFAULT,
无网络时,使用LOAD_CACHE_ELSE_NETWORK。
浏览器缓存机制-前端开发人员负责(静态文件缓存)
- 在HTTP 协议头里有这样一个字段 Cache-Control是用来控制缓存的。
//代表本地缓存,且从发起请求算起,接下来500s,如果有该资源请求都不会进行HTTP请求,而是读取本地的缓存
Cache-Control:max-age=500
Dom Storage
- 主要用于存储一些临时、简单的数据缓存和cookie。
- DOM Storage 分为 SessionStorage 和 LocalStorage。
- SessionStorage是临时性的,在WebView关闭后就会失效。
- LocalStorage是持久性的,在WebView关闭后不会失效,可以进行使用。
//通过webSettings.setDomStorageEnabled(true);//生效。
webSettings.setDomStorageEnabled(true)
IndexDB
- IndexDB是NoSQL 数据库,通过Key - Value来提供存储功能。
- 在Android4.4开始对其进行支持
//通过JS执行开发进行使能
webSettings.setJavaScriptEnabled(true);
缓存总结
- 存储静态资源文件(JS、CSS、图片资源)- 采用浏览器缓存机制。
- 存储临时、简单的数据 - 采用Dom Storage缓存机制。
- 存储结构复杂、数据量大的数据 - 采用IndexDB缓存机制。
WebView防止内存泄漏
- 导致内存泄漏的原因:WebView持有对Activity的引用,导致WebVIew的内存无法释放。
解决方案
- 使用AIDL跨进程通信,WebView开启一个新的进程,通过AIDL与主进程进行通信,WebView所在的进程在业务合适的阶段对WebView进行销毁。
- 自己封装WebView,不在XML文件中直接定义,在代码中new出来,并传入Application的context来防止Activity引用导致的泄漏。
activity_webview.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rl_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>
WebViewActivity.java
public class WebViewActivity extends BaseActivity {
private RelativeLayout rl_layout;
private WebView webview;
private WebSettings webSettings;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview);
initWebView();
}
private void initWebView(){
//避免WebView内存泄露
rl_layout = (RelativeLayout) findViewById(R.id.rl_layout);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
webview = new WebView(getApplicationContext());
webview.setLayoutParams(params);
rl_layout.addView(webview);
webSettings = webview.getSettings();
webSettings.setUseWideViewPort(true);//将图片调整到适合webview的大小
webSettings.setLoadWithOverviewMode(true);// 缩放至屏幕的大小
webSettings.setSupportZoom(true);//支持缩放,默认为true。是下面那个的前提。
webSettings.setBuiltInZoomControls(true);//支持缩放,默认为true。是下面那个的前提。
webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存
webSettings.setAllowFileAccess(true); //设置可以访问文件
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
webSettings.setDomStorageEnabled(true);// 开启 DOM storage API 功能
webSettings.setAppCacheEnabled(true);// 开启 Application Caches 功能
//优先使用缓存:
// WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
// 缓存模式如下:
// LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
// LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
// LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
// LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
// 不使用缓存: WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
//根据以上两种模式,建议缓存策略为:
// 判断是否有网络,有的话,使用LOAD_DEFAULT,
// 无网络时,使用LOAD_CACHE_ELSE_NETWORK。
webview.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
//api21以下
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if(url.contains("logo.gif")){
InputStream is = null;
try {
is = getApplicationContext().getAssets().open("images/index.jpg");
} catch (IOException e) {
e.printStackTrace();
}
WebResourceResponse response = new WebResourceResponse("image/jpg","utf-8",is);
return response;
}
return super.shouldInterceptRequest(view, url);
}
//api21以上
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
if (request.getUrl().toString().contains("logo.gif")){
InputStream is = null;
try {
is = getApplicationContext().getAssets().open("images/index.jpg");
} catch (IOException e) {
e.printStackTrace();
}
WebResourceResponse response = new WebResourceResponse("image/jpg","utf-8",is);
return response;
}
return super.shouldInterceptRequest(view, request);
}
});
webview.loadUrl("http://ip.cn/");
}
@Override
protected void onResume() {
super.onResume();
webSettings.setJavaScriptEnabled(true);
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
webSettings.setJavaScriptEnabled(false);
}
@Override
protected void onDestroy() {
//避免WebView内存泄露
if (webview != null){
webview.loadDataWithBaseURL(null,"","tex/html","utf-8",null);
webview.clearHistory();
((ViewGroup)webview.getParent()).removeView(webview);
webview.destroy();
webview = null;
}
super.onDestroy();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && webview.canGoBack()){
webview.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
}