Android控件<第二十四篇>:超详细的Webview攻略(二)

由于文章太长,官方不让发布,所以文章就一分为二了

超详细的Webview攻略(一)

WebViewClient相关方法

  1. shouldOverrideUrlLoading(WebView view, String url)和shouldOverrideUrlLoading(WebView view, WebResourceRequest request)

shouldOverrideUrlLoading执行时机是重定向时(网页自动重定向或手动点击网页内部链接)

    mywebview.setWebViewClient(new WebViewClient(){

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            //过时
            Log.d("yunchong", "shouldOverrideUrlLoading1:"+url);
            if(!TextUtils.isEmpty(url)){
                if(url.startsWith("http://") || url.startsWith("https://")){
                    view.loadUrl(url);
                } else{
                    //跳转到第三方
                    try {
                        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                        MainActivity.this.startActivity(intent);
                    }catch (Exception e){
                        //如果抛出异常,则说明本地没有安装第三方
                        e.printStackTrace();
                    }
                }
                return true;
            }
            return false;
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
            Log.d("yunchong", "shouldOverrideUrlLoading2:"+url);
            String url = view.getUrl();
            if(!TextUtils.isEmpty(url)){
                if(url.startsWith("http://") || url.startsWith("https://")){
                    view.loadUrl(url);
                } else{
                    //跳转到第三方
                    try {
                        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                        MainActivity.this.startActivity(intent);
                    }catch (Exception e){
                        //如果抛出异常,则说明本地没有安装第三方
                        e.printStackTrace();
                    }
                }
                return true;
            }
            return false;
        }

主要注意以下几点:

(1)前者在API24(Android 7.0)之后已经过时,虽然已经过时了,但是我们还是需要用到这个方法的,原因是API24之前的手机只执行前者,后者只有在API24之后才会执行;
(2)以上代码中,后者的返回值有true和false两种, 第三种返回值是默认返回值

   return super.shouldOverrideUrlLoading(view, request);

这个返回值最终还是会执行前者方法。

2.shouldInterceptRequest(WebView view, String url)和shouldInterceptRequest(final WebView view, WebResourceRequest request)

        @Nullable
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
            //过时
            Log.d("yunchong", url);
            return super.shouldInterceptRequest(view, url);
        }

捕获的日志如下:

图片.png

此方法废弃于API21,调用于非UI线程,虽然废弃了,但是我在Android4.4、Android6.0、Android8.0的手机经过测试,依然可以拦截到加载资源
拦截资源请求并返回响应数据,返回null时WebView将继续加载资源
注意:API21以下的AJAX请求会走onLoadResource,无法通过此方法拦截

在API21之后新增一个新的方法:

        @Nullable
        @Override
        public WebResourceResponse shouldInterceptRequest(final WebView view, WebResourceRequest request) {
            view.post(new Runnable() {
                @Override
                public void run() {
                    Log.d("yunchong", "shouldInterceptRequest2:"+view.getUrl());
                }
            });
            return super.shouldInterceptRequest(view, request);
        }

用于替代前者。

3.onLoadResource(WebView view, String url)

这个方法和shouldInterceptRequest一样,同样可以拦截到加载资源,他们的区别是:shouldInterceptRequest已过时,onLoadResource没有过时。

3.onTooManyRedirects(WebView view, Message cancelMsg, Message continueMsg)

这个方法是为了解决一些重定向问题,已经彻底废弃,现在处理重定向问题是用shouldOverrideUrlLoading方法了。

4.onPageStarted和onPageFinished

网页的加载开始和加载结束。

