WebView你可能不知道的细节

前言

现在Android应用native和H5结合的越来越紧密。最近也一直在处理Android原生页面和H5交互的问题,发现好多以前都没有注意过的细节,统一整理下,希望对自己,对大家有所帮助。

WebSettings设置

//设置是否支持缩放,我这里为false,默认为true。
mWebView.getSettings().setSupportZoom(false);

//设置是否显示缩放工具,默认为false
mWebView.getSettings().setBuiltInZoomControls(false);

//设置默认的字体大小,默认为16,有效值区间在1-72之间
mWebView.getSettings().setDefaultFontSize(18);

//首先阻塞图片,让图片不显示
mWebSettings.setBlockNetworkImage(true);

//页面加载好以后,在放开图片
mWebSettings.setBlockNetworkImage(false);

//设置自适应屏幕
webSettings.setUseWideViewPort(true); 
// 缩放至屏幕的大小
webSettings.setLoadWithOverviewMode(true); 

//支持插件
webSettings.setPluginsEnabled(true); 

//提高渲染等级
mWebSettings.setRenderPriority(WebSettings.RenderPriority.HIGH);

//禁止webview上面控件获取焦点(黄色边框)
mWebSettings.setNeedInitialFocus(false);

//关闭webview中缓存 
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); 

//设置可以访问文件 
webSettings.setAllowFileAccess(true); 

//支持通过JS打开新窗口 
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

//支持自动加载图片 
webSettings.setLoadsImagesAutomatically(true); 

//设置编码格式
webSettings.setDefaultTextEncodingName("utf-8");

webview加载方式

加载asset中html文件

webview.loadUrl("file:///android_asset/index.html");

加载网络url

webview.loadUrl("http://www.baidu.com");

加载手机本地html文件

webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");

加载带参数的post接口

String postDate = "name=zhangsan&age=18&sex=1";
webView.postUrl("www.test.com",EncodingUtils.getBytes(postDate,"BASE64"));

加载html字符串

//不支持#、%、\、? 四种字符
webView.loadData(URLEncoder.encode(data, encoding),"text/html", "utf-8");  

webView.loadDataWithBaseURL("about:blank", html, "text/html", "utf-8", null);

Back键控制网页后退

public boolean onKeyDown(int keyCode, KeyEvent event) {
  if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) { 
      mWebView.goBack();
      return true;
  }
  return super.onKeyDown(keyCode, event);
}

是否调用外部浏览器

webview.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        view.loadUrl(url);
        return true;
    }
});

当设置WebViewClient时,不会跳转到外部浏览器,否则会跳转到外部浏览器

Webview内存泄漏问题

通常我们声明一个webview是使用xml布局实现的

<WebView
    android:id="@+id/webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

然而使用这种方式,加载webview的Activity得不到销毁,即使使用以下代码也没用任何效果

@Override
protected void onDestroy() {
    super.onDestroy();
    webview.destroy();
    webview=null;
}

为了验证加载webview的Activity一直被持有,可以通过Android Studio来查看
点击Android Monitor,然后选择Monitors,点击下图圆圈部分


image

然后会生成一个.hprof文件,进入到Activity目录下面


image

你会发现没进入一次,WenviewActivity的total cout和heap cout会增加一次。

解决使用webview造成的内存泄漏问题,我们通常不使用xml布局文件声明webview,而是动态生成webview。

首先是在xml文件中的webview位置生明一个LinearLayout

<LinearLayout
    android:id="@+id/webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

然后只onCreate方法动态生明一个webview,把webview添加到LinearLayout中

webView=new WebView(getApplicationgContext());
linearLayout.addView(webview);

最后中onDestroy()移除LinearLayout所有子试图

 @Override
protected void onDestroy() {
    super.onDestroy();
    webView.removeAllViews()
    webview.destroy();
    webview=null;
}

javascript互调

原生与H5互相调用情况之一是javascript互调。

不管是H5调用原生还是原生调用H5,首先我们都需要设置webview支持javascript,所以我们必须加上下面这行代码

WebView.getSettings().setJavaScriptEnabled(true);

有了javascript支持,你就可以尽情的去相互调用了。

我们通过一个实际示例来分析一下

有这么一个需求,Html页面上长按语音录制按钮调用手机原生语音录制功能,然后对语音进行识别,把识别后的String传递个Html。在这个需求中,就涉及到Html调用原生语音录制以及原生调用Htmljavascript方法将识别后的语音通过字符串传给Html。

首先我们实现长按Html按钮调用本地语音录制,这就是Html调用原生方法了,这里的方法是本地创建的js方法

private class AshJavaScriptInterface{
    public AshJavaScriptInterface(BaseActivity context) {
        super(context);
    }
    @android.webkit.JavascriptInterface
    public void onTouchDown() {
        //TODO 按钮被按下,类似于原生按下操作
    }

    @android.webkit.JavascriptInterface
    public void onTouchUp() {
        //TODO 按钮被松开,类似于原生松开操作
    }
}

