Webview现状
版本 | 内核 | 描述 |
<19(Android 4.4) | Android Webkit内核 | 对HTML5的支持不是很好,js存在安全漏洞 |
>=19
| Chromium内核 |
|
Android5.0开始 |
| WebView移植成了一个独立的apk |
Android7.0 |
| 安装Chrome (version>51),那么Chrome将会直接为应用的WebView提供渲染,WebView版本会随着Chrome的更新而更新,用户也可以选择WebView的服务提供方(在开发者选项->WebView Implementation里),WebView可以脱离应用,在一个独立的沙盒进程中渲染页面
|
Android8.0 |
| 默认开启WebView多进程模式,即WebView运行在独立的沙盒进程中 |
Webview 配置
public static void setDefaultWebSettings(WebView webView) {
WebSettings webSettings = webView.getSettings();
//允许js代码
webSettings.setJavaScriptEnabled(true);
//禁用放缩
webSettings.setDisplayZoomControls(false);
webSettings.setBuiltInZoomControls(false);
//禁用文字缩放
webSettings.setTextZoom(100);
//设置浏览器缓存
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
//缓存模式如下:
//LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
//LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
//LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
//LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
//5.0以上开启混合模式加载
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
//是否使用预览模式加载界面
webSettings.setLoadWithOverviewMode(true);
//设置WebView是否使用viewport
webSettings.setUseWideViewPort(true);
//允许SessionStorage/LocalStorage存储
webSettings.setDomStorageEnabled(true);
//10M缓存,api 18后,系统自动管理。
webSettings.setAppCacheMaxSize(10 * 1024 * 1024);
//允许App缓存
webSettings.setAppCacheEnabled(true);
//设置App缓存地址
webSettings.setAppCachePath(context.getDir("appcache", 0).getPath());
//允许WebView使用File协议
webSettings.setAllowFileAccess(true);
//不保存密码
webSettings.setSavePassword(false);
//设置UA
webSettings.setUserAgentString(webSettings.getUserAgentString() + "kaolaApp/" + AppUtils.getVersionName());
//移除部分系统JavaScript接口
KaolaWebViewSecurity.removeJavascriptInterfaces(webView);
//自动加载图片
webSettings.setLoadsImagesAutomatically(true);
}
WebViewClient类
处理各种通知、请求事件
常用方法:
- shouldOverrideUrlLoading():加载超链接时回调过来,通过重写可以实现对网页中超链接的拦截
- onPageStarted():开始载入页面调用的,我们可以设定一个loading的页面,告诉用户程序在等待网络响应。
- onPageFinished():在页面加载结束时调用。我们可以关闭loading 条,切换程序动作。或者执行js注入
- shouldInterceptRequest():在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次,可以进行本地资源监测、替换或缓存
- onReceivedError():加载页面的服务器出现错误时(如404)调用。
- onReceivedSslError():处理https请求c出错
WebChromeClient类
辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等。
常用方法:
- onProgressChanged():获得网页的加载进度并显示
- onReceivedTitle():获取Web页中的标题
- onJsAlert ():拦截Alert
- onJsPrompt():拦截Prompt
- onJsConfirm():拦截Confirm
缓存
Android WebView自带的缓存机制有5种:
- 浏览器缓存机制
- App Cache
- Dom Storage
- Web SQL Database缓存机制
- Indexed Database 缓存机制
- File System缓存机制
https://blog.csdn.net/carson_ho/article/details/71402764
JS-Native交互
WebSettings.setJavaScriptEnabled(true);在Android 4.4(<19)以下版本存在安全漏洞,
如果启用了JavaScript,务必做好安全措施,防止远程执行漏洞
@TargetApi(11)
private static final void removeJavascriptInterfaces(WebView webView) {
try {
if (Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT < 17) {
webView.removeJavascriptInterface("searchBoxJavaBridge_");
webView.removeJavascriptInterface("accessibility");
webView.removeJavascriptInterface("accessibilityTraversal");
}
} catch (Throwable tr) {
tr.printStackTrace();
}
}
在Android 4.4上通过注解@JavascriptInterface方式建立Javascript对象和android原生对象的绑定
java调用JavaScript
- webview.loadUrl(“javascript:”)
- webview.evaluateJavascript() Android4.4版本开始支持,方法最大的好处就是能够直接在一次执行的时候获取到 JS 返回的结果
webView.evaluateJavascript("javascript:Date.now()", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { System.out.println(value); } });
JavaScript调用Java
- 通过addJavascriptInterface()进行对象映射
webView.addJavascriptInterface(new JSInterface(),"android"); class JSInterface{ @JavascriptInterface public void getUserInfo(){} @JavascriptInterface public void getDeviceInfo(){} }
- WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url
webView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if(url.equals('sdk:hello')) { System.out.println('hello world'); return true; } return super.shouldOverrideUrlLoading(view, url); } });
- 方法劫持方法劫持主要是利用 JS 的一些方法执行时会触发 Android 客户端中的一些回调如:WebChromeClient的onJsAlert、onJsPrompt、onJsConfirm、onConsoleMessage方法
private class hijackWebChromeClient extends WebChromeClient { @Override public boolean onJsPrompt(WebView view,String url, String message, String defaultValue, JsPromptResult result) { if (this.hijack(message)) { return true; } return super.onJsPrompt(view, url, message, defaultValue, result); } } //注入劫持回调类 WebView webview = (WebView) findViewById(R.id.webview); webview.loadUrl('http://imnerd.org'); webview.setWebChromeClient(new hijackChromeClient);
以上讲述了 JS 调用客户端的方法,以及客户端调用前端的方法。除了这两种单向调用的方式之外,往往比较多的是 JS 调用客户端方法,客户端再调用 JS 返回结果的双向调用。在 JS 调用的时候需要传入一个回调方法名,然后客户端直接执行回调方法。
JsBridge
该项目在Java和JavaScript之间架起了一座桥梁。它提供了从js调用Java代码并从java调用js代码的安全方便的方法。
传送门:https://github.com/lzyzsd/JsBridge
当然也有很多其他的方案,但思想大都一致。
调试:
优化:
Web具有快速迭代发布的天然优势,但也存在中一些让人诟病的问题,比如加载速度慢,体验差等。但现在有很多优化框架来解决这些问题,优化后虽说不能媲美Native,但也不是那么差,当然这个过程需要前后端的配合。
CandyWebCache是移动端web资源的本地缓存解决方案
首屏优化:
如何缩短这些过程的时间,就成了优化WebView性能的关键。优化主要从webview的初始化、加载、渲染这几个方向进行
WebView初始化
初始化webview设计到浏览器内核初始化,还只能在主线程中执行。
首次初始化时间 | 二次初始化时间 |
403ms | 22.5ms |
解决方案:提前初始化,并创建一个WebView缓存池(需要多个webview做转场动画)
public class GMWebViewPool {
/**
* 创建WebView实例
* 用了applicationContext
*/
public void prepareNewWebView(Context context) {
if (mCachedWebViewStack.size() < CACHED_WEBVIEW_MAX_NUM) {
mCachedWebViewStack.push(new GMWebView(new MutableContextWrapper(context.getApplicationContext())));
}
}
/**
* 从缓存池中获取合适的WebView
*
* @param context activity context
* @return WebView
*/
public GMWebView acquireWebViewInternal(Context context) {
// 为空,直接返回新实例
if (mCachedWebViewStack == null || mCachedWebViewStack.isEmpty()) {
return new GMWebView(context);
}
GMWebView webView = mCachedWebViewStack.pop();
// webView不为空,则开始使用预创建的WebView,并且替换Context
MutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext();
contextWrapper.setBaseContext(context);
return webView;
}
}
MutableContextWrapper,作为Context的一个中间层。我们会将Activity context包在MutableContextWrapper里面,destory的时候,会将WebView的Context设置为Application的Context,从而释放Activity Context。
//precreate WebView
MutableContextWrapper contextWrapper = new MutableContextWrapper(BaseApplicationImpl.sApplication);
mPool[0] = new WebView(contextWrapper);
//reset WebView
ct =(MutableContextWrapper)webview.getContext();
ct.setBaseContext(getApplication());
//reuse WebView
((MutableContextWrapper)webview.getContext()).setBaseContext(activityContext);
优化后:
WebView初始化 |
3ms |
待续
- 腾讯x5浏览器
- 框架使用VasSonic、CandyWebCache