5.onPageCommitVisible(WebView view, String url)

        // 这个回调添加于API23,仅用于主框架的导航
        // 通知应用导航到之前页面时,其遗留的WebView内容将不再被绘制。
        // 这个回调可以用来决定哪些WebView可见内容能被安全地回收,以确保不显示陈旧的内容
        // 它最早被调用,以此保证WebView.onDraw不会绘制任何之前页面的内容,随后绘制背景色或需要加载的新内容。
        // 当HTTP响应body已经开始加载并体现在DOM上将在随后的绘制中可见时,这个方法会被调用。
        // 这个回调发生在文档加载的早期,因此它的资源(css,和图像)可能不可用。
        // 如果需要更细粒度的视图更新,查看 postVisualStateCallback(long, WebView.VisualStateCallback).
        // 请注意这上边的所有条件也支持 postVisualStateCallback(long ,WebView.VisualStateCallback)
        @Override
        public void onPageCommitVisible(WebView view, String url) {
            Log.d("yunchong", "onPageCommitVisible:"+url);
            super.onPageCommitVisible(view, url);
        }

跟踪日志发现任意的网页跳转都会执行onPageCommitVisible方法,参数url就是当前页面的url。

6.onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse)

        // 此方法添加于API23
        // 在加载资源(iframe,image,js,css,ajax...)时收到了 HTTP 错误(状态码>=400)
        @Override
        public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
            super.onReceivedHttpError(view, request, errorResponse);
        }

7.onFormResubmission(WebView view, Message dontResend, Message resend)

        // 是否重新提交表单,默认不重发
        @Override
        public void onFormResubmission(WebView view, Message dontResend, Message resend) {
            super.onFormResubmission(view, dontResend, resend);
        }

8.doUpdateVisitedHistory(WebView view, String url, boolean isReload)

        // 通知应用可以将当前的url存储在数据库中,意味着当前的访问url已经生效并被记录在内核当中。
        // 此方法在网页加载过程中只会被调用一次,网页前进后退并不会回调这个函数。
        @Override
        public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
            super.doUpdateVisitedHistory(view, url, isReload);
        }

9.onReceivedClientCertRequest(WebView view, ClientCertRequest request)

        // 此方法添加于API21,在UI线程被调用
        // 处理SSL客户端证书请求,必要的话可显示一个UI来提供KEY。
        // 有三种响应方式:proceed()/cancel()/ignore(),默认行为是取消请求
        // 如果调用proceed()或cancel(),Webview 将在内存中保存响应结果且对相同的"host:port"不会再次调用 onReceivedClientCertRequest
        // 多数情况下,可通过KeyChain.choosePrivateKeyAlias启动一个Activity供用户选择合适的私钥
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
            super.onReceivedClientCertRequest(view, request); //默认
            //request.cancel();//取消
            //request.ignore();//忽视
            //request.proceed(param1, param2);//接受
        }

10.onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm)

        // 处理HTTP认证请求,默认行为是取消请求
        @Override
        public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
            Log.d("yunchong", "onReceivedHttpAuthRequest");
            super.onReceivedHttpAuthRequest(view, handler, host, realm);//默认
            //handler.cancel();//取消
            //handler.proceed(username, password);//接受
        }

11.shouldOverrideKeyEvent(WebView view, KeyEvent event)

        // 给应用一个机会处理按键事件
        // 如果返回true,WebView不处理该事件,否则WebView会一直处理,默认返回false
        @Override
        public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
            return super.shouldOverrideKeyEvent(view, event);
        }

12.onUnhandledKeyEvent(WebView view, KeyEvent event)

        // 处理未被WebView消费的按键事件
        // WebView总是消费按键事件,除非是系统按键或shouldOverrideKeyEvent返回true
        // 此方法在按键事件分派时被异步调用
        @Override
        public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
            super.onUnhandledKeyEvent(view, event);
        }

13.onScaleChanged(WebView view, float oldScale, float newScale)

        // 通知应用页面缩放系数变化
        @Override
        public void onScaleChanged(WebView view, float oldScale, float newScale) {
            super.onScaleChanged(view, oldScale, newScale);
        }

当webview支持双指缩放时,onScaleChanged回调方法可以监听webview的缩放系数。

14.onReceivedLoginRequest(WebView view, String realm, @Nullable String account, String args)

        //通知应用有个已授权账号自动登陆了
        @Override
        public void onReceivedLoginRequest(WebView view, String realm, @Nullable String account, String args) {
            super.onReceivedLoginRequest(view, realm, account, args);
        }

