Android WebView 分析整理

原始简单用法

在布局文件中加入 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 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·开车指南

猜你喜欢

转载自blog.csdn.net/xiao6gui/article/details/80813265