Android WebView常见用法解析及填坑指南

一、简介

WebView是Android系统中的原生控件,其主要功能与前端页面进行响应交互。
Android的Webview在低版本和高版本采用了不同的webkit版本内核,4.4后直接使用了Chrome。

WebView控件功能强大,除了具有一般View的属性和设置外,还可以对url请求、页面加载、渲染、页面交互进行强大的处理。

二、WebView相关配置:WebSettings

WebSettings webSettings = webView.getSettings();

//常用操作
webSettings.setJavaScriptEnabled(true); -> 是否开启JS支持
webSettings.setPluginsEnabled(true); -> 是否开启插件支持
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); -> 是否允许JS打开新窗口

//设置自适应屏幕,两者合用
webSettings.setUseWideViewPort(true); -> //将图片调整到适合webview的大小 
webSettings.setLoadWithOverviewMode(true); -> 缩放至屏幕大小

//缩放操作
webSettings.setSupportZoom(true); -> 是否支持缩放
webSettings.setBuiltInZoomControls(true); -> 是否支持缩放变焦,前提是支持缩放
webSettings.setDisplayZoomControls(false); -> 是否隐藏缩放控件

webSettings.setAllowFileAccess(true); -> 是否允许访问文件
webSettings.setDomStorageEnabled(true); -> 是否节点缓存
webSettings.setDatabaseEnabled(true); -> 是否数据缓存
webSettings.setAppCacheEnabled(true); -> 是否应用缓存
webSettings.setAppCachePath(uri); -> 设置缓存路径

webSettings.setMediaPlaybackRequiresUserGesture(false); -> 是否要手势触发媒体
webSettings.setTextZoom(100); -> 设置文本缩放的百分比
webSettings.setMinimumFontSize(8); -> 设置文本字体的最小值(1~72)
webSettings.setDefaultFontSize(16); -> 设置文本字体默认的大小

webSettings.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); -> 按规则重新布局
webSettings.setLoadsImagesAutomatically(false); -> 是否自动加载图片
webSettings.setDefaultTextEncodingName("UTF-8"); -> 设置编码格式
webSettings.setNeedInitialFocus(true); -> 是否需要获取焦点
webSettings.setGeolocationEnabled(false); -> 设置开启定位功能
webSettings.setBlockNetworkLoads(false); -> 是否从网络获取资源
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存 

三、WebViewClient类

作用:用于处理各种通知,以及请求事件