15.onRenderProcessGone(WebView view, RenderProcessGoneDetail detail)

        //这个API处理一个WebView对象的渲染程序消失的情况,要么是因为系统杀死了渲染器以回收急需的内存,要么是因为渲染程序本身崩溃了。
        // 通过使用这个API,您可以让您的应用程序继续执行,即使渲染过程已经消失了。
        @RequiresApi(api = Build.VERSION_CODES.O)
        @Override
        public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
            //RenderProcessGoneDetail : 此类提供了有关渲染进程退出原因的更具体信息。应用程序可以使用它来决定如何处理这种情况。
            //RenderProcessGoneDetail提供了两个方法
            //didCrash():指示是否观察到呈现进程崩溃,或者是否被系统终止。
            //rendererPriorityAtExit():返回在渲染器退出时设置的渲染器优先级。
            if(!detail.didCrash()){
                //由于系统内存不足,渲染器被终止。
                //通过创建新的WebView实例,应用程序可以正常恢复
                if (mywebview != null) {
                    ViewGroup webViewContainer = (ViewGroup) findViewById(R.id.mywebview);
                    webViewContainer.removeView(mywebview);
                    mywebview.destroy();
                    mywebview = null;
                }
                return true;
            }
            return false;
        }

16.onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponse callback)

        //通知主应用程序加载URL已被安全浏览标记。
        @Override
        public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponse callback) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
                callback.backToSafety(true);//类似于用户点击了“返回安全”按钮
                callback.proceed(true);//类似于用户单击了“访问此不安全站点”按钮一样。
                callback.showInterstitial(true);//如果为true则显示报告复选框
            }
            super.onSafeBrowsingHit(view, request, threatType, callback);
        }

以上的注释是由谷歌官方文档翻译而来,可悲的是我尝试了7.0、8.0、8.1的手机仍然没有执行到该方法。没有发现此方法的执行时机。

图片.png

17.onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)

当加载https网页的情况下,默认不进行证书校验,当服务器要求webview必须进行证书校验的时候,默认情况下加载网页是一片空白,这个时候我们需要在onReceivedSslError方法里做一些处理

        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            handler.proceed();
        }

也就是说,接受所有的https证书。
然而这样做是不安全的,我们需要对证书做ssl校验,代码如下:(我们事先将crt证书放入assets目录下)

@Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
    String currentUrl = view.getUrl();
    if(TextUtils.isEmpty(currentUrl)){//个别手机获取到的url是空的
        currentUrl = homeUrl;//这里的homeUrl是从其他地方获取到的
    }
    SslManager.webviewSsl(currentUrl, new SslListener() {
        @Override
        public void onResponse() {
            handler.proceed();
        }

        @Override
        public void onFailure() {
            handler.cancel();
        }
    });
}


public class SslManager {

    /**
     * webview ssl校验
     * @param url
     */
    public static void webviewSsl(String url, final SslListener sslListener) {

        if(!TextUtils.isEmpty(url)){
            getOkHttpBuilder(url).build().newCall( new Request.Builder().url(url).build()).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    sslListener.onFailure();
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    sslListener.onResponse();
                }
            });
        }
    }


    public static OkHttpClient.Builder getOkHttpBuilder(String url){
        OkHttpClient.Builder builder = null;
        String crt;
        if(!TextUtils.isEmpty(url)){
            crt = "crt/crtname.crt";
            try {
                builder = setCertificates(new OkHttpClient.Builder(), IMApp.getAppContext().getResources().getAssets().open(crt));
            } catch (IOException e) {
                builder = new OkHttpClient.Builder();
            }
        }
        return builder;
    }

    private static OkHttpClient.Builder setCertificates(OkHttpClient.Builder client, InputStream... certificates) {
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates) {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
                try {
                    if (certificate != null)
                        certificate.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
            SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
            X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory);
            client.sslSocketFactory(sslSocketFactory, trustManager);
            //hostName验证
            client.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    String peerHost = session.getPeerHost();//服务器返回的域名
                    try {
                        X509Certificate[] peerCertificates = (X509Certificate[]) session.getPeerCertificates();
                        for (X509Certificate c : peerCertificates) {
                            X500Principal subjectX500Principal = c.getSubjectX500Principal();
                            String name = new X500p(subjectX500Principal).getName();
                            String[] split = name.split(",");
                            for (String s : split) {
                                if (s.startsWith("CN")) {
                                    if (s.contains(hostname) && s.contains(peerHost)) {
                                        return true;
                                    }
                                }
                            }
                        }
                    } catch (SSLPeerUnverifiedException e) {
                        e.printStackTrace();
                    }
                    return false;
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }
        return client;
    }
}

