美文网首页
Android基础: WebView常用类、JS交互、内存泄漏

Android基础: WebView常用类、JS交互、内存泄漏

作者: 红发_SHANKS | 来源:发表于2018-07-11 14:04 被阅读123次

    原始简单用法##

    在布局文件中加入 WebView

    <?xml version="1.0" encoding="utf-8"?>
    <WebView  xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    />
    

    使用LoadUrl()方法加载 WebView

    WebView myWebView = (WebView) findViewById(R.id.webview);
    // 如果在loadUrl()方法中,网页产生异常,并不会抛出到我们的 app 中
    myWebView.loadUrl("http://www.example.com");
    

    最后不要忘记添加权限

    <manifest ... >
        <uses-permission android:name="android.permission.INTERNET" />
        ...
    </manifest>
    

    打开系统浏览器

     * Uri uri = Uri.parse("https://www.example.com");
     * Intent intent = new Intent(Intent.ACTION_VIEW, uri);
     * startActivity(intent);
    

    WebView 常用方法

    • public void onPause ()尽可能的暂停能够被暂停的进程,比如动画、定位,但是不能暂停 JavaScript 的调用。
    • public void onResume ()从暂停状态恢复。
    • public void pauseTimers ()停止应用内所有(不仅仅是当前 WebView)的 webView 的 界面绘制、元素解析、JavaScript执行等工作,当应用暂停的时候,调用这个方法能够有效降低 CPU 功耗。
    • public void resumeTimers ()恢复所有的 WebView 的 layout,parsing,JavaScript timers 等。
    • public void destroy ()销毁 WebView 的内部状态,该方法调用后,WebView 不会再执行任何操作。调用该方法之前,需要从视图系统中(比如Activity) remove(removeView) 掉该 WebView ,因为在构建的时候,WebView 持有了视图系统的上下文引用。
    • public boolean canGoBack ()WebView是否有可回退的历史记录。
    • public boolean canGoForward ()WebView 是否有可前进的历史记录。
    • public void goBack () 回退到这个 WebView 的上一个历史页面。
    • public void goForward () 前进到历史纪录中的一个页面。对比网页浏览器的前进按钮。
    • public void goBackOrForward (int steps)后退或者前进,steps 是正数就前进,负数就后退。
    • public void clearCache (boolean includeDiskFiles)清除应用内所有 WebView 的缓存
    • public void clearHistory ()通知 WebView 清除历史记录
    • pageUp(boolean top):将WebView展示的页面滑动至顶部。
    • pageDown(boolean bottom):将WebView展示的页面滑动至底部。
    // 常见的让返回键在网页中回退,而不是直接退出网页
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) { 
            mWebView.goBack();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
    

    三个常用类##

    WebSettings###

    用于对WebView进行配置和管理

    //声明WebSettings子类,如果 WebView 已经 destroy,再调用 WebSettings 的方法会抛出 IllegalStateException
    WebSettings webSettings = webView.getSettings();
    
    //如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
    webSettings.setJavaScriptEnabled(true);  
    
    //支持插件
    webSettings.setPluginsEnabled(true); 
    
    //设置自适应屏幕,两者合用
    webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小 
    webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小
    
    //缩放操作
    webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。
    webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放
    webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件
    
    //其他细节操作
    webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //网络可用就网络加载,否则使用缓存,通过改变常量值可以调整为只网络加载或者只缓存加载 
    webSettings.setAllowFileAccess(true); //设置可以访问文件 
    webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口 
    webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
    webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
    
    // 设置网页字体大小
    setTextZoom (int textZoom) //textZoom 默认大小是 100
    // 设置是否支持导航
    setGeolocationEnabled(boolean flag)
    
    // 设置缓存模式和缓存地址的常规方法
     File cacheFile = this.getApplicationContext().getCacheDir();
     if (cacheFile != null) {
         // 这个方法应该只被调用一次,重复调用会被无视
         mWebView.getSettings().setAppCachePath(cacheFile.getAbsolutePath());
     }
     /**
      * 设置缓存加载模式
      * LOAD_DEFAULT(默认值):如果缓存可用且没有过期就使用,否则从网络加载
      * LOAD_NO_CACHE:从网络加载
      * LOAD_CACHE_ELSE_NETWORK:缓存可用就加载即使已过期,否则从网络加载
      * LOAD_CACHE_ONLY:不使用网络,只加载缓存即使缓存不可用也不去网络加载
      */
     int type = AppUtil.getNetWorkType(this);
     switch (type) {
         case AppUtil.NETWORKTYPE_4G:
         case AppUtil.NETWORKTYPE_WIFI:
             mWebView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
             break;
         default:
             mWebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
             break;
     }
    
    

    WebViewClient类###

    处理各种通知和请求事件,当发生的事情影响到内容的渲染 (例如, 错误或表单提交) 时, 就会调用它,还可以在此处拦截 URL 加载。

    • WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request): 当WebView需要请求某个数据时,这个方法可以拦截该请求来告知app并且允许app本身返回一个数据来替代我们原本要加载的数据。

    • boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request): 当我们没有给WebView提供WebViewClient时,WebView如果要加载一个url会向ActivityManager寻求一个适合的处理者来加载该url(比如系统自带的浏览器),这通常是我们不想看到的。于是我们需要给WebView提供一个WebViewClient,并重写该方法返回true来告知WebView url的加载就在app中进行,返回false,表示当前 app 对 url 进行处理。这时便可以实现在app内访问网页。

    • onReceivedSslError(WebView view, SslErrorHandler handler, SslError error): 当WebView加载某个资源引发SSL错误时会回调该方法,这时WebView要么执行handler.cancel()取消加载,要么执行handler.proceed()方法继续加载(默认为cancel)。需要注意的是,这个决定可能会被保留并在将来再次遇到SSL错误时执行同样的操作。

    • onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse):上一个方法提到onReceivedError并不会在服务器返回错误码时被回调,那么当我们需要捕捉HTTP ERROR并进行相应操作时应该怎么办呢?API23便引入了该方法。当服务器返回一个HTTP ERROR并且它的status code>=400时,该方法便会回调。这个方法的作用域并不局限于Main Frame,任何资源的加载引发HTTP ERROR都会引起该方法的回调,所以我们也应该在该方法里执行尽量少的操作,只进行非常必要的错误处理等

    • onPageFinished(WebView view, String url):该方法只在WebView完成一个页面加载时调用一次(同样也只在Main frame loading时调用),我们可以可以在此时关闭加载动画,进行其他操作。特别注意:JS代码调用一定要在 onPageFinished() 回调之后才能调用,否则不会调用。

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        // 如果链接不是常规的 url , 尝试使用系统浏览器打开
        if (URLUtil.isNetworkUrl(url)) {
            view.loadUrl(url);
        } else {
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
            PackageManager pm = getPackageManager();
            List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
            if (activities.size() > 0) {
                startActivity(intent);
            }
        }
    
        return true;
    }
    
    // webView默认是不处理https请求的,页面显示空白,需要进行如下设置:
    webView.setWebViewClient(new WebViewClient() {    
            @Override    
            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {    
                handler.proceed();    //表示等待证书响应,继续进行请求
            // handler.cancel();      //表示挂起连接,为默认方式
            // handler.handleMessage(null);    //可做其他处理
            }    
        }); 
    

    WebChromeClient类###

    如果说WebViewClient是帮助WebView处理各种通知、请求事件的“内政大臣”的话,那么WebChromeClient就是辅助WebView处理Javascript的对话框,网站图标,网站title,加载进度等偏外部事件的“外交大臣”。

    • onProgressChanged(WebView view, int newProgress):当页面加载的进度发生改变时回调,用来告知主程序当前页面的加载进度。

    • onReceivedTitle(WebView view, String title):用来接收web页面的title,我们可以在这里将页面的title设置到Toolbar。

    • boolean onJsAlert(WebView view, String url, String message, JsResult result):处理Javascript中的Alert对话框

    • onShowCustomView(View view, WebChromeClient.CustomViewCallback callback):该方法在当前页面进入全屏模式时回调,主程序必须提供一个包含当前web内容(视频 or Something)的自定义的View。

    • onHideCustomView():该方法在当前页面退出全屏模式时回调,主程序应在这时隐藏之前show出来的View。

    // gei WebView 添加一个进度条
    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        if (newProgress < 100) {
            progress.setVisibility(View.VISIBLE);
            progress.setProgress(newProgress);
        } else {
            progress.setProgress(newProgress);
            progress.setVisibility(View.GONE);
        }
        super.onProgressChanged(view, newProgress);
    }
    
    
    // 将网页标题设置到 WebViewActivity
    webview.setWebChromeClient(new WebChromeClient(){
    
        @Override
        public void onReceivedTitle(WebView view, String title) {
           titleview.setText(title);
        }
    
    // 处理网页中有需要提交文件的情况
    //For Android  >= 4.1
      public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
          uploadMessage = valueCallback;
          openImageChooserActivity();
      }
    
      // For Android >= 5.0
      @Override
      public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
          uploadMessageAboveL = filePathCallback;
          openImageChooserActivity();
          return true;
      }
    
    ...
    // 在Activity 中选择文件和回调
        private void openImageChooserActivity() {
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILE_CHOOSER_RESULT_CODE);
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == FILE_CHOOSER_RESULT_CODE) {
                if (null == uploadMessage && null == uploadMessageAboveL) return;
                Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
                if (uploadMessageAboveL != null) {
                    onActivityResultAboveL(requestCode, resultCode, data);
                } else if (uploadMessage != null) {
                    uploadMessage.onReceiveValue(result);
                    uploadMessage = null;
                }
            }
        }
    
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {
            if (requestCode != FILE_CHOOSER_RESULT_CODE || uploadMessageAboveL == null)
                return;
            Uri[] results = null;
            if (resultCode == Activity.RESULT_OK) {
                if (intent != null) {
                    String dataString = intent.getDataString();
                    ClipData clipData = intent.getClipData();
                    if (clipData != null) {
                        results = new Uri[clipData.getItemCount()];
                        for (int i = 0; i < clipData.getItemCount(); i++) {
                            ClipData.Item item = clipData.getItemAt(i);
                            results[i] = item.getUri();
                        }
                    }
                    if (dataString != null)
                        results = new Uri[]{Uri.parse(dataString)};
                }
            }
            uploadMessageAboveL.onReceiveValue(results);
            uploadMessageAboveL = null;
        }
    
    

    Js与WebView交互##

    对于Android调用JS代码的方法有2种:

    1. 通过WebView的webView.loadUrl("javascript:methodName(parameterValues)"); // 适用于无返回值的 JS方法
    2. 通过WebView的evaluateJavascript(String script, ValueCallback<String> resultCallback)// 适用于有返回值的 JS 方法
    // 设置与Js交互的权限
    webSettings.setJavaScriptEnabled(true);
    
    try {
        InputStream is = FileUtil.getStream(WebViewActivity.this, "raw://inithtml");
        String js = FileUtil.readStreamString(is, "UTF-8");
        mWebView.loadUrl("javascript:" + js);
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    
    // 这种方法执行不会使页面刷新,但是 loadUrl执行 js 代码则会刷新页面
    mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            //此处为 js 返回的结果
        }
    });
    }
    

    对于JS调用Android代码的方法有3种:

    1. 通过WebView的addJavascriptInterface()进行对象映射
    2. 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url
    3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息
    // 1、在本地定义供 JS 调用的原生方法,被JS调用的方法必须加入@JavascriptInterface注解
    @JavascriptInterface
    public void show(String s){
        Toast.makeText(getApplication(), s, Toast.LENGTH_SHORT).show();
    }
    
    // 2、通过WebView的addJavascriptInterface()进行对象映射,将JSObject类映射成为 JS 中的 JSObjectInJavascript 对象
    mWebView.addJavascriptInterface(new JSObject(this), "JSObjectInJavascript");
    
    // 3、编写 JS 方法,调用第一步中编写的原生方法
    function toastClick(){
        window.android.show("JavaScript called~!");
    }
    

    WebView 内存泄露###

    // 在 onPause()中暂停 webview
    mWebView.pauseTimers
    
    //在 onDestroy 中释放资源
    if (mWebView != null) {
      ViewParent parent = mWebView.getParent();
      if (parent != null) {
         ((ViewGroup) parent).removeView(mWebView);
      }
      mWebView.removeAllViews();
      mWebView.destroy();
      mWebView = null;
    }
    

    参考
    Android:你要的WebView与 JS 交互方式 都在这里了
    Android:最全面的 Webview 详解
    WebView·开车指南

    相关文章

      网友评论

          本文标题:Android基础: WebView常用类、JS交互、内存泄漏

          本文链接:https://www.haomeiwen.com/subject/kdjhpftx.html