Webview开发及性能优化

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类

处理各种通知、请求事件

常用方法:

  1. shouldOverrideUrlLoading():加载超链接时回调过来,通过重写可以实现对网页中超链接的拦截
  2. onPageStarted():开始载入页面调用的,我们可以设定一个loading的页面,告诉用户程序在等待网络响应。
  3. onPageFinished():在页面加载结束时调用。我们可以关闭loading 条,切换程序动作。或者执行js注入
  4. shouldInterceptRequest():在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次,可以进行本地资源监测、替换或缓存
  5. onReceivedError():加载页面的服务器出现错误时(如404)调用。
  6. onReceivedSslError():处理https请求c出错

WebChromeClient类

辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等。

常用方法:

  1. onProgressChanged():获得网页的加载进度并显示
  2. onReceivedTitle():获取Web页中的标题
  3. onJsAlert ():拦截Alert
  4. onJsPrompt():拦截Prompt
  5. onJsConfirm():拦截Confirm

缓存

Android WebView自带的缓存机制有5种:

  1. 浏览器缓存机制
  2. App Cache
  3. Dom Storage
  4. Web SQL Database缓存机制
  5. Indexed Database 缓存机制
  6. 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

  1. webview.loadUrl(“javascript:”)
  2. webview.evaluateJavascript() Android4.4版本开始支持,方法最大的好处就是能够直接在一次执行的时候获取到 JS 返回的结果
    webView.evaluateJavascript("javascript:Date.now()", new ValueCallback() {
        @Override
        public void onReceiveValue(String value) {
            System.out.println(value); 
        }
    });

JavaScript调用Java

  1. 通过addJavascriptInterface()进行对象映射
    webView.addJavascriptInterface(new JSInterface(),"android");
    class JSInterface{
        @JavascriptInterface
        public void getUserInfo(){}
        @JavascriptInterface
        public void getDeviceInfo(){}
    }

     

  2. 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);
        }
    });

     

  3. 方法劫持方法劫持主要是利用 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

当然也有很多其他的方案,但思想大都一致。

调试:

Webview开发及性能优化_第1张图片

优化:

Web具有快速迭代发布的天然优势,但也存在中一些让人诟病的问题,比如加载速度慢,体验差等。但现在有很多优化框架来解决这些问题,优化后虽说不能媲美Native,但也不是那么差,当然这个过程需要前后端的配合。

VasSonic:轻量级高性能Hybrid框架

CandyWebCache是移动端web资源的本地缓存解决方案

首屏优化:

Webview开发及性能优化_第2张图片

如何缩短这些过程的时间,就成了优化WebView性能的关键。优化主要从webview的初始化、加载、渲染这几个方向进行

WebView初始化

初始化webview设计到浏览器内核初始化,还只能在主线程中执行。

首次初始化时间

二次初始化时间

403ms

22.5m‬s

解决方案:提前初始化,并创建一个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

待续

  1. 腾讯x5浏览器
  2. 框架使用VasSonic、CandyWebCache

你可能感兴趣的:(Android)