android收集Androidandroid技术专栏

Android WebView 概述、捕获url、js交互、内存

2017-01-10  本文已影响1274人  Goo_Yao

知识点汇总:

前言

结合H5页面开发的App日渐多了起来,而WebView正是Html与Native的纽带,今天就借着一个新的项目需求顺便做一下WebView的知识总结,如有错漏,恳请大家指点指点。(项目需求:将适配好的网页打包成App,并能够调用系统摄像头进行二维码识别、拍照或是选择本地图片上传、获取用户位置等)


WebView 小科普

Basic usage
By default, a WebView provides no browser-like widgets, does not enable JavaScript and web page errors are ignored.
理解:默认情况,WebView并没有开启对JavaScript的支持,仅起展示作用,因此,我们需要进一步配置WebView才能满足各种各样的需求。

WebView基本使用

<uses-permission android:name="android.permission.INTERNET" />
<!--另外附上一些可能会用到的权限:-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--获取网络状态权限(情景:WebView联网前,应检查当前网络状态)-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--通过WiFi或移动基站的方式获取用户错略的经纬度信息-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!--通过GPS芯片接收卫星的定位信息权限(情景:结合HTML5使用Geolocation API获取位置时)-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--读写存储权限(情景:拍照/选择图片,涉及图片读取、编辑、压缩等功能时)-->
 WebSettings  mWebSetting = mWebView.getSettings();  //获取WebSetting
 setJavaScriptEnabled(true);//让WebView支持JavaScript
 setDomStorageEnabled(true);//启用H5 DOM API (默认false)
 setDatabaseEnabled(true);//启用数据库api(默认false)可结合 setDatabasePath 设置路径
 setCacheMode(WebSettings.LOAD_DEFAULT)//设置缓存模式
 setAppCacheEnabled(true);//启用应用缓存(默认false)可结合 setAppCachePath 设置缓存路径
 setAppCacheMaxSize()//已过时,高版本API上,系统会自行分配
 setPluginsEnabled(true);  //设置插件支持
 setRenderPriority(RenderPriority.HIGH);  //提高渲染的优先级
 setUseWideViewPort(true);  //将图片调整到适合webview的大小
 setLoadWithOverviewMode(true); // 缩放至屏幕的大小
 setSupportZoom(true);  //支持缩放,默认为true
 setBuiltInZoomControls(true); //设置内置的缩放控件(若SupportZoom为false,该设置项无效)
 setDisplayZoomControls(false); //隐藏原生的缩放控件
 setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); //支持内容重新布局
 supportMultipleWindows();  //支持多窗口
 setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);  //关闭webview中缓存
 setAllowFileAccess(true);  //设置可以访问文件
 setNeedInitialFocus(true); //当webview调用requestFocus时为webview设置节点
 setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
 setLoadsImagesAutomatically(true);  //自动加载图片
 setDefaultTextEncodingName("utf-8");//设置编码格式
 //实际应用中,一般配置即可满足基本需求
 mWebSettings.setSupportZoom(true);
 mWebSettings.setJavaScriptEnabled(true);
 mWebSettings.setLoadWithOverviewMode(true);
 mWebSettings.setUseWideViewPort(true);
 mWebSettings.setDefaultTextEncodingName("utf-8");
 mWebSettings.setLoadsImagesAutomatically(true);
//网页
private static final String URL_NET = "http://www.google.com"; // 记得加 "http://"
//assets 中的 html 资源
private static final String URL_LOCAL ="file:///android_asset/xxx.html路径"; 
//SD 卡中的 html 资源
private static final String URL_SD_CARD ="content://com.android.htmlfileprovider/mnt/sdcard/xxx.html"; 
mWebView.loadUrl(URL_NET);
mWebView.loadUrl(URL_LOCAL);
mWebView.loadUrl(URL_SD_CARD);

WebViewClient 与 WebChromeClient

WebViewClient与WebChromeClient的区别

