Android之使用WebView的那些事儿

说正文之前,先来个故事提提神,如有雷同,纯属巧合:

开始,公司主项目只有一个m站
领导:我们要开发原生的APP,一切向m站看齐,时间紧,任务中,努力吧,骚年!
码农:领导,放心,保证完成任务。霹雳啪哒。。。。开始了

2周后…

码农:领导完成了。但是,很多没有接口,都是本地数据
领导看了看,说:还行,可以交差了;现在,另外一个项目需要重做,上阵吧
又一个2周后…

码农:领导那个完成了。
领导:好的,情况有变,那个你们就放市场上不用管了,回来接着弄这个主项目吧。然后,玩了玩主项目的APP说,不对啊,怎么数据都跟m站的不一样啊。
此时,m站已经更新了好几版了。。。
码农:看了看项目,说有些数据是本地的,有些数据是因为接口没有了。。。。
领导:那你们赶紧按照m站改吧。
码农:好的。

几天后…

领导:你们改完没,我能不能每天看到你们做的新东西啊
码农:m站都没有改完,我们更没有了。。想每天都能看到我们做的新东西的话,比较困难。。
几天后…

领导:你们看m站更新挺快的,有没有办法把m站嵌入APP啊
码农:可以啊。然后,本篇文章诞生了。。。。




正式开始

简介: Webview是Android系统控件。在Android4.4以下WebView采用WebKit内核,在Android4.4以上采用chromium内核。Webkit 默认采用JavaScriptCore引擎,效率十分的低效;在Android4.4以上换成了V8引擎,直接提升了JavaScript性能。详细介绍点击这里

技术点:

1,原生跟Html交互
2,调用原生相机相册
3,页面重定向
4,缓存问题
5,加载失败处理
6,优化问题

一、原生跟Html交互

一般项目,涉及到会经常变动的页面,一般都会采用html的方式来做。用到html,一般就会涉及到原生跟html的交互了。


你懂得

  • Html调用原生

用Html 调用原生一般有两种做法:1,WebView.addJavascriptInterface。2,拦截url(一般双方会定一个协议,来拦截)

    • 第一种
      JS
function getCreditCard(num,appSessionId){
        var appSessionArry = [num,appSessionId];
        try{
            //Android
            window.androidCreditManage.demo(appSessionArry);
        }catch(err){
            //IOS
            window.webkit.messageHandlers.creditManage.postMessage(appSessionArry);
        };
        return num;
    };

java代码

//允许JS交互
settings.setJavaScriptEnabled(true);
//JS交互
mWebView.addJavascriptInterface(new JSInterface(), "androidCreditManage");
···
 final class JSInterface {
        //4.2系统以上只有添加这个注解才可以
        @JavascriptInterface
        public void demo(final String[] num) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (num == null || num.length <= 0) {
                        return;
                    }
                    if (num.length >= 2) {
                        SharePreferHelper.setTest(num[1]);
                    }
                    getCorpId(num[0]);

                }
            });
        }
    }
    • 第二种,url拦截
      通过复写 WebViewClient 的shouldOverrideUrlLoading(WebView view, String url)方法
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (interceptUrl(url)) {
            return true;
        }
        return super.shouldOverrideUrlLoading(view, url);
    }
    
    ···
    //判断是否符合协议,进行拦截
        public boolean interceptUrl(String url) {
        //栗子:url=js:"js://token?arg=1111"
        //Uri uri = Uri.parse(url);
        //uri.getScheme();// =js
        //uri.getAuthority(); //= token
        //Set<String> params =  uri.getQueryParameterNames();// 参数
        return false;
    }
  • 原生调用HTML

    1, JS代码

    function showCreditIcon(){   
    //something
    };

    2,java代码

    通常情况下我们是用下面来调用

     mWebView.loadUrl("javascript:showCreditIcon()");

    上面的调用效率并不高,在Android4.4及以上提供了下面的API。所以,我们可以在Android4.4以上采用下面的方式,在Android4.4一下采用上面的方式。

 mWebView.evaluateJavascript("javascript:showCreditIcon()", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            //TODO: 处理返回的结果
        }
    });

二、调用原生相机相册