WebChromeClient相关方法

1.onProgressChanged(WebView view, int newProgress)
这是webview加载网页的进度监听,常用作于添加浏览器的进度条。

    //监听加载网页的进度情况
    mywebview.setWebChromeClient(new WebChromeClient(){
        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            super.onProgressChanged(view, newProgress);
        }

2.onReceivedTitle(WebView view, String title)

        //获取网页的标题
        @Override
        public void onReceivedTitle(WebView view, String title) {
            super.onReceivedTitle(view, title);
        }

3.onReceivedIcon(WebView view, Bitmap icon)

        //字面上的意思是:当前网页接收到icon的时候执行此方法
        @Override
        public void onReceivedIcon(WebView view, Bitmap icon) {
            super.onReceivedIcon(view, icon);
        }

4.onReceivedTouchIconUrl(WebView view, String url, boolean precomposed)

        //字面上的意思是:触摸当前网页接收到icon的时候执行此方法
        @Override
        public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
            super.onReceivedTouchIconUrl(view, url, precomposed);
        }

5.onShowCustomView和onHideCustomView
这两个方法往往是一起使用,常用于解决webview不能全屏播放视频的问题。(有必要的话对当前activity开启硬件加速)
代码如下:

private View customView;//默认view
private FrameLayout fullscreenContainer;//全屏view
/** 视频全屏参数 */
private FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
private WebChromeClient.CustomViewCallback customViewCallback;


        //显示自定义View:常用于视频全屏展示
        @Override
        public void onShowCustomView(View view, CustomViewCallback callback) {
            if (customView != null) {
                callback.onCustomViewHidden();
                return;
            }

            //MainActivity.this.getWindow().getDecorView();

            FrameLayout decor = (FrameLayout) getWindow().getDecorView();
            fullscreenContainer = new FullscreenHolder(MainActivity.this);
            fullscreenContainer.addView(view, COVER_SCREEN_PARAMS);
            decor.addView(fullscreenContainer, COVER_SCREEN_PARAMS);
            customView = view;
            setStatusBarVisibility(false);
            customViewCallback = callback;
        }

        //显示自定义View:常用于视频全屏展示
        @Override
        public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {
            onShowCustomView(view, callback);
        }

        //隐藏自定义View:
        @Override
        public void onHideCustomView() {

            hideCustomView();
            
        }


//视频从全屏还原到原来
private void hideCustomView(){
    if (customView == null) {
        return;
    }

    setStatusBarVisibility(true);
    FrameLayout decor = (FrameLayout) getWindow().getDecorView();
    decor.removeView(fullscreenContainer);
    fullscreenContainer = null;
    customView = null;
    customViewCallback.onCustomViewHidden();

    mywebview.setVisibility(View.VISIBLE);
}

这里还需要注意的是,点击返回键应该退出全屏

@Override
public void onBackPressed() {
    if(customView != null){
        hideCustomView();
    }else if(mywebview !=  null && mywebview.canGoBack()){
        mywebview.goBack();
    }else{
        finish();
    }
}