将创建的Js方法设置到webview中

public AshJavaScriptInterface createJsInterface() {
    return new AshJavaScriptInterface(this);
}

webView.addJavascriptInterface(jsExec,GlobalVariable.JS_INTERFACE_OBJ;

然后剩余的工作就是Html前端的工作了,前端监听按钮事件,当按钮被按下时调用onTouchDown方法(比如QBaoJSBridge.onTouchDown()),而客户端就在onTouchDown方法中处理按钮被按下后的操作,当手松开之后,前端调用onTouchUp方法(比如QBaoJSBridge.onTouchUp()),客户端同时在onTouchUp方法中处理手被松开的操作。到这里,按钮长按监听就完成了,这里主要是Html调用原声Js方法。

语音录制之后,客户端需要对录制的语音进行识别,将语音转换成String字符串(我们这里是使用百度语音识别),识别过程是一个异步耗时的操作,当我们完成识别之后需要把字符串传递给Html,那么我们就需要调用Html的js方法了

webView.loadUrl("javascript:sendMsgByApp(\"" + msg + "\")");

sendMsgByApp方法就是前端定义的一个Js方法,做完这一步,这个需求整个流程就结束了,整个过程中实现了Html和原生的js相互调用

除了以上场景,比如Html要获取手机设备号,点击Html按钮弹出对话框等都可以通过调用本地方法实现

html链接处理

我们常常会遇到点击Html按钮跳转到原生指定的Activity页面或者获取url链接中的参数进行本地接口调用等情况

那么我们就需要对url链接进行处理,通过判断url进行不同需求操作

具体如何实现的呢?

首先要设置webview的WebViewClient,当我们点击html按钮或者链接时会调用shouldOverrideUrlLoading方法,所以我们对点击进行操作时通常也是在shouldOverrideUrlLoading方法中进行处理

 webview.setWebViewClient(new WebViewClient(){
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request){
        boolean isHandle = handleProtocol(url);
        if (isHandle){
        return true;
    }
        return super.shouldOverrideUrlLoading(view, request);
    }
});

handleProtocol就是url处理方法

protected boolean handleQianBaoProtocol(String url) {
    Uri uri = Uri.parse(url);
    String scheme = uri.getScheme();
    String host = uri.getHost();
    String path = uri.getPath();
    
    if ("https".equals(uri.getScheme())
                && "maliprod.alipay.com".equals(uri.getHost())
                && "/w/trade_pay.do".equals(uri.getPath())) {
        String orderIds = uri.getQueryParameter("pay_order_id");
        if (!TextUtils.isEmpty(orderIds)) {
            //提交商品订单
        }
    }
    
     if ("newtab".equals(uri.getScheme())
            && "goodstuff.qbao.com".equals(uri.getHost())
            && "/similar".equals(uri.getPath())) {
        String pid = uri.getQueryParameter("pid");
        FindLikeActivity.startActivity(this, pid);
        return true;
    }
return false;
}

webview长按监听

长按主要是针对图片、链接、电话、email等等,所以监听长按,我们可以进行图片打开、调用手机邮箱、打电话、分享等操作

监听代码如下

webview.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
        WebView.HitTestResult result = ((WebView) v).getHitTestResult();
        if (null == result) return true;
        int type = result.getType();
        if (type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE ||
                type == WebView.HitTestResult.IMAGE_TYPE) {
            String imgUrl = result.getExtra();
            if (!TextUtils.isEmpty(imgUrl)) {
                onImgLongClick(imgUrl);
                return true;
            }
        }
        return true;
    }
});

以上代码主要长按打开图片,其他的具体操作可以通过查看HitTestResult类,里面有不同类型的常量,通过判断type来进行我们具体的操作

设置html加载进度

加载网络url一般都是耗时操作,而且当网络环境比较差的时候,加载花费的时间可能更长,为了比较好的体验,我们通常会给一个加载进度。


class MyWebChrimeClien extends WebChromeClient {
    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        if (newProgress == 100) {
            mProgress.setVisibility(View.GONE);
        } else {
            if (View.INVISIBLE == mProgress.getVisibility()) {
                mProgress.setVisibility(View.VISIBLE);
            }
            mProgress.setProgress(newProgress);
        }
        super.onProgressChanged(view, newProgress);
    }
}

mWebView.setWebChromeClient(new MyWebChrimeClien());

主要是通过设置WebChromeClient,通过newProgress的数字判断加载到什么位置,这个进度是包含百分比的,如果不需要知道具体百分比,可以通过设置WebViewClient,
重写onPageStarted方法和onPageFinished方法

class MyWebViewClient extends WebViewClient {
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        view.loadUrl(url);
        return true;
    }

    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);
        mProgress.setVisibility(View.VISIBLE);
    }

    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        mProgress.setVisibility(View.GONE);
    }
}

mWebView.setWebViewClient(new MyWebViewClient());

具体使用哪种方式,那就看你实际需求了,而且进度条也可以根据自己的需求进行自定义

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容