因为项目用的是嵌套m站的方式,m站在浏览器是调用浏览器的访问文件方式。但是,在嵌套到原生APP里面后。。。不行啦!


不要搞事情

然后,我们需要复写WebChromeClientopenFileChooseronShowFileChooser(5.0及以上系统)方法来用原生实现调取拍照,相册功能的过程。
代码如下:

    //1,告诉客户端显示一个文件选择器
    //2,这是处理HTML form表单的 type是'file'的button.
    //3,取消请求调用filePathCallback.onReceiveValue(null)
    //4,如果实现了这个方法返回true,必须调用一个 返回结果或者取消请求
    /**
     * Tell the client to show a file chooser.
     *
     * This is called to handle HTML forms with 'file' input type, in response to the
     * user pressing the "Select File" button.
     * To cancel the request, call <code>filePathCallback.onReceiveValue(null)</code> and
     * return true.
     *
     * @param webView The WebView instance that is initiating the request.
     * @param filePathCallback Invoke this callback to supply the list of paths to files to upload,
     *                         or NULL to cancel. Must only be called if the
     *                         <code>showFileChooser</code> implementations returns true.
     * @param fileChooserParams Describes the mode of file chooser to be opened, and options to be
     *                          used with it.
     * @return true if filePathCallback will be invoked, false to use default handling.
     *
     * @see FileChooserParams
     */
    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
        super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
        //打开选择相册相机dialog
        mFilePathCallback = filePathCallback;
        //如果filePathCallback被调用返回true,默认处理返回false
        return true;
    }

    public void openFileChooser(ValueCallback<Uri> filePathCallback) {
        //打开选择相册相机dialog
        mFilePathCallbackOld = filePathCallback;
    }

    public void openFileChooser(ValueCallback filePathCallback, String acceptType) {
        //打开选择相册相机dialog
        mFilePathCallbackOld = filePathCallback;
    }

    public void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
        //打开选择相册相机dialog
        mFilePathCallbackOld = filePathCallback;
    }

处理调取后的结果

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        /**
         * 处理页面返回或取消选择结果
         */
        switch (requestCode) {
            case REQUEST_CODE_PICK_PHOTO:
                pickPhotoResult(resultCode, data);
                break;
            case REQUEST_CODE_TAKE_PHOTO:
                takePhotoResult(resultCode);
                break;
            default:

                break;
        }
    }

    ···

    private void pickPhotoResult(int resultCode, Intent data) {
        //m站调用文件管理器
        if (mFilePathCallback != null) {
            Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
            if (result != null) {
                //获取路径
                String path =getPath(this, result);
                Uri uri = Uri.fromFile(new File(path));
                //返回给mFilePathCallback路径的uri
                mFilePathCallback.onReceiveValue(new Uri[]{uri});

            } else {
                /**
                 * 实现了方法,必须返回结果或返回null.不然,无法重复回调
                 */
                mFilePathCallback.onReceiveValue(null);
                mFilePathCallback = null;
            }
        }
    }

三、页面重定向

在我们使用WebView的过程中,尤其,加载第三方Url的时候,不可避免的会遇到重定向的问题


真是个悲伤的故事

一般的处理方式有两个:

1,自己维护一个webview的历史栈,自己实现返回上一个页面
2,重写WebViewClient shouldOverrideUrlLoading()方法

在项目中,我用的是第一种方式,具体参考:点我点我

第二种方式其实非常简单,我使用的测试机器(有限)也没有问题,如下

   @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        //就是直接返回false
        return false;
    }

最后,查资料还发现了一种解决方式,也是复写shouldOverrideUrlLoading()方法;
但是,在4.2的模拟器上,发下无法解决问题,代码如下

               @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                WebView.HitTestResult hitTestResult = view.getHitTestResult();
                if (!TextUtils.isEmpty(url) && hitTestResult == null) {
                    view.loadUrl(url);
                    return true;
                }
                return super.shouldOverrideUrlLoading(view, url);
            }

四、缓存问题

webview的缓存模式有五种:

1,LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
2,LOAD_DEFAULT: 根据cache-control决定是否从网络上取数据。
3,LOAD_CACHE_NORMAL: API level 17中已经废弃, 从API level 11开始作用同LOAD_DEFAULT模式
4,LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
5,LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。