WebViewClient 常用方法 说明
shouldOverrideUrlLoading 加载时调用,可捕获url
onPageStart 开始加载时调用(可以设置加载中提示)
onPageFinish 加载完成时调用(无法打开也是完成的一种,在这里取消加载提示显示)
onReceiveError 接收到错误信息时调用(通常在该方法中处理404之类的加载错误,但这里有点坑,API23中,在低于API23的设备上运行时,该方法失效,不调用我猜原因可能是: 新版的onReceiveError不再接收网页连接的错误,而是接收WebView自身运行出现的错误,另外有onReceivedHttpError方法来接收(然而在我的测试中,该方法还是未能接收到404错误)。可暂时用API23过时的方法“onReceivedError(WebView view, int errorCode, String description, String failingUrl)”替代新版本中的“onReceivedError(WebView view, WebResourceRequest request, WebResourceError error)”;)传送门:stackoverflow Q&A
WebChromeClient 常用方法 说明
onProgressChanged 加载进度
onReceivedTitle、onReceivedIcon 获取网页标题、图标
onGeolocationPermissionsShowPrompt 页面发起GEO定位请求时调用
... WebChromeClient源码中对各种方法都有详细解析,需要用到的时候查一下即可
mWebView.setWebViewClient(mWebViewClient);
mWebView.setChromeClient(mWebChromeClient);

WebView与Javascript交互

/**
 * This method can be used to allow JavaScript to control the host
 * application. This is a powerful feature, but also presents a security
 * risk for apps targeting{@link android.os.Build.VERSION_CODES#JELLY_BEAN} 
 * or earlier.
 * /
 //理解:该方法可以让js控制app,很强势,但在API17(4.2)及之前的版本存在安全问题
 addJavascriptInterface(Object object, String name);
 
 //使用方法
 mWebView.addJavascriptInterface(MethodObject,"name");

 //还需要写一个方法类
 class MethodObject extends Object {
    //无参函数,js中通过:var str = window.name.HtmlcallJava(); 获取到
    @JavascriptInterface
    public String HtmlcallJava() {
        return "Html call Java";
    }

    //有参函数,js中通过:window.jsObj.HtmlcallJava2("IT-homer blog");
    @JavascriptInterface
    public String HtmlcallJava2(final String param) {
        return "Html call Java : " + param;
    }
}

WebView小技巧

/**
 * 捕获Url
 * 多页面在同一个WebView打开
 * /
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
            if(url.equal("需要捕获的url")){
                // 捕获后的操作
                // 前面提到的项目需求中,调用系统摄像头识别二维码、拍照、选择本地图片等功能
                // 均可该方式简单实现(当然addJavascriptInterface方式也能达到相同效果)
                return true;
            }
            view.loadUrl(url);//该方法可让多页面在同一个WebView中打开(不用新建Activity或是调用浏览器)
            return true;
        }
//我们再回头看看该方法的官方API,会发现上面的方法多少还是有点坑(重定向问题,出现原理)     
/**
     * Give the host application a chance to take over the control when a new
     * url is about to be loaded in the current WebView. If WebViewClient is not
     * provided, by default WebView will ask Activity Manager to choose the
     * proper handler for the url. If WebViewClient is provided, return true
     * means the host application handles the url, while return false means the
     * current WebView handles the url.
     * /
     
     //该方法为应用提供处理新url的机会,如果WebView没有设置WebViewClient,WebView会调用系统来找到合适应用来处理该url;而如果设置了WebViewClient,该方法返回true说明该url由应用自行处理,而false则交给WebView自动处理。
     
     //那么,问题来了:上面方法中,我们returne的是true,而处理代码是让WebView直接loadUrl(不管什么情况都是直接loadUrl,并把该url加入历史记录),如果该url会重定向到其他url,如果调用了goBack,返回到该url,而该url又重定向到另外一个url,造成goBack失败。
     
     //解决方式:将处理重定向的url交给webView本身,webView能自行判断url是否为重定向url,能够确保历史记录准确性,自身跳转则需要另想办法:
     //1. 与前端人员协商能够去掉重定向url? 
     //2. 建立自身的历史栈,舍弃goBack()方法,移除重定向url与重定向后的url,根据需求自行进行loadUrl(需要思考一个合理的跳转逻辑)
     //3. 建立自身的历史栈,与前端配合,提供js函数判断是否为重定向url,捕获url调用js函数,若为重定向url则作过滤处理,则不加入历史栈
public boolean shouldOverrideUrlLoading(WebView view, String url){
    return false;
}
/**
 * 返回上一浏览页面
 * /
public boolean onKeyDown(int keyCode, KeyEvent event) {       
    if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {       
        mWebView.goBack();       
        return true;       
    }       
    return super.onKeyDown(keyCode, event);       
}
// WebClient 中 onReceivedError 的旧方法(API23 已过时)
 @Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
      showErrorTips();// 显示错误页面/提示(大家可以将 WebView 错误页面替换掉、结合 mWebView.reload 实现点击重连)
}

WebView 进阶

WebView 内存泄漏问题

WebView解析网页时会申请Native堆内存用于保存页面元素,当页面较复杂时会有很大的内存占用。如果页面包含图片,内存占用会更严重。并且打开新页面时,为了能快速回退,之前页面占用的内存也不会释放。有时浏览十几个网页,都会占用几百兆的内存。这样加载网页较多时,会导致系统不堪重负,最终强制关闭应用,也就是出现应用闪退或重启。

由于占用的都是Native堆内存,所以实际占用的内存大小不会显示在常用的DDMS Heap工具中(这里看到的只是Java虚拟机分配的内存,一般即使Native堆内存已经占用了几百兆,这里显示的还只是几兆或十几兆)。只有使用adb shell中的一些命令比如dumpsys meminfo 包名,或者在程序中使用Debug.getNativeHeapSize()才能看到。

据说由于WebView的一个BUG,即使它所在的Activity(或者Service)结束也就是onDestroy()之后,或者直接调用WebView.destroy()之后,它所占用这些内存也不会被释放。

解决这个问题最直接的方法是:把使用了WebView的Activity(或者Service)放在单独的进程里。然后在检测到应用占用内存过大有可能被系统干掉或者它所在的Activity(或者Service)结束后,调用System.exit(0),主动Kill掉进程。由于系统的内存分配是以进程为准的,进程关闭后,系统会自动回收所有内存。

WebView 缓存机制

推荐文章Android WebView缓存机制详解深入探究webView的缓存机制

经过一番搜索得来的结果:

  1. WebView缓存分为:页面缓存和数据缓存。页面缓存指加载网页时,对页面或资源数据的缓存。一般使用RE管理器进入目录: “/data/data/(packageName)/cache/org.chromium.android_webview“可看到;
    数据缓存又分为 AppCache 与 DOM Storage 。AppCache可以有选择地缓存我们所想要缓存的东西;DOM Storage 则是HTML5的一个缓存机制,常用于存储简单的表单数据,关于DOM Storage,详情可学习参考下 “浅谈HTML5 的DOM Storage机制” 一文。
  2. webView的缓存模式:
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,都使用缓存中的数据。
  1. 将缓存路径转移到外置sd卡
    查看 Context 类API 发现这样一个方法,重写该方法即可(注意赋予相关权限,但Android 4.4 上权限限制,会使该方法失效)
    另外趁机附上:Android 外部存储权限分析(译)Android存储访问及目录
/**
     * Returns the absolute path to the application specific cache directory
     * on the filesystem. These files will be ones that get deleted first when the
     * device runs low on storage.
     * There is no guarantee when these files will be deleted.
     *
     * <strong>Note: you should not <em>rely</em> on the system deleting these
     * files for you; you should always have a reasonable maximum, such as 1 MB,
     * for the amount of space you consume with cache files, and prune those
     * files when exceeding that space.</strong>
     *
     * @return The path of the directory holding application cache files.
     *
     * @see #openFileOutput
     * @see #getFileStreamPath
     * @see #getDir
     */
        public abstract File getCacheDir();