WebViewClient webViewClient = new WebViewClient(){
  @Override
  public void onPageStarted(WebView view, String url, Bitmap favicon) {

  }

  @Override
  public void onPageFinished(WebView view, String url) {

  }

  @Override
  public boolean onLoadResource(WebView view, String url) {

  }

  @Override
  public boolean shouldOverrideUrlLoading(WebView view, String url) {
 
  }

  @Override
  public void onReceivedError(WebView view, int errorCode, 
    String description, String failingUrl){
    view.loadUrl("file:///android_assets/error.html"); -> assets目录下放置文件
}

webView.setWebViewClient(webViewClient);

各回调方法作用:

onPageStarted():页面开始加载时调用,这时候可以显示加载进度条
onPageFinished():页面完成加载时调用,这时候可以隐藏加载进度条
onLoadResource():页面每次加载资源时调用。每一个资源(比如图片)的加载都会调用一次。
shouldOverrideUrlLoading():给WebView提供时机,让其选择是否对UrlLoading进行拦截。
onReceivedError():页面加载发生错误时调用,这时候可以跳转到自定义的错误提醒页面,总比系统默认的错误页面美观,优化用户体验。
onReceivedHttpError():页面加载请求时发生错误。
onReceivedSslError():页面加载资源时发生错误。
shouldOverrideKeyEvent():覆盖按键默认的响应事件,这时候可以根据自身的需求在点击某些按键时加入相应的逻辑。
onScaleChanged():页面的缩放比例发生变化时调用,这时候可以根据当前的缩放比例来重新调整WebView中显示的内容,如修改字体大小、图片大小等。
shouldInterceptRequest():可以根据请求携带的内容来判断是否需要拦截请求。

四、WebChromeClient类

作用:辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题,设置加载进度条等等

onProgressChanged():页面加载进度发生变化时调用,可以通过该方法实时向用户反馈加载情况,如显示进度条等。

webview.setWebChromeClient(new WebChromeClient(){

      @Override
      public void onProgressChanged(WebView view, int newProgress) {
          if (newProgress < 100) {
              String progress = newProgress + "%";
              progress.setText(progress);
            } else {
        }
    });

onReceivedIcon():接收Web页面的图标,可以通过该方法把图标设置在原生的控件上。

前三个比较常用
onProgressChanged():加载进度发生变化时调用,可以通过该方法设置加载进度条.
onReceivedIcon():接收Web页面的图标,可以通过该方法把图标设置在原生的控件上
onReceivedTitle():接收Web页面的标题,可以通过该方法把图标设置在原生的控件上.

onPermissionRequest()Web页面请求Android权限时调用。
onPermissionRequestCanceled()Web页面请求Android权限被取消时调用。
onShowFileChooser()Web页面上传文件时调用。
getVideoLoadingProgressView():自定义媒体文件播放加载时的进度条。
getDefaultVideoPoster():设置媒体文件默认的预览图。
onShowCustomView():媒体文件进入全屏时调用。
onHideCustomView():媒体文件退出全屏时调用。

五、Android原生与H5交互

交互方式

JAVA调用JS代码
方式一: 使用WebViewloadUrl()方法,以loadUrl(script)的方式调用。
方式二: 使用WebViewevaluateJavascript()方法。

JS调用JAVA代码
方式一: 使用WebViewaddJavascriptInterface()方法注入对象。
方式二: 使用WebViewClientshouldOverrideUrlLoading()方法回调拦截请求。
方式三: 重写 WebChromeClientonJsAlert()onJsConfirm()onJsPrompt()方法回调拦截JS对话框alert()confirm()prompt() 消息。

Java调用Js:

假设前端有下面这个方法:

 function javaCallJsNoParam(){
       document.getElementById("result").innerHTML= 'JAVA调用JS成功!';
  }

java这样调用:

webView.loadUrl("javascript:javaCallJsNoParam()");

或者:

webView.evaluateJavascript(javascript, new ValueCallback<String>() {
            @Override
            public void onReceiveValue(String response) {
                //response为响应数据
            	}
            }
        });

JS调用Java:

主要方法:

public void addJavascriptInterface (Object object, String name)

Object: 要注入到WebViewJavaScript上下文中的Java对象。不能为空。
String: 该名称用于在JS中表示注入对象,不能为空。

作用:将提供的Java对象注入到此WebView中。使用提供的名称将对象注入到网页的所有框架中,包括所有iframes。这允许从JavaScript访问Java对象的方法。

示例:

class JsObject {
	//1.要被JS调用的方法添加@JavascriptInterface
    @JavascriptInterface
    public String javaMethod() { 
   		return "injectedObject"; 
    }
 }
 webview.getSettings().setJavaScriptEnabled(true);
 //2、注入java对象,后面的字符串也需与前端约定好
 webView.addJavascriptInterface(new JsObject(), "injectedObject");
 webView.loadUrl(url);

六、常见踩坑经历

6.1.WebView的内存泄露

问题描述:
webview内存泄露的情况还是很严重的,尤其是当你加载的页面比较庞大的时候。

解决方案:

1 单独为WebView所在的activity开一个进程

 <---你的WebView所在的activity->
 <activity
      android:name="com.processkill.B"
      android:process="com.xxx.xxx" //为WebView所在的activity单独开一个进程
      android:label="@string/app_name" >
 </activity>

onDestroy中:

@Override
public void onDestroy() {
       android.os.Process.killProcess(android.os.Process.myPid());
       super.onDestroy();
}

2.在代码中创建和销毁WebView

1.不要在xml中使用WebView,通过一个ViewGroup,使用代码动态往ViewGroupaddView(webview),这样可以在onDestory()里销毁掉webview及时清理内存。
2.创建webview需要使用applicationContext而不是activity的context,销毁时不再占有activity对象
3.结束时需要及时销毁WebView

创建:

@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        mWebView = new WebView(getApplicationContext());
        mWebView.setLayoutParams(layoutParams);
        mLayout.addView(mWebView);
    }

销毁:

@Override
protected void onDestroy() {
    if (mWebView != null) {
		 //移除WebView
        mLayout.removeAllViews();  
        //或者这么移除
		ViewParent parent = mWebView.getParent();
        if (parent != null) {
            ((ViewGroup) parent).removeView(mWebView);
        }
		
        mWebView.stopLoading();
        // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
        //同时可以避免webView加载的一些html里的js在后台持续耗电问题
        mWebView.getSettings().setJavaScriptEnabled(false);
        //销毁VebView
        mWebView.removeAllViews();  
        mWebView.destroy();
        mWebView = null;
    }
     super.onDestroy();
}

3.使用腾讯的X5WebView,相关创建销毁问题也需要自己注意。

6.2.WebView shouldOverrideUrlLoading功能误区

网上常见错误解释:阻止WebView调用系统浏览器

若想让WebView在loadUrl时,不调用系统浏览器,设置自定的WebViewClient即可。

 webView.setWebViewClient(new WebViewClient());

shouldOverrideUrlLoading接口:

 webView.setWebViewClient(new WebViewClient(){
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        // WebView不加载该Url
        return true;
    }
});

```java
 webView.setWebViewClient(new WebViewClient(){
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        // WebView加载该Url
        return false;
    }
});

该接口返回值的解释:

If a WebViewClient is provided, returning true causes the current WebView to abort loading the URL, 
while returning false causes the WebView to continue loading the URL as usual.

总的来说:

1.若没有设置 WebViewClient 则由系统(Activity Manager)处理该 url,通常是使用浏览器打开或弹出浏览器选择对话框,让用户选择用哪个浏览器打开。
2.若设置 WebViewClient 且该方法返回 true ,则说明由应用的代码处理该 url,WebView 不加载该url,也就是程序员自己做处理。
3.若设置 WebViewClient 且该方法返回 false,则说明由 WebView 处理该 url,即用 WebView 加载该 url。

6.3. WebView监听加载完成

onPageFinished,尽量不要使用这个函数,会多次调用,通过这个函数基本上监听不到加载完成,参考http://stackoverflow.com/questions/3149216/how-to-listen-for-a-webview-finishing-loading-a-url-in-android
使用WebChromeClient里onProgressChanged这个方法来监听,可能都比onPageFinished要好点,用这个方法判断是否加载完成,不过会触发多次,就是会出现多次newProgress==100的情况。可以用一个boolean变量控制一下。

 boolean  isFirst=true;
 webView.setWebChromeClient(new WebChromeClient() {
     @Override
     public void onProgressChanged(WebView view, int newProgress) {
         super.onProgressChanged(view, newProgress);
         if (newProgress == 100) {
         	isFirst=false;
           	...
         }
     }
 });

6.4. 监听WebView滑动到底端的问题

我之前写过一个:https://blog.csdn.net/qq_34512207/article/details/115760720?spm=1001.2014.3001.5501

6.5.WebView返回上一页

 public void myOnclick() {
//      返回按钮,返回到上一网页
        llBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(webView.canGoBack()){
                    webView.goBack();
                }else {
                    finish();
                }

            }
        });
    }

    /**
     * 返回键返回上一网页
     */
    @Override
    public void onBackPressed() {
        if(webView.canGoBack()){
            webView.goBack();
        }else {
            finish();
        }
    }

6.6.WebView在Android 9.0 以上,多进程报错问题

报错如下:

 java.lang.RuntimeException: Using WebView from more than one process at once with the same data directory is not supported.

即:不支持同时多个进程中WebView使用具有相同数据目录的WebView资源。
解决:在application的onCreate初始化中添加以下代码:


    /**
     * 为webView设置目录后缀
     * @param context
     */
    public  void initWebViewDataDirectory(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            String processName = getProcessName(context);
            LogUtils.e(TAG, "processName = " + processName);
            LogUtils.e(TAG, "getPackageName = " + context.getPackageName());
            if (!context.getPackageName().equals(processName)) {//判断是否是默认进程名称
                WebView.setDataDirectorySuffix(processName);
            }
        }
    }
    /**
     * 得到进程名称
     * @param context
     * @return
     */
    public  String getProcessName(Context context) {
        try {
            if (context == null)
                return null;
            ActivityManager manager = (ActivityManager)
                    context.getSystemService(Context.ACTIVITY_SERVICE);
            for (ActivityManager.RunningAppProcessInfo processInfo :
                    manager.getRunningAppProcesses()) {
                if (processInfo.pid == android.os.Process.myPid()) {
                    return processInfo.processName;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

建议:初始化放到attachBaseContext方法中

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值