纳尼

使用时候:一般推荐有网的时候使用DFAULT,没网使用LOAD_CACHE_ELSE_NETWORK

  mWebView.getSettings().setJavaScriptEnabled(true);  
        mWebView.getSettings().setRenderPriority(RenderPriority.HIGH);  
        mWebView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);  //设置 缓存模式  
        // 开启 DOM storage API 功能  
        mWebView.getSettings().setDomStorageEnabled(true);  
        //开启 database storage API 功能  
        mWebView.getSettings().setDatabaseEnabled(true);   
        String cacheDirPath = getFilesDir().getAbsolutePath()+APP_CACAHE_DIRNAME;  
//      String cacheDirPath = getCacheDir().getAbsolutePath()+Constant.APP_DB_DIRNAME;  
        Log.i(TAG, "cacheDirPath="+cacheDirPath);  
        //设置数据库缓存路径  
        mWebView.getSettings().setDatabasePath(cacheDirPath);  
        //设置  Application Caches 缓存目录  
        mWebView.getSettings().setAppCachePath(cacheDirPath);  
        //开启 Application Caches 功能  
        mWebView.getSettings().setAppCacheEnabled(true);  


    ···

      /** 
         * 清除WebView缓存 
         */  
        public void clearWebViewCache(){  

            //清理Webview缓存数据库  
            try {  
                deleteDatabase("webview.db");   
                deleteDatabase("webviewCache.db");  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  

            //WebView 缓存文件  
            File appCacheDir = new File(getFilesDir().getAbsolutePath()+APP_CACAHE_DIRNAME);  
            Log.e(TAG, "appCacheDir path="+appCacheDir.getAbsolutePath());  

            File webviewCacheDir = new File(getCacheDir().getAbsolutePath()+"/webviewCache");  
            Log.e(TAG, "webviewCacheDir path="+webviewCacheDir.getAbsolutePath());  

            //删除webview 缓存目录  
            if(webviewCacheDir.exists()){  
                deleteFile(webviewCacheDir);  
            }  
            //删除webview 缓存 缓存目录  
            if(appCacheDir.exists()){  
                deleteFile(appCacheDir);  
            }  
        }  

详细介绍点击这里–>


五、加载失败处理

加载失败也是加载完成了,所以 ,WebChromeClient的onProgressChanged(WebView view, int newProgress)方法中newProgress也会走到100,用这个方法并不能解决问题。


这

加载失败处理,一般是复写 WebViewClient的onReceivedError(方法)。 需要注意的是: 这个方法在api 23里面 已经 deprecation。如果需要继续使用 则在该方法上面加入 @SuppressWarnings(“deprecation”),并且这个方法并不能捕获404。

  @SuppressWarnings("deprecation")
    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        super.onReceivedError(view, errorCode, description, failingUrl);
        showErrorView();
    }

在api 23以上通过onReceivedHttpError()可以捕获

 @Override
    public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
        super.onReceivedHttpError(view, request, errorResponse);
    }

六、优化问题

  • 1,WebView的释放问题
    在页面结束时,资源要自己释放,不然,退出了内存也会很高。
    应用内存使用命令:
    adb shell dumpsys meminfo -a <’process id>/<’process name>
    详细介绍可以看这里:点我点我
    /**
     * 释放WebView
     */
    private void recycleWebView(){
        if(mWebView != null){
            mRootView.removeView(mWebView);
            mWebView.removeAllViews();
            mWebView.destroy();
        }
    }

    @Override
    protected void onDestroy() {
        recycleWebView();
        super.onDestroy();
    }
  • 其他的暂时还没有尝试。如,常用资源预加载,常用 JS 本地化及延迟加载等。
    优化可以参考这里

一些注意事项

1,在android 5.0以上WebView要同时加载http和https,需要如下设置

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {  
     webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);  
}  

2,当然,你也可以选择鹅厂的TBS。相关文章介绍

最后

现在,项目只有一个模块是用原生做,其他的都是嵌套m站,嵌套m站部分基本快完成了,这里总结了项目大部分用到的东西。有不对的地方,欢迎指出;有好的建议,也欢迎留言。

猜你喜欢

转载自blog.csdn.net/ecliujianbo/article/details/73610144