摘要:
WebView有几个定制功能方便添加开发者期待的属性。
1、创建并设置一个WebChromeClient子类,该子类在想要改变浏览器的UI界面时回调对应的方法,比如,进度条的更新和JS弹窗
2、创建并设置一个WebViewClient子类,该子类在改变网页内容的呈现方式时回调对应的方法,比如,网页加载错误或提交表单,同时可以通过回调shouldOverrideUrlLoading()方法拦截URL加载。
3、修改WebSettings,配置WebView各种属性,比如:开启JavaScript脚本功能,setJavaScripeEnabled(true)
4、调用addJavascriptInterface(Object,String)方法将Java对象注入WebView加载的页面,H5开发者通过JavaScript脚本访问Java对象提供的方法或属性。
一、详细分析WebChromeClient个回调方法
WebChromeClient给TeachCourse的第一印象是要么是抽象类,要么是接口,查看源码后发现其实是一个普通的Java类文件,WebChromeClient声明了一个内部接口WebChromeClient.CustomViewCallback、一个内部类WebChromeClient.FileChooserParams和多个空方法体的方法,比如:
- /**
- * Tell the host application the current progress of loading a page.
- * @param view The WebView that initiated the callback.
- * @param newProgress Current page loading progress, represented by
- * an integer between 0 and 100.
- */
- public void onProgressChanged(WebView view, int newProgress) {}
- /**
- * Notify the host application of a change in the document title.
- * @param view The WebView that initiated the callback.
- * @param title A String containing the new title of the document.
- */
- public void onReceivedTitle(WebView view, String title) {}
- /**
- * Notify the host application of a new favicon for the current page.
- * @param view The WebView that initiated the callback.
- * @param icon A Bitmap containing the favicon for the current page.
- */
- public void onReceivedIcon(WebView view, Bitmap icon) {}
可能你会像TeachCourse一样感到奇怪,为什么声明的是空的方法体呢?如果是接口,还可以理解,能够实现监听接口;或许是抽象类也可以理解,可以实现方法回调,但WebChromeClient偏偏是一个普通的Java类,却可以实现接口的监听和方法的回调功能。
WebView通过setWebChromeClient传入WebChromeClient对象,读完《深入理解接口的定义和意义》这篇文章的小伙伴们会发现类似于设置接口监听,然后重写WebChromeClient或其子类方法,实现类似抽象类的方法回调,比如,上面提到的进度条的更新或提醒应用程序当前网页title发生改变,在WebView切换另一个页面的时候,回调onReceivedTitle方法获取改变的title信息。
其它回调方法:
- public void onReceivedTouchIconUrl(WebView view, String url,boolean precomposed) {}
通知加载当前网页的主机应用程序,获取手机触屏的图标
- public interface CustomViewCallback {
- /**
- * Invoked when the host application dismisses the
- * custom view.
- */
- public void onCustomViewHidden();
- }
这是内部一个接口,由主机应用程序通知当前网页是否隐藏
- public void onShowCustomView(View view, CustomViewCallback callback) {};
6.通知主机应用程序当前页面进入全屏模式,在全屏模式下的主机应用程序必须包含网页内容:video或其他特定的HTML标签,与是否视频全屏播放有关的回调方法
- @Deprecated
- public void onShowCustomView(View view, int requestedOrientation,CustomViewCallback callback) {};
7.通知主机应用程序当前页面想要在某个位置显示一个自定义视图
- public void onHideCustomView() {}
8.通知主机应用程序当前页面退出全屏模式,主机必须隐藏自定义视图;效果和onShowCustomView相反
- public boolean onCreateWindow(WebView view, boolean isDialog,
- boolean isUserGesture, Message resultMsg) {
- return false;
- }
9.请求主机程序创建一个新的窗口,主机选择这样的请求,该方法必须返回true,将窗口放到视图系统内同时以参数的形式发送提供的消息结果给目标。反之,主机没有选择这样的请求,该方法返回false,默认,该方法的实现返回false。
- public void onRequestFocus(WebView view) {}
10.当前WebView请求显示并获取焦点,这样的情况发生在另一个WebView正在打开当前WebView的一条连接同时正在请求显示。
- public void onCloseWindow(WebView window) {}
11.通知主机程序关闭WebView同时需要的时候从系统视图中移除,此时,内核停止加载
- public boolean onJsAlert(WebView view, String url, String message,
- JsResult result) {
- return false;
- }
12.告诉客户端显示一个JS弹窗,如果客户端返回true,WebView将会接受客户端处理弹窗;反之,返回false,程序继续执行
- public boolean onJsConfirm(WebView view, String url, String message,
- JsResult result) {
- return false;
- }
13.告诉客户端向用户显示确认对话框,如果客户端返回true,同样,WebView将会接受客户端处理对话框和回调JsResult方法;反之,返回false,程序继续执行,默认返回false
- public boolean onJsPrompt(WebView view, String url, String message,
- String defaultValue, JsPromptResult result) {
- return false;
- }
14.告诉客户端向用户显示警告弹窗,如果客户端返回true,同样,WebView将会接受客户端处理警告弹窗和回调JsPromptResult方法;反之,返回false,程序继续执行,默认返回false
- public boolean onJsBeforeUnload(WebView view, String url, String message,
- JsResult result) {
- return false;
- }
15.告诉客户端展示一个弹窗确认从当前页面离开,这是未加载之前JavaScript事件的结果。如果客户端返回true,WebView将会接受客户端处理确认弹窗和回调JsResult方法,默认true,返回给javascript接受离开当前页面;否则,返回false,默认的行为将会返回false,设置JsResult返回true,将离开当前页面,该方法false,取消离开。
- @Deprecated
- public void onExceededDatabaseQuota(String url, String databaseIdentifier,
- long quota, long estimatedDatabaseSize, long totalQuota,
- WebStorage.QuotaUpdater quotaUpdater) {
- // This default implementation passes the current quota back to WebCore.
- // WebCore will interpret this that new quota was declined.
- quotaUpdater.updateQuota(quota);
- }
16.告诉客户端数据容量已超过Web SQL数据库,对于一个特定的数据源,并要求一个新的容量。客户端通过调用WebStorage.QuotaUpdater提供实例的方法,新容量可以被设置的最小值是当前页面容量,默认以当前页面容量实现响应,以至于容量不会增长。
- @Deprecated
- public void onReachedMaxAppCacheSize(long requiredStorage, long quota,
- WebStorage.QuotaUpdater quotaUpdater) {
- quotaUpdater.updateQuota(quota);
- }
17.提醒主机程序当前应用缓冲以达到最大值,客户端必须通过调用WebStorage.QuotaUpdater提供的实例方法响应,新容量可以被设置的最小值是当前页面容量,默认以当前页面容量实现响应,以至于容量不会增长。
- public void onGeolocationPermissionsShowPrompt(String origin,GeolocationPermissions.Callback callback) {}
18.提醒主机程序来自特殊数据源的网页内容想要使用定位API,当前设置没有允许状态。主机程序应该以期待的权限调用指定的回调,详细信息可以参考GeolocationPermissions
- public void onGeolocationPermissionsHidePrompt() {}
19.提醒主机程序请求地理位置权限,先前的调用onGeolocationPermissionsShowPrompt和onGeolocationPermissionsShowPrompt被取消,所有相关的UI因此被隐藏。
- public void onPermissionRequest(PermissionRequest request) {
- request.deny();
- }
20.提醒主机程序当前网页内容正在请求访问特殊的资源,当前权限是否获取或禁止,主机程序必须调用PermissionRequest的grant()方法或PermissionRequest的deny()方法。如果该方法没有被重写,默认禁止。
- public void onPermissionRequestCanceled(PermissionRequest request) {}
21.提醒主机程序给出的权限请求已经被取消,所有相关的UI因此被隐藏。
- public boolean onJsTimeout() {
- return true;
- }
22.告诉客户端javascript执行超时,客户端选择是否阻止执行。如果客户端返回true,javascript被阻止;否则,返回false,脚本继续执行。在继续执行的例子中,超时次数将会被重置,同时如果下次检查脚本没有结束,继续回调该方法。
- @Deprecated
- blic void onConsoleMessage(String message, int lineNumber, String sourceID) { }
23.向主机程序报告javascript错误消息,ChromeClient应该重写该方法检查日志消息正如他们期待的一样。
- public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
- // Call the old version of this function for backwards compatability.
- onConsoleMessage(consoleMessage.message(), consoleMessage.lineNumber(),
- consoleMessage.sourceId());
- return false;
- }
24.想主机程序报告javascript控制台消息,ChromeClient应该重写该方法检查日志消息正如他们期待的一样。返回true,消息由客户端处理。
- public Bitmap getDefaultVideoPoster() {
- return null;
- }
25.当没有正在播放视频时,video标签显示“提示”图片,使用的这个“提示”图片可以被video标签的属性指定。如果缺少该属性,使用默认“提示”图片,这个方法运行ChromeClient提供默认的图片。
- public View getVideoLoadingProgressView() {
- return null;
- }
26.获取一个显示全屏视频正在缓冲的一个View,主机程序重写该方法获取一个旋转的进度提示或相似的
- public void getVisitedHistory(ValueCallback<String[]> callback) {}
27.获取所有被访问选项列表
- public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
- FileChooserParams fileChooserParams) {
- return false;
- }
28.告诉客户端显示一个文件选择器。该方法被回调处理表单中的“file”输入类型,调用filePathCallback.onReceiveValue(null)方法取消请求打开文件选择器并返回true。
- @SystemApi
- @Deprecated
- public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) {
- uploadFile.onReceiveValue(null);
- }
29.告诉客户端打开一个文件选择器。
- public void setupAutoFill(Message msg) { }
30.告诉客户端被浏览器的页面有一个自动补全表单功能,当用户想要设置一个配置文件。
- public static abstract class FileChooserParams {
- public static final int MODE_OPEN = 0;
- public static final int MODE_OPEN_MULTIPLE = 1;
- public static final int MODE_OPEN_FOLDER = 2;
- public static final int MODE_SAVE = 3;
- public static Uri[] parseResult(int resultCode, Intent data) {
- return WebViewFactory.getProvider().getStatics().parseFileChooserResult(resultCode, data);
- }
- public abstract int getMode();
- public abstract String[] getAcceptTypes();
- public abstract boolean isCaptureEnabled();
- public abstract CharSequence getTitle();
- public abstract String getFilenameHint();
- public abstract Intent createIntent();
- }
二、WebChromeClient常见使用实例
分析完WebChromeClient源码的所有方法后,TeachCourse可以肯定其所有的方法在系统的内部被调用,然后通过setWebChromeClient()方法获取传递回来的值,那么我们就可以根据开发需要重写对应的方法,这是WebChromeClient为什么不声明为抽象类或接口的原因吧。
1、onProgressChanged(WebView view, int newProgress)实现各式各样的加载进度
newsProgress是0到100的整数,即每次更新的进度,那么显示进度条视图可以调用ProgressBar或ProgressDialog的setProgress(int)方法,当newProgress达到100时,进度条消失。具体例子参考《ProgressBar+WebView实现自定义浏览器》
2、onReceivedTitle(WebView view, String title)显示当前网页的HTML的title标签内容。标准的HTML页面都可能包含一个title标签,类似一篇文章都必须包含标题一样,TeachCourse用WebView开发新闻客户端,喜欢回调用该方法获取title标签内容,然后在浏览器导航栏显示。
PS:查看源码,发现WebView提供的getTitle()方法直接调用返回空值,获取不到当前网页的title标签内容。原因是,只有当WebChromeClient.onReceivedTitle方法被回调的时候,才可以使用上述方法。
3、onReceivedIcon(WebView view, Bitmap icon)获取当前网页的logo图标。该logo显示在浏览器的地址栏,打开浏览器输入任何一个网站,基本都有一个icon图标,如果我们在开发自己的浏览器,就需要这样一个回调方法获取head标签内的link内容,同时结合上面第二个方法一起使用。
PS:同理,调用WebView的getFavicon()方法也必须回调上面方法,否则返回null。
- @Override
- public void onReceivedTitle(WebView view, String title) {
- super.onReceivedTitle(view, title);
- if (!TextUtils.isEmpty(mWebview.getTitle()))
- mAddressUrl_tv.setText(mWebview.getTitle());
- }
- @Override
- public void onReceivedIcon(WebView view, Bitmap icon) {
- super.onReceivedIcon(view, icon);
- Bitmap bitmap = mWebview.getFavicon();
- Drawable drawable = new BitmapDrawable(bitmap);
- if (drawable != null)
- mAddressUrl_tv.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
- }
注意:为了保证,调用getTitle()或getFavcion()方法时对应的回调方法已执行,参照上述保险的做法。
4、最后分析一下如何打开文件管理器onShowFileChooser(WebView webView, ValueCallback filePathCallback,FileChooserParams fileChooserParams)方法使用。
在WebView中想要回调该方法,在表单中声明input的类型为file,指定手机文件的存放路径。这个时候使用到ValueCallBack接口,取消打开文件管理器,filePathCallback.onReceiveValue(null)方法传入null,同时onShowFileChooser()返回true。
- @Override
- public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
- super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
- mFilePathCallback = filePathCallback;
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("*/*");
- startActivityForResult(Intent.createChooser(intent, "选择文件"),
- REQUEST_FILE_PICKER);
- return false;
- }
- /**
- * 过时的方法:openFileChooser
- */
- public void openFileChooser(ValueCallback<Uri> filePathCallback) {
- mFilePathCallback = filePathCallback;
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("image/*");
- startActivityForResult(
- Intent.createChooser(intent, "File Chooser"),
- REQUEST_FILE_PICKER);
- }
- /**
- * 过时的方法:openFileChooser
- */
- public void openFileChooser(ValueCallback filePathCallback,
- String acceptType) {
- mFilePathCallback = filePathCallback;
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("image/*");
- startActivityForResult(
- Intent.createChooser(intent, "File Chooser"),
- REQUEST_FILE_PICKER);
- }
- /**
- * 过时的方法:openFileChooser
- */
- public void openFileChooser(ValueCallback<Uri> filePathCallback,
- String acceptType, String capture) {
- mFilePathCallback = filePathCallback;
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("image/*");
- startActivityForResult(
- Intent.createChooser(intent, "File Chooser"),
- REQUEST_FILE_PICKER);
- }
执行流程:WebView加载带表单的HTML标签,同时input类型为file,点击按钮回调onShowFileChooser()方法或openFileChooser()方法,执行打开媒体管理器的操作,完成后选择文件后,在onActivityResult()方法中回调,同时再调接口方法onReceiveValue()将路径显示在WebView加载的表单页面中。
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
- super.onActivityResult(requestCode, resultCode, intent);
- if (requestCode == REQUEST_FILE_PICKER && mFilePathCallback != null) {
- Uri result = intent == null || resultCode != RESULT_OK ? null
- : intent.getData();
- if (result != null) {
- String path = GetPathFromUri4kitkat.getPath(this, result);
- Uri uri = Uri.fromFile(new File(path));
- mFilePathCallback.onReceiveValue(uri);
- String url = uri.getPath();
- Log.d(TAG, "onActivityResult: " + url);
- } else {
- mFilePathCallback.onReceiveValue(null);
- }
- }
- }
下一篇《详细分析WebViewClient源码各个回调方法使用说明》