File extStorageAppCachePath = null;
    public File getCacheDir() {
        //读写权限判断
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            //获取外置存储路径,生成相应缓存路径
            File externalStorageDir = Environment.getExternalStorageDirectory();
            if (externalStorageDir != null) {
                extStorageAppCachePath = new File(externalStorageDir.getAbsolutePath() +
                        File.separator + "Android" + File.separator + "data" + File.separator + getPackageName() + File.separator + "webViewCache");
                //路径不存在,创建
                if (!extStorageAppCachePath.exists()) {
                    if (!extStorageAppCachePath.mkdirs()) {
                        //创建路径失败
                        extStorageAppCachePath = null;
                        return super.getCacheDir();
                    } else {
                        //成功创建,返回路径
                        if (extStorageAppCachePath != null) {
                            return extStorageAppCachePath;
                        }
                    }
                } else {
                    //路径已存在,直接返回
                    if (extStorageAppCachePath != null) {
                        return extStorageAppCachePath;
                    }
                }
            }
        }
        return super.getCacheDir();
    }

结束语

本文主要是对自己学习WebView的过程、应用WebView遇到的一些问题,结合强大的网络资源总结而来,如果错漏,恳请指教,希望能给大家提供小小的帮助,在分享技术过程中,提升、成长!

上一篇 下一篇

猜你喜欢

热点阅读