6.onCreateWindow和onCloseWindow

        //请求主机应用创建一个新窗口。
        //如果主机应用选择响应这个请求,则该方法返回true,并创建一个新的WebView,
        //将其插入到视图系统中,并将其提供的resultMsg作为参数提供给新的WebView。 
        //如果主机应用选择不响应这个请求时,则该方法返回false。 
        //默认情况下,该方法不做任何处理并返回false。
        @Override
        public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
            return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
        }


        //通知主机主机应用WebView关闭了,并在需要的时候从view系统中移除它。
        //此时,WebCore已经停止窗口中的所有加载进度,并在javascript中移除了所有cross-scripting的功能。
        @Override
        public void onCloseWindow(WebView window) {
            super.onCloseWindow(window);
        }

7.onRequestFocus(WebView view)

        @Override
        public void onRequestFocus(WebView view) {
            super.onRequestFocus(view);
        }

不太清楚它的执行时机,其他博客的解释是:请求获得WebView的焦点,这可能由于另一个WebView打开一个连接,需要被展示.

8.三种提示框Alert、Confirm、Prompt

        @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            return super.onJsAlert(view, url, message, result);
        }

        @Override
        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
            return super.onJsConfirm(view, url, message, result);
        }

        @Override
        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
            return super.onJsPrompt(view, url, message, defaultValue, result);
        }

常用的提示框是Alert和Confirm。
网上也是有相关博客 js中三种弹出框

9.onJsBeforeUnload(WebView view, String url, String message, JsResult result)

        //JS相关操作,会在页面关闭或刷新调用,触发事件时,弹出对话框是否关闭,确定则关闭页面,取消则保持该页面。
        @Override
        public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
            return super.onJsBeforeUnload(view, url, message, result);
        }

10.onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater)

        //使webview支持建立html5本地缓存数据库
        @Override
        public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater) {
            quotaUpdater.updateQuota(estimatedDatabaseSize * 2);
        }

11.onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater)

        //已被废弃,不用管
        @Override
        public void onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater) {
            super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
        }

12.webview定位权限提示框

        @Override
        public void onGeolocationPermissionsShowPrompt(final String origin, final GeolocationPermissions.Callback callback) {
            final boolean remember = false;//是否记住
            AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
            builder.setTitle("位置信息");
            builder.setMessage(origin + "允许获取您的地理位置信息吗?")
                    .setCancelable(true)
                    .setPositiveButton("允许", new DialogInterface.OnClickListener() {
                @Override public void onClick(DialogInterface dialog, int id) {
                    callback.invoke(origin, true, remember);
                } })
                    .setNegativeButton("不允许", new DialogInterface.OnClickListener() {
                        @Override public void onClick(DialogInterface dialog, int id) {
                            callback.invoke(origin, false, remember);
                        }
                    });
            AlertDialog alert = builder.create();
            alert.show();
        }

        @Override
        public void onGeolocationPermissionsHidePrompt() {
            super.onGeolocationPermissionsHidePrompt();
        }

13.来自网页的权限请求

        @Override
        public void onPermissionRequest(PermissionRequest request) {

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                request.deny();//调用此方法以拒绝请求(默认)
                request.getOrigin();//调用此方法以获取试图访问受限资源的网页的来源。
                request.getResources();//调用此方法获取网页试图访问的资源。
                request.grant(request.getResources());//调用此方法授予Origin访问给定资源的权限。
            }
        }

        @Override
        public void onPermissionRequestCanceled(PermissionRequest request) {
            super.onPermissionRequestCanceled(request);
        }

默认是拒绝所有请求,开发者可以根据需求授予指定网页的权限请求。

14.onJsTimeout()

        //已过时,不用管
        @Override
        public boolean onJsTimeout() {
            return super.onJsTimeout();
        }

15.onConsoleMessage

        //三个参数的方法已经废弃,被一个参数的方法替代
        @Override
        public void onConsoleMessage(String message, int lineNumber, String sourceID) {
            super.onConsoleMessage(message, lineNumber, sourceID);
        }

        @Override
        public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
            return super.onConsoleMessage(consoleMessage);
        }

可以捕获到网页打印在控制台的日志,有利于日志的跟踪。

