美文网首页
Hybrid开发之WebView使用方法及注意事项

Hybrid开发之WebView使用方法及注意事项

作者: zizi192 | 来源:发表于2017-11-05 22:46 被阅读0次

    工作这么长时间,细细想来有很长时间都在与WebView打交道。在Hybrid App的开发中,积累了一定的经验,在此做一个简单的工作总结。

    Hybrid开发中最常用的组件就是WebView了。WebView不仅用来展示Web页面,更是Web页面和安卓手机Native之间沟通的桥梁。但由于历史原因,android不同版本之间WebView不同,存在一些兼容性问题。

    注:鉴于在市场上Android 4.0及以后的系统占90%之上,较多的开源工程的minSdkVersion为14。本人参与开发的app也只关注Android 4.0及以上系统的兼容性。

    WebView

    在Android 4.4系统之前,WebView一直采用WebKit内核;而在Android 4.4及以后google采用了chromium内核。二者的API变化不大,但是在某些场景下的表现有些微差异。整体来说chromium内核更为高效,支持V8引擎解析Javascript。更重要的是Chromium支持远程调试。

    开发中web页面可使用console.log打印控制台日志。此时可通过Android Studio的Logcat查看到打印信息。在4.4系统之前,WebView相关日志tag是webkit;而在4.4及之后,tag是chromium。日志中包括Javascript运行中打印的日志,错误信息等,有助于分析Hybrid开发中遇到的问题。

    I/chromium: [INFO:CONSOLE(1)] "The key "target-densitydpi" is not supported.", source: file:///data/user/0/com.tfzq.gcs.dev/files/www/m_tf/trade/indexTota.js?v=0.4672763997119571 (1)
    

    创建WebView包括两种方法,一是在Xml中配置;二是直接使用new动态创建。推荐使用第二种方式进行开发。WebView可以使用loadUrl加载本地或线上的Web页面,执行javascript语句;也可以使用loadData直接加载html数据。另一方法loadDataWithBaseUrl在加载页面中有本地图片时可以使用。

    使用WebView之前需要通过WebSettings进行一定的配置。

    WebSettings settings = getSettings();
     //默认是false 设置true允许和js交互
     settings.setJavaScriptEnabled(true);
     //  WebSettings.LOAD_DEFAULT 如果本地缓存可用且没有过期则使用本地缓存,否加载网络数据 默认值
     //  WebSettings.LOAD_CACHE_ELSE_NETWORK 优先加载本地缓存数据,无论缓存是否过期
     //  WebSettings.LOAD_NO_CACHE  只加载网络数据,不加载本地缓存
     //  WebSettings.LOAD_CACHE_ONLY 只加载缓存数据,不加载网络数据
     //Tips:有网络可以使用LOAD_DEFAULT 没有网时用LOAD_CACHE_ELSE_NETWORK
     settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
     //开启 DOM storage API 功能 较大存储空间,使用简单
     settings.setDomStorageEnabled(true);
     //设置数据库缓存路径 存储管理复杂数据 方便对数据进行增加、删除、修改、查询 不推荐使用
     settings.setDatabaseEnabled(true);
     final String dbPath = context.getApplicationContext().getDir("db", Context.MODE_PRIVATE).getPath();
     settings.setDatabasePath(dbPath);
     //开启 Application Caches 功能 方便构建离线APP 不推荐使用
     settings.setAppCacheEnabled(true);
     final String cachePath = context.getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath();
     settings.setAppCachePath(cachePath);
     settings.setAppCacheMaxSize(5 * 1024 * 1024);
    

    WebChromeClient

    WebChromeClient常用以下几个回调方法

    • WebChromeClient. onProgressChanged
      页面加载进度回调,progress从0-100。可用来实现自定义加载进度条。

    • WebChromeClient. onReceivedTitle
      可用来接收当前页面的标题,实现本地标题栏的变化。

    • WebChromeClient.onJsPrompt
      可用来实现自定义的弹窗,也可用来实现安全的JSBridge。phonegap混合开发框架即使用onJsPrompt实现在Android 4.2以下系统的安全性。类似的方法还有onJsConfirm,onJsAlert。

    在开发中遇到过界面相关问题,恍惚记得WebView使用chromium内核之后,onJsPrompt方法运行线程不在是主线程导致。年代久远,有兴趣的读者可自行验证。

    WebViewClient

    WebViewClient常用以下几个回调方法。

    • WebViewClient.shouldOverrideUrlLoading
      在WebView加载Url前调用,app可拦截该方法来自己处理本次url的加载。该方法返回true代表app自己处理url;返回false代表WebView处理url。该方法可配合自定义的协议头来区分url是事件或普通的web连接。JSBridge框架主要依赖此方法实现JS事件的回调。

    • WebViewClient.shouldInterceptRequest
      可拦截WebView对页面中资源的加载,使用本地资源代替。曾经在一项目中用该方法实现本地缓存。

    • WebViewClient.onPageStarted
      该方法表示页面开始加载,理论上是只调用一次。但是在WebKit内核开发时,遇到过调用次数超过一次的情况。

    • WebViewClient.onReceivedError
      页面加载出现错误时调用该方法。可用来定制错误页面。

    • WebViewClient.onPageFinished
      该方法一般用来处理页面加载完成时的一些操作。比如注入JSBridge框架代码建立JS-Native通信通道。但是在我早期的开发经验中,4.*的系统上低概率出现onPageFinished方法未回调的问题。在开发测试中需要关注。Web页面中很容易有一些自动跳转逻辑,这时onPageFinished会被调用多次,当然url的参数不同。可参考How to listen for a Webview finishing loading a URL in Android?

    • WebViewClient

    Web与Native通信

    关于Web与Native之间的通信,可参考我的文章。介绍了基本的通信方法,及开源库JsBridge的使用。
    JSbridge系列解析(一):JS-Native调用方法
    JSbridge系列解析(二):lzyzsd/JsBridge使用方法
    JSbridge系列解析(三):lzyzsd/JsBridge源码解析
    JSbridge系列解析(四):Web端发消息给Native代码流程具体分析

    WebView内存泄漏

    网上搜索Webview,可以说最多的就是内存泄漏相关的介绍,如说某篇文章中出现的下段。文章指出不要在xml中直接使用webview,否则可能出现webview所在activity的内存泄漏。

    webview的创建也是有技巧的,最好不要在layout.xml中使用webview,可以通过一个viewgroup容器,使用代码动态往容器里addview(webview),这样可以在onDestory()里销毁掉webview及时清理内存,另外需要注意创建webview需要使用applicationContext而不是activity的context,销毁时不再占有activity对象,这个大家应该都知道了,最后离开的时候需要及时销毁webview,onDestory()中应该先从viewgroup中remove掉webview,再调用webview.removeAllViews();webview.destory();

    最初做hybrid相关开发时,4.4及以上系统的手机较少,确实存在webview.destroy()后无法释放内存的问题。但现在6.0系统已经成了主流,为了进一步验证,我用红米Note 4,Android 6.0系统进行了测试。

    代码如下,webview直接定义在xml文件中,且onDestroy中没有针对webview的destroy操作。通过模拟器验证Android 5.0表现与6.0一致,back退出后内存顺利回收;但4.4系统就会出现内存泄漏。

    public class WebViewTestActivity extends Activity {
        private WebView webview;
    
        @Override
        public void onCreate(Bundle saveInstance){
            super.onCreate(saveInstance);
            setContentView(R.layout.activity_webview_test);
    
            webview = findViewById(R.id.webview);
            webview.loadUrl("https://www.baidu.com");
    
            //部分页面,如百度主页,如果不设置setJavaScriptEnabled为true,则显示白屏
            WebSettings webSettings = webview.getSettings();
            webSettings.setJavaScriptEnabled(true);
        }
    
        @Override
        public void onDestroy(){
            super.onDestroy();
        }
    }
    

    多次进入主界面后按Back键退出,通过执行adb shell dumpsys meminfo 包名命令,监控发现WebView及对应Activity的资源释放了。这也说明google官方做了一定的修复。但为了兼容低版本,仍建议通过ViewGroup容器动态添加WebView,使用完成后进行清理操作。

    public class WebViewTestActivity extends Activity {
        private WebView webview;
        private FrameLayout webviewContainer;
    
        @Override
        public void onCreate(Bundle saveInstance){
            super.onCreate(saveInstance);
            setContentView(R.layout.activity_webview_test);
    
            webviewContainer = findViewById(R.id.webview_container);
    
            webview = new WebView(this);
            webviewContainer.addView(webview);
            webview.loadUrl("https://www.baidu.com");
    
            //部分页面,如百度主页,如果不设置setJavaScriptEnabled为true,则显示白屏
            WebSettings webSettings = webview.getSettings();
            webSettings.setJavaScriptEnabled(true);
        }
    
        @Override
        public void onDestroy(){
            super.onDestroy();
    
            if(webview != null){
                ViewGroup parent = (ViewGroup)webview.getParent();
                if (parent != null){
                    parent.removeView(webview);
                }
                webview.removeAllViews();
                webview.destroy();
            }
        }
    }
    

    4.4的模拟器使用上面改进的代码,但仍出现内存泄漏,不过是InputMethodManager泄漏引起。后面有时间再研究这个。

    网上另一种解决内存泄漏的方法,是在创建WebView时使用Application的Context。但是如果Web页面中出现Video时,会出现如果崩溃。Web页面弹框的场景未测试。

    W/System.err: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
    W/System.err:     at android.app.ContextImpl.startActivity(ContextImpl.java:1034)
    W/System.err:     at android.app.ContextImpl.startActivity(ContextImpl.java:1021)
    W/System.err:     at android.content.ContextWrapper.startActivity(ContextWrapper.java:311)
    W/System.err:     at com.android.webview.chromium.WebViewContentsClientAdapter$NullWebViewClient.shouldOverrideUrlLoading(WebViewContentsClientAdapter.java:196)
    W/System.err:     at com.android.webview.chromium.WebViewContentsClientAdapter.shouldOverrideUrlLoading(WebViewContentsClientAdapter.java:293)
    W/System.err:     at com.android.org.chromium.android_webview.AwContentsClientBridge.shouldOverrideUrlLoading(AwContentsClientBridge.java:96)
    W/System.err:     at com.android.org.chromium.base.SystemMessageHandler.nativeDoRunLoopOnce(Native Method)
    W/System.err:     at com.android.org.chromium.base.SystemMessageHandler.handleMessage(SystemMessageHandler.java:27)
    W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
    W/System.err:     at android.os.Looper.loop(Looper.java:136)
    W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5017)
    W/System.err:     at java.lang.reflect.Method.invokeNative(Native Method)
    W/System.err:     at java.lang.reflect.Method.invoke(Method.java:515)
    W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
    W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
    W/System.err:     at dalvik.system.NativeStart.main(Native Method)
    A/libc: Fatal signal 6 (SIGABRT) at 0x0000084d (code=-6), thread 2125 (n.myapplication)
    

    WebView如果执行destory操作,则后续不能再进行loadUrl的操作,否则会出现白屏。这一点在webView的复用时需要考虑。同时WebView复用时,

    实际测试WebView复用的场景,将WebView置为static变量,下列代码仅有示范的作用,不具实际的意义。目前我仍未想到WebView复用的意义所在。注意在界面销毁时需要将WebView从父布局中remove,避免持有父布局引用导致当前界面的内存泄漏。但是仍无办法解决WebView创建时持有的Activity的内存泄漏。最初我怀疑用复用的WebVIew播放视频会出现问题,毕竟其持有的Context已不可见,但实际测试运行视频OK。

    public class WebViewTestActivity extends Activity {
        private static WebView webview;
        private FrameLayout webviewContainer;
    
        @Override
        public void onCreate(Bundle saveInstance){
            super.onCreate(saveInstance);
            setContentView(R.layout.activity_webview_test);
    
            webviewContainer = findViewById(R.id.webview_container);
    
            if (webview == null) {
                webview = new WebView(this);
            }
    
            webviewContainer.addView(webview);
            webview.loadUrl("https://www.baidu.com");
    
            //部分页面,如百度主页,如果不设置setJavaScriptEnabled为true,则显示白屏
            WebSettings webSettings = webview.getSettings();
            webSettings.setJavaScriptEnabled(true);
        }
    
        @Override
        public void onDestroy(){
            super.onDestroy();
    
            if(webview != null){
                ViewGroup parent = (ViewGroup)webview.getParent();
                if (parent != null){
                    parent.removeView(webview);
                }
                webview.removeAllViews();
    //            webview.destroy();
            }
        }
    }
    

    关于内存泄漏的另一种解决方案,即将显示WebView的activity运行在另外的进程。这样在WebView界面关闭时将该进程直接kill,避免对主程序的影响。但是该方法对多WebView的程序不太适合,毕竟跨进程通信成本较大,容易出现各种问题。该方案只适应一定场景,了解即可。

    参考:
    Android Webview的一些使用总结和遇到过得坑
    Android:你不知道的 WebView 使用漏洞
    Android 各个版本WebView
    安卓webview的一些坑
    H5 缓存机制浅析 移动端 Web 加载性能优化

    相关文章

      网友评论

          本文标题:Hybrid开发之WebView使用方法及注意事项

          本文链接:https://www.haomeiwen.com/subject/gwrfmxtx.html