16.getDefaultVideoPoster()

        //Html中,视频(video)控件在没有播放的时候将给用户展示一张“海报”图片(预览图)。
        // 其预览图是由Html中video标签的poster属性来指定的。如果开发者没有设置poster属性, 则可以通过这个方法来设置默认的预览图。
        @Override
        public Bitmap getDefaultVideoPoster() {
            Bitmap bitmap = super.getDefaultVideoPoster();
            if(bitmap == null){
                return BitmapFactory.decodeResource(getApplicationContext().getResources(), R.mipmap.ic_launcher);
            }
            return super.getDefaultVideoPoster();
        }

17.getVideoLoadingProgressView()

        //播放视频时,在第一帧呈现之前,需要花一定的时间来进行数据缓冲。
        //ChromeClient可以使用这个函数来提供一个在数据缓冲时显示的视图。
        // 例如,ChromeClient可以在缓冲时显示一个转轮动画。
        @Override
        public View getVideoLoadingProgressView() {
            return super.getVideoLoadingProgressView();
        }

18.getVisitedHistory(ValueCallback<String[]> callback)

        //获得所有访问历史项目的列表,用于链接着色。
        @Override
        public void getVisitedHistory(ValueCallback<String[]> callback) {
            super.getVisitedHistory(callback);
        }

19.让webview支持inputfile控件

public ValueCallback<Uri> mUploadMessage;
public ValueCallback<Uri[]> mUploadMessageForAndroid5;
public final static int FILECHOOSER_RESULTCODE = 1;
public final static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;


        //android3.0以前
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
        }

        //android3.0到android4.0
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
        }

        //android4.0到android4.3
        public void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
            mUploadMessage = filePathCallback;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
        }

        //android5.0以上
        @Override
        public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
            if (mUploadMessageForAndroid5 != null) {
                mUploadMessageForAndroid5.onReceiveValue(null);
                mUploadMessageForAndroid5 = null;
            }

            mUploadMessageForAndroid5 = filePathCallback;
            Intent intent = null;
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                intent = fileChooserParams.createIntent();
            }
            try {
                startActivityForResult(intent, FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
            } catch (ActivityNotFoundException e) {
                mUploadMessageForAndroid5 = null;
                return false;
            }
            return true;
        }
    });


@Override
protected void onActivityResult(int requestCode, int resultCode,Intent intent) {

    if (mUploadMessage != null) {
        mUploadMessage.onReceiveValue(null);
        mUploadMessage = null;
    }
    if (mUploadMessageForAndroid5 != null) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mUploadMessageForAndroid5.onReceiveValue(null);
            mUploadMessageForAndroid5 = null;
        }
    }



    if (requestCode == FILECHOOSER_RESULTCODE) {
        if (null == mUploadMessage){
            return;
        }

        Uri result = intent == null || resultCode != RESULT_OK ? null:intent.getData();
        mUploadMessage.onReceiveValue(result);
        mUploadMessage = null;
    } else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5){
        if (null == mUploadMessageForAndroid5){
            return;
        }
        Uri result = (intent == null || resultCode != RESULT_OK) ? null: intent.getData();
            if (result != null) {
                mUploadMessageForAndroid5.onReceiveValue(new Uri[]{result});
            } else {
                mUploadMessageForAndroid5.onReceiveValue(new Uri[]{});
        }
        mUploadMessageForAndroid5 = null;
    }
}

Android有个很大的坑,添加以上代码几乎可以支持Android的所有版本了,唯独Android4.4.*不支持,这里推荐H5和客户端判断Android的版本,采用JS的方式支持inputfile。

webview之JS交互

  • Android调用JS代码

(1)方法一:采用loadUrl方式

第一步:准备好html静态页面,写好JS方法

<!DOCTYPE html>
<html>
<head> <meta charset="utf-8">
    <title>Carson_Ho</title> // JS代码
    <script>
   function callJS(){
      alert("Android调用了JS的callJS方法");
   }
</script>
</head>
</html>

第二步:加载该页面

mywebview.loadUrl("file:///android_asset/htmldemo.html");

第三步:点击某按钮,加载JS代码

@Override
public void onClick(View v) {
    switch (v.getId()){
        case R.id.bt_js:
            mywebview.loadUrl("javascript:callJS()");
            break;
    }
}

效果展示:


图片.png

弊端:会刷新当前页面,执行效率慢。

(2)方法二:采用evaluateJavascript方式

其步骤和展示效果和方法一一样

@Override
public void onClick(View v) {
    switch (v.getId()){
        case R.id.bt_js:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                mywebview.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
                    @Override
                    public void onReceiveValue(String value) {

                    }
                });
            }else {
                mywebview.loadUrl("javascript:callJS()");
            }
            break;
    }
}

优点:不会刷新当前页面
弊端:Andorid4.4之后才能使用(不过现在4.4之前的手机几乎已被淘汰)

  • JS调用Android代码

方法一:采用addJavascriptInterface映射

步骤一:定义接口

public class AndoridToJsInterface {

    @JavascriptInterface
    public void hello(){
        System.out.println("hello");
    }

}

AndoridToJsInterface 类专门负责声明接口,接口方法必须添加“@JavascriptInterface”修饰,该注解将接口暴露给JS。其作用是为JS代码提供调用的接口。JS代码想要调用Android代码声明的接口,就必须给JS创建一个映射

    //AndroidtoJS类对象映射到js的test对象
    mywebview.addJavascriptInterface(new AndoridToJsInterface(), "android");

第一个参数是AndoridToJsInterface 对象,第二个参数就是AndoridToJsInterface对象的别名,也就是给JS使用的映射。

现在开始编写JS代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Carson</title>
    <script>
         function callAndroid(){
            android.hello();
         }
      </script>
</head>
<body>
<button type="button" id="button1" onclick="callAndroid()">点击调用Android的hello方法</button>
</body>
</html>

优点:使用简单
缺点:注解使Android的接口暴露出来,是一个高危漏洞

方法二:采用shouldOverrideUrlLoading拦截URl

编写好JS代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Carson_Ho</title>
    <script>
         function callAndroid(){
            //事先预定的URI
            document.location = "android://webview?name=zhangsan&age=22";
         }
      </script>
</head>
<body>
<button type="button" id="button1" onclick="callAndroid()">点击调用Android代码</button>
</body>
</html>

其中"android://webview?name=zhangsan&age=22"是事先预定好的格式,然后在点击网页上的按钮触发JS代码,并在Android端捕获

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Uri uri = Uri.parse(url);
            if(uri.getScheme().equals("android") && uri.getAuthority().equals("webview")){
                Set<String> paraNames = uri.getQueryParameterNames();
                Iterator it = paraNames.iterator();
                while (it.hasNext()){
                    String name = (String) it.next();
                    Log.d("aaa", "name:"+name);
                }
            }
            return super.shouldOverrideUrlLoading(view,url);
        }

优点:没有方法一的安全漏洞
缺点:使用复杂,并且将所有的JS处理都放在shouldOverrideUrlLoading中略显臃肿

方法三:采用onJsAlert、onJsConfirm、onJsPrompt捕获URL

和方法二原理类似,这里就不在描述

        @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            return super.onJsAlert(view, url, message, result);
        }

        @Override
        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
            return super.onJsConfirm(view, url, message, result);
        }

        @Override
        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
            return super.onJsPrompt(view, url, message, defaultValue, result);
        }

优点:没有方法一的安全漏洞
缺点:使用复杂,并且将所有的JS处理都放在onJsAlert、onJsConfirm、onJsPrompt中略显臃肿。

JS交互总结:

  • Android调用JS代码推荐使用evaluateJavascript,他的好处显而易见,既不会重新刷新页面,也可以获取返回值
  • 三种JS调用Android代码的方法都不推荐使用,addJavascriptInterface映射的方式是一个高危漏洞,其他两种使用太过复杂,这里推荐使用JsBridge实现JS交互。

最后分享一个github上开源的JsBridge案例:

https://github.com/lzyzsd/JsBridge

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

推荐阅读更多精彩内容