设计思想理解
在WebView的设计中,不是什么事都要WebView类干的,有相当多的杂事是分给其他类做的,这样WebView专心干好自己的解析、渲染工作就行了。比如我们最熟知的,所有针对WebView的设置都封装到了WebSettings里。我们知道,在使用WebView加载资源过程中,可能会有大量各种类型事件的回调,为了方便开发组处理这些回调,针对不同的事件回调,google将这些回调进行了分类集合,于是就产生了WebViewClient、WebChromeClient这两个大类。
很多同学一看到WebChromeClient类里有Chrome,立马就会想到google的Chrome浏览器,其实这里并不是"特指"Chrome浏览器的意思,而是"泛指"浏览器的意思。
为什么叫WebChromeClient呢?这是因为WebChromeClient中集合了影响浏览器的事件到来时的回调方法,所以这里需要突出浏览器的概念,而Chrome则是google自家的浏览器名称,也是目前市面上最受欢迎的浏览器,所以就采用了WebChromeClient来做为名称吧,纯属臆想……
简单来说就是
- WebViewClient:在影响【View】的事件到来时,会通过WebViewClient中的方法回调通知用户
- WebChromeClient:当影响【浏览器】的事件到来时,就会通过WebChromeClient中的方法回调通知用法。
实际使用的话,如果你的WebView只是用来处理一些html的页面内容,只用WebViewClient就行了,如果需要更丰富的处理效果,比如JS、进度条等,就要用到WebChromeClient。
API
1、获取网页的加载进度
- void onProgressChanged(WebView view, int newProgress) Tell the host application the current progress of loading a page.
- newProgress: Current page loading progress, represented by an integer between 0 and 100.
- 大家一定要注意,底层实现时,是利用handler来定时轮循当前进度的,每隔一定时间查询一次,所以每次拿到的进度数据是不一样的。
- 也就是说如果页面较简单,可能会直接返回100,而跳过中间的各个数据。也就是说,除了100,其它任何一个数值不是一定返回的。
- 所以大家如果要用到进度,除了数值100可以用等号来判断,其它一定要用大于号或小于号,如果用了等号,可能永远也不会执行到。
void onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater) This method was deprecated in API level 19. This method is no longer called; WebView now uses the HTML5 / JavaScript Quota Management API.
2、获取网页中的基本信息
- void onReceivedIcon(WebView view, Bitmap icon) Notify the host application of a new favicon图标 for the current page.
- icon: A Bitmap containing the favicon for the current page.
- void onReceivedTitle(WebView view, String title) Notify the host application of a change in the document title 文档标题中的更改.
- title: A String containing the new title of the document.
- 获取标题的时间主要取决于网页前段设置标题的位置,一般设置在页面加载前面,可以较早调用到这个函数
- void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) Notify the host application of the url for an apple-touch-icon(苹果图标).
- 苹果为iOS设备配备了apple-touch-icon私有属性,添加该属性,在iPhone,iPad,iTouch的safari浏览器上可以使用添加到主屏按钮将网站添加到主屏幕上,方便用户以后访问。apple-touch-icon 标签支持sizes属性,可以用来放置对应不同的设备。
- url: The icon url.
- precomposed: True if the url is for a precomposed touch icon.
3、拦截网页中JS控制台消息
当html中调用console相关输出的时候,就会通过onConsoleMessage进行通知
和alert,prompt,confirm不同,我们不需要强制设置WebChromeClient(但是仍需要setJavaScriptEnabled为true),当点击log按钮时,也会调用console相应的函数把日志打印出来。
在我们logcat中也可以看到如下日志:
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Log.i("bqt", "【onConsoleMessage】" + "\nmessage=" + consoleMessage.message()
+ "\nlineNumber=" + consoleMessage.lineNumber()
+ "\nmessageLevel=" + consoleMessage.messageLevel() + "\nsourceId=" + consoleMessage.sourceId());
return super.onConsoleMessage(consoleMessage);
}
I/chromium: [INFO:CONSOLE(18)] "log日志", source: file:///android_asset/h5/JS%E5%BC%B9%E6%A1%86%E6%BC%94%E7%A4%BA.html (18)
I/chromium: [INFO:CONSOLE(19)] "warn日志", source: file:///android_asset/h5/JS%E5%BC%B9%E6%A1%86%E6%BC%94%E7%A4%BA.html (19)
I/chromium: [INFO:CONSOLE(20)] "error日志", source: file:///android_asset/h5/JS%E5%BC%B9%E6%A1%86%E6%BC%94%E7%A4%BA.html (20)
可以获取到的信息有:
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Log.i("bqt", "【onConsoleMessage】" + "\nmessage=" + consoleMessage.message()
+ "\nlineNumber=" + consoleMessage.lineNumber()
+ "\nmessageLevel=" + consoleMessage.messageLevel() + "\nsourceId=" + consoleMessage.sourceId());
return super.onConsoleMessage(consoleMessage);
}
- boolean onConsoleMessage(ConsoleMessage consoleMessage) Report a JavaScript console message to the host application. 向主机应用程序报告JavaScript控制台消息。
- The ChromeClient should override this to process the log message as they see fit 处理他们认为合适的日志消息.
- consoleMessage ConsoleMessage: Object containing details of the console message.
- 返回值:如果返回true时,就表示拦截了console输出,系统就不再通过console输出出来了;如果返回false(默认值),则表示没有拦截console输出,调用系统默认处理。
void onConsoleMessage(String message, int lineNumber, String sourceID) This method was deprecated in API level 8. Use onConsoleMessage(ConsoleMessage) instead.
4、拦截网页中JS弹框
1、如何使HTML中的弹框生效
虽然我们启用了JavaScript(前提条件),但是点击网页中的confrim()、alert()、prompt()却没有效果! 这是因为我们还需要设置WebChromClient!
在程序中,我们只需要给WebView加上.setWebChromeClient(new WebChromeClient())就可以实现confrim()、alert()、prompt()的弹出效果了。
2、JsResult的两个函数的作用
JsResult有两个函数:JsResult.confirm()和JsResult.cancel()。JsResult.confirm()表示点击了弹出框的确定按钮,JsResult.cancel()则表示点击了弹出框的取消按钮。
如果我们是return false,此时不需要我们手动调用JsResult的.confirm()或.cancel()方法,因为当我们点击弹框中的确认或取消按钮时JS会调用相应的方法。
如果我们是return true,此时我们必须手动调用JsResult的.confirm()或.cancel()方法,因为如果没有调用JsResult的confirm()或cancel()来告诉WebView你的处理结果,则WebView就会认为这个弹出框还一直弹在那里(虽然此时根本没有弹框弹出),所以之后你再点击alert按钮时,将会无效。这一点一定要注意。
3、方法返回值的意义
如果return true,WebView就会认为我们已经拦截了alert()函数,并且需要以自己的逻辑处理弹框,所以就不弹出alert对话框了。
否则,WebView就会认为我们没有拦截alert()函数,会继续弹出alert对话框(当然我们的吐司仍会弹出来)。
4、一共三种类型的弹框
- onJsAlert:当网页调用alert()来弹出alert弹出框前回调,用以拦截alert()函数
- onJsConfirm:当网页调用confirm()来弹出confirm弹出框前回调,用以拦截confirm()函数
- onJsPrompt:当网页调用prompt()来弹出prompt弹出框前回调,用以拦截prompt()函数
5、相关API
- boolean onJsAlert(WebView view, String url, String message, JsResult result) Tell the client to display a javascript alert dialog. 告诉客户端显示一个javascript警报对话框。
- If the client returns true, WebView will assume假定 that the client will handle the dialog. If the client returns false, it will continue execution 继续执行.
- The default behavior is to return false.(下面几个方法都是如此)
- 参数 url: The url of the page requesting the dialog.(下面几个方法都是如此)
- 参数 message: Message to be displayed in the window.(下面几个方法都是如此)
- 参数 result: A JsResult to confirm that the user hit enter. 一个用以确认用户点击进入的JsResult对象。
- Returns:boolean Whether the client will handle the alert dialog. 如果return true,不弹出对话框了;否则,会继续弹出对话框(下面几个方法都是如此)
- boolean onJsConfirm(WebView view, String url, String message, JsResult result) Tell the client to display a confirm dialog to the user.
- If the client returns true, WebView will assume假定 that the client will handle the confirm dialog and call the appropriate相应的 JsResult method.If the client returns false, a default value of false will be returned to javascript.
- Returns:boolean Whether the client will handle the confirm dialog. 如果return true,不弹出对话框了;否则,会继续弹出对话框
- boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) Tell the client to display a prompt dialog to the user.
- If the client returns true, WebView will assume假定 that the client will handle the prompt dialog and call the appropriate相应的 JsPromptResult method. If the client returns false, a default value of false will be returned to to javascript.
- 参数 defaultValue: The default value displayed in the prompt dialog.
- 参数 result: A JsPromptResult used to send the user's reponse to javascript.
- boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) Tell the client to display a dialog to confirm navigation导航 away from离开 the current page.
- This is the result of the onbeforeunload javascript event. If the client returns true, WebView will assume假定 that the client will handle the confirm dialog and call the appropriate相应的 JsResult method. If the client returns false, a default value of true will be returned to javascript to accept navigation away from the current page.
- Setting the JsResult to true will navigate away from the current page, false will cancel the navigation.
boolean onJsTimeout() This method was deprecated in API level 17. This method is no longer supported and will not be invoked.
5、打开和关闭Window(多窗口)
1、onCreateWindow的回调逻辑
我们都知道,超级链接标签a里有一个target属性,其意义是决定"是否在新窗口/标签页中打开链接",如果不写target=”_blank”那么就是在相同的标签页打开,如果写了,就是在新的空白标签页中打开。比如:
<a href="https://www.taobao.com/" title="淘宝" target="_blank">新窗口打开链接</a>
而我们WebView默认是不支持target这个属性的,默认情况下,点击上面的链接会在当前WebView中打开此链接。
那么,如何如何在一个新窗口打开一个具有target="_blank"属性的超链接呢?
要实现这个功能,我们必须设置如下属性:
webSettings.setSupportMultipleWindows(true);//支持多窗口。如果设置为true,主程序要实现onCreateWindow
完整逻辑如下
- 没有设置setSupportMultipleWindows属性为true
- 没有setWebChromeClient:点击此链接会在当前WebView中打开此链接
- 有setWebChromeClient:点击此链接会在当前WebView中打开此链接,不会回调onCreateWindow方法
- 有设置setSupportMultipleWindows属性为true
- 没有setWebChromeClient:点击此链接不会在当前WebView中打开此链接
- 有setWebChromeClient
- 没有重写onCreateWindow方法:点击此链接不会在当前WebView中打开此链接,会回调onCreateWindow方法
- 有重写onCreateWindow方法:点击此链接不会在当前WebView中打开此链接,会回调onCreateWindow方法,会在你新创建的WebView中打开此链接
2、案例:在新窗口打开一个具有target="_blank"属性的超链接
这里有一个案例 http://blog.csdn.net/asdfghgw/article/details/76066769 ,但是试验后发现根本不行!网上也找了很多资料,都是不行!
可供参考的代码段:
@Override
public boolean onCreateWindow(WebView webView, boolean isDialog, boolean isUserGesture, Message resultMsg) {
Log.i("bqt", "【onCreateWindow】 " + isDialog + " " + isUserGesture + "\n详细信息" + resultMsg.toString());
if (activity != null) {
WebView childView = new WebView(activity);//Parent WebView cannot host it's own popup window.
childView.setBackgroundColor(Color.GREEN);
childView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.i("bqt", "【shouldOverrideUrlLoading-子】");
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
return true;
}
});
WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
transport.setWebView(childView);//setWebView和getWebView两个方法
resultMsg.sendToTarget();
return true;
} else return super.onCreateWindow(webView, isDialog, isUserGesture, resultMsg);//默认是returns false
}
3、相关API
- boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) Request the host application to create a new window. 请求主机应用程序创建一个新窗口。
- If the host application chooses to honor this request遵守、尊敬此请求, it should return true from this method, create a new WebView to host托管 the window, insert it into the View system and send the supplied提供的 resultMsg message to its target with the new WebView as an argument. If the host application chooses not to honor the request, it should return false from this method.
- The default implementation of this method does nothing and hence因此、因而 returns false.
- 参数 view: The WebView from which the request for a new window originated 发起新窗口的请求的.
- 参数 isDialog: True if the new window should be a dialog, rather than而不是 a full-size window.
- 参数 isUserGesture: True if the request was initiated by发起 a user gesture用户手势, such as the user clicking a link单击链接.
- 参数 esultMsg: The message to send when once a new WebView has been created. resultMsg.obj is a WebView.WebViewTransport object. This should be used to transport the new WebView, by calling WebView.WebViewTransport.setWebView(WebView).
- 返回值:This method should return true if the host application will create a new window, in which case在这种情况下 resultMsg should be sent to its target. Otherwise, this method should return false. Returning false from this method but also sending resultMsg will result in导致 undefined behavior未定义的行为.
- void onCloseWindow(WebView window) Notify the host application to close the given WebView and remove it from the view system if necessary. 通知主机主机应用WebView关闭了,并在需要的时候从view系统中移除它。
- 此时,WebCore已经停止窗口中的所有加载进度,并在JavaScript中移除了所有cross-scripting的功能。
- 实验发现,在JS调用window.close()方法时会回调此方法【<button onclick="window.close()">关闭窗口</button>】
- 参数 window: The WebView that needs to be closed.
6、视频(全屏)播放
- Bitmap getDefaultVideoPoster() When not playing, video elements are represented by a 'poster' image. 当不播放时,视频元素由“poster”图像表示。
- The image to use can be specified指定 by the poster attribute of the video tag in HTML. If the attribute is absent不存在, then a default poster will be used. This method allows the ChromeClient to provide that default image.
- 返回值:Bitmap The image to use as a default poster, or null if no such image is available.
- View getVideoLoadingProgressView() Obtains a View to be displayed while buffering of full screen video is taking place. 获取在全屏幕视频缓冲期间显示的视图。
- The host application can override this method to provide a View containing a spinner微调器、旋转器 or similar.
- 播放视频时,在第一帧呈现之前,需要花一定的时间来进行数据缓冲。ChromeClient可以使用这个函数来提供一个在数据缓冲时显示的视图。
- 返回值:The View to be displayed whilst the video is loading.
- void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) Notify the host application that the current page has entered full screen mode 全屏模式.
- The host application must show the custom View which contains the web contents — video or other HTML content — in full screen mode. Also see "Full screen support" documentation on WebView.
- 网页中有H5播放flash video的时候按下全屏按钮将会调用到这个方法,一般用作设置网页播放全屏操作。
- view: the View object to be shown.
- callback: invoke this callback to request the page to exit full screen mode.
void onShowCustomView(View view, int requestedOrientation, WebChromeClient.CustomViewCallback callback) This method was deprecated in API level 18. This method supports the obsolete plugin mechanism, and will not be invoked in future.- void onHideCustomView() Notify the host application that the current page has exited full screen mode.
- The host application must hide the custom View, ie.即 the View passed to传递给 onShowCustomView(view, callback) when the content entered fullscreen. Also see "Full screen support" documentation on WebView.
7、文件选择器
- boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) Tell the client to show a file chooser 文件选择器. added in API level 21
- This is called to handle HTML forms with 'file' input type, in response to the user pressing the "Select File" button. 这是为了响应用户按下“选择文件”按钮来处理带有“file”输入类型的HTML表单
- To cancel the request, call filePathCallback.onReceiveValue(null) and return true.
- 参数 filePathCallback: Invoke this callback to supply the list of paths to files to upload 提供要上传的文件的路径列表, or NULL to cancel. Must only be called if the showFileChooser implementations returns true.
- 参数 fileChooserParams: Describes the mode of file chooser to be opened, and options to be used with it. 描述要打开的文件选择器的模式,以及与之一起使用的选项。
- 返回值:Returns true if filePathCallback will be invoked, false to use default handling.
8、请求权限/获取地理位置
- void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) Notify the host application that web content from the specified origin指定源 is attempting to use the Geolocation地理定位 API, but no permission state is currently set for that origin源.
- The host application should invoke调用 the specified callback with the desired所需 permission state. See GeolocationPermissions for details 有关详细信息.
- Note that for applications targeting Android N and later更高版本的 SDKs (API level > M) this method is only called for requests originating from源自 secure origins安全起始点 such as https. On non-secure origins geolocation requests are automatically denied拒绝.
- 参数 origin: The origin of the web content attempting to use the Geolocation API.
- 参数 callback: The callback to use to set the permission state for the origin.
GeolocationPermissions.Callback接口只有一个invoke方法
public void invoke (String origin, boolean allow, boolean retain)
Sets the Geolocation permission state for the supplied origin.
Parameters
origin String: the origin for which permissions are set
allow boolean: whether or not the origin should be allowed to use the Geolocation API
retain boolean: whether the permission should be retained保留 beyond在...之内 the lifetime of a page currently being displayed by a WebView
- void onGeolocationPermissionsHidePrompt() Notify the host application that a request for Geolocation地理定位 permissions, made with a previous call to onGeolocationPermissionsShowPrompt() has been canceled. 通知主机应用程序,以前调用**的地理位置权限请求已被取消。
- Any related相关 UI should therefore因此 be hidden.
- void onPermissionRequest(PermissionRequest request) Notify the host application that web content is requesting permission to access the specified resources访问指定资源 and the permission currently isn't granted授予 or denied拒绝.
- The host application must invoke调用 grant(String[]) or deny(). If this method isn't overridden, the permission is denied拒绝.
- 参数 request: the PermissionRequest from current web content.
- void onPermissionRequestCanceled(PermissionRequest request) Notify the host application that the given permission request has been canceled.
- Any related相关 UI should therefore因此 be hidden.
9、其他方法
- void getVisitedHistory(ValueCallback<String[]> callback) Obtains获得 a list of all visited history items, used for link coloring. 获得所有访问历史项目的列表,用于链接着色。
void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater) This method was deprecated in API level 19. This method is no longer called; WebView now uses the HTML5 / JavaScript Quota Management API.
- void onRequestFocus(WebView view) Request display and focus for this WebView.
- This may happen due to由于 another WebView opening a link in this WebView and requesting that this WebView be displayed.
- 参数view: The WebView that needs to be focused.
全屏播放视频案例
1、清单文件设置
必要的权限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
播放视频的Activity必须设置以下属性
android:configChanges="keyboardHidden|orientation|screenSize"
android:hardwareAccelerated="true"
2、自定义WebChromeClient
private View mCustomView;//onShowCustomView传过来的view,其实就是我们构造的View
private CustomViewCallback mCustomViewCallback;//onShowCustomView传过来的callback
private FullscreenHolder videoFullView;//全屏播放视频时的根布局
// 播放网络视频时全屏会被调用的方法
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
Log.i("bqt", "【onShowCustomView】" + view.getClass().getSimpleName());//FrameLayout
if (view instanceof ViewGroup) {
ViewGroup vp = (ViewGroup) view;
Log.i("bqt", "【onShowCustomView】count=" + vp.getChildCount()
+ " type=" + vp.getChildAt(0).getClass().getSimpleName());//count=1 type=FullScreenView
}
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
if (mCustomView == null) {
mCustomView = view;
mCustomViewCallback = callback;
videoFullView = new FullscreenHolder(activity);
videoFullView.addView(mCustomView);
((FrameLayout) activity.getWindow().getDecorView()).addView(videoFullView);
} else callback.onCustomViewHidden();
}
// 视频播放退出全屏会被调用的
@Override
public void onHideCustomView() {
Log.i("bqt", "【onHideCustomView】");
// 是全屏播放状态
if (videoFullView != null && mCustomView != null && mCustomViewCallback != null) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
videoFullView.removeView(mCustomView);
videoFullView.setVisibility(View.GONE);//这个不能省
mCustomView = null;
videoFullView = null;
mCustomViewCallback.onCustomViewHidden();
}
}
public boolean isOnShowCustomView() {
return mCustomView != null;
}
3、Activity中设置重写后退按键事件
@Override
//设置当点击后退按钮时不是退出Activity,而是让WebView后退一页。也可以通过webview.setOnKeyListener设置
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (mWebChromeClient != null && mWebChromeClient.isOnShowCustomView()) {
mWebChromeClient.onHideCustomView();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
return true;
} else if (webview.canGoBack()) {//返回网页上一页
webview.goBack(); //后退,goForward() 前进
return true;
} else {//退出网页
webview.loadUrl("about:blank");
finish();
}
}
return super.onKeyDown(keyCode, event);
}
4、参考HTML代码
<video width="100%p" height="150" controls="controls" poster="../icon.jpg">
<source src="http://qnfile.ogod.xin/_F-UBAMKr_AID.mp4" type="video/mp4"/>
</video>
文件选择器案例
1、重写WebChromeClient
//********************************************以下为5.0以上,上传文件相关代码*******************************************
private ValueCallback<Uri[]> mUploadMessageForAndroid5;
public static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> uploadMsg, FileChooserParams params) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Log.i("bqt", "【onShowFileChooser】 5.0+" + " " + params.getMode() + " " + params.getTitle() + " "//Mode为0
+ params.isCaptureEnabled() + " " + params.getFilenameHint() + " " + Arrays.toString(params.getAcceptTypes()));
}
mUploadMessageForAndroid5 = uploadMsg;
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType("image/*");//文件类型
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
chooserIntent.putExtra(Intent.EXTRA_TITLE, "图片选择");
activity.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
return true;
}
//5.0以上,上传图片成功后的回调
public void mUploadMessageForAndroid5(Intent intent, int resultCode) {
Log.i("bqt", "【上传图片成功后的回调】 5.0+");
if (mUploadMessageForAndroid5 != null) {
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;
}
}
//*******************************************以下为5.0以下,上传文件相关代码***********************************************
private ValueCallback<Uri> mUploadMessage;
public static int FILECHOOSER_RESULTCODE = 1;
//The undocumented magic method override Eclipse will swear at you if you try to put @Override here
//undocumented:无正式文件的,无事实证明的;与…不协调,咒骂,发誓
// For Android 3.0-
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
openFileChooserImpl(uploadMsg);
}
// For Android 3.0+
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
openFileChooserImpl(uploadMsg);
}
//For Android 4.1
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
openFileChooserImpl(uploadMsg);
}
//低版本上传文件代码
private void openFileChooserImpl(ValueCallback<Uri> uploadMsg) {
Log.i("bqt", "【openFileChooser】");
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
activity.startActivityForResult(Intent.createChooser(i, "文件选择"), FILECHOOSER_RESULTCODE);
}
//5.0以下,上传图片成功后的回调
public void mUploadMessage(Intent intent, int resultCode) {
Log.i("bqt", "【上传图片成功后的回调】 5.0-");
if (mUploadMessage != null) {
if (intent != null && resultCode == RESULT_OK) mUploadMessage.onReceiveValue(intent.getData());
mUploadMessage = null;
}
}
2、Activity中处理上传图片之后的回调
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
Log.i("bqt", "【onActivityResult】 requestCode=" + requestCode + " resultCode=" + resultCode);
//上传图片之后的回调
if (requestCode == MyWebChromeClient.FILECHOOSER_RESULTCODE) {
mWebChromeClient.mUploadMessage(intent, resultCode);
} else if (requestCode == MyWebChromeClient.FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {
mWebChromeClient.mUploadMessageForAndroid5(intent, resultCode);
}
}
3、参考HTML代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图片上传预览</title>
<script type="text/javascript">
function imgPreview(fileDom){
//判断是否支持FileReader
if (window.FileReader) var reader = new FileReader();
else alert("您的设备不支持图片预览功能,如需该功能请升级您的设备!");
//获取文件
var file = fileDom.files[0];
var imageType = /^image\//;
//是否是图片
if (!imageType.test(file.type)) {
alert("请选择图片!");
return;
}
//读取完成
reader.onload = function(e) {
//获取图片dom
var img = document.getElementById("preview");
//图片路径设置为读取的图片
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
</script>
</head>
<body>
<img id="preview" width="100%P"/>
<br/>
<input type="file" name="file" onchange="imgPreview(this)"/>
</body>
</html>
完整的WebChromeClient
public class MyWebChromeClient extends WebChromeClient {
private WebViewActivity activity;//控件的显示和隐藏应该都由WebViewClient控制
public MyWebChromeClient(WebViewActivity activity) {
super();
this.activity = activity;
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
Log.i("bqt", "【onProgressChanged】 " + newProgress);
if (activity.getProgress_bar() != null) activity.getProgress_bar().setProgress(newProgress);
super.onProgressChanged(view, newProgress);
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
//If the client returns true, WebView will assume that the client will handle the dialog. Otherwise, it will continue execution执行.
boolean b = new Random().nextBoolean();
Log.i("bqt", "【onJsAlert】" + b + " " + url + " " + message);
if (b) return super.onJsAlert(view, url, message, result);//默认为return false,有弹窗
else {//拦截html中alert函数之后,我们可以在这里做任何自己的操作。我们还可以根据message来做不同的操作
Toast.makeText(view.getContext(), "onJsAlert,message=" + message, Toast.LENGTH_SHORT).show();
result.confirm();//confirm()表示点击了弹出框的确定按钮,cancel()则表示点击了弹出框的取消按钮。两者必须调用一个
return true;//如果return true,不弹出对话框了;否则,会继续弹出对话框
}
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
boolean b = new Random().nextBoolean();
Log.i("bqt", "【onJsConfirm】" + b + " " + url + " " + message);
//return super.onJsConfirm(view, url, message, result);//默认为return false,有弹窗
Toast.makeText(view.getContext(), "onJsConfirm,message=" + message, Toast.LENGTH_SHORT).show();
result.confirm();
return b;//如果return true,不弹出对话框了;否则,会继续弹出对话框
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
boolean b = new Random().nextBoolean();
Log.i("bqt", "【onJsPrompt】" + b + " " + url + " " + message + " " + defaultValue);
Toast.makeText(view.getContext(), "亲,请输入你的昵称", Toast.LENGTH_SHORT).show();
return super.onJsPrompt(view, url, message, defaultValue, result);//默认为return false,有弹窗
}
@Override
public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
boolean b = new Random().nextBoolean();
Log.i("bqt", "【onJsBeforeUnload】" + b + " " + url + " " + message);
return super.onJsBeforeUnload(view, url, message, result);
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Log.i("bqt", "【onConsoleMessage】" + "\nmessage=" + consoleMessage.message()
+ "\nlineNumber=" + consoleMessage.lineNumber()
+ "\nmessageLevel=" + consoleMessage.messageLevel() + "\nsourceId=" + consoleMessage.sourceId());
return super.onConsoleMessage(consoleMessage);
}
@Override
public void onReceivedTitle(WebView view, String title) {
Log.i("bqt", "【onReceivedTitle 标题】" + title);
activity.getTv_title().setText(title);
super.onReceivedTitle(view, title);
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
Log.i("bqt", "【onReceivedIcon 图标】");
activity.getIv_icon().setVisibility(View.VISIBLE);
activity.getIv_icon().setImageBitmap(icon);
super.onReceivedIcon(view, icon);
}
@Override
public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
//【url】The icon url. 【precomposed】True if the url is for a precomposed touch icon.
Log.i("bqt", "【onReceivedTouchIconUrl 苹果图标】" + precomposed + " " + url);
super.onReceivedTouchIconUrl(view, url, precomposed);
}
//获得所有访问历史项目的列表,用于链接着色。
@Override
public void getVisitedHistory(ValueCallback<String[]> callback) {
Log.i("bqt", "【getVisitedHistory 不知道怎么用】" + callback.toString());
super.getVisitedHistory(callback);
}
@Override
public boolean onCreateWindow(WebView webView, boolean isDialog, boolean isUserGesture, Message resultMsg) {
Log.i("bqt", "【onCreateWindow】 " + isDialog + " " + isUserGesture + "\n详细信息" + resultMsg.toString());
return super.onCreateWindow(webView, isDialog, isUserGesture, resultMsg);//默认是returns false
}
@Override
public void onCloseWindow(WebView window) {
Log.i("bqt", "【onCloseWindow】");
super.onCloseWindow(window);
}
@Override
public void onPermissionRequest(PermissionRequest request) {
Log.i("bqt", "【onPermissionRequest】");
//The host application must invoke grant(String[]) or deny(). If this method isn't overridden, the permission is denied拒绝.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Log.i("bqt", "Origin=" + request.getOrigin().toString() + " Resources=" + Arrays.toString(request.getResources()));
}
super.onPermissionRequest(request);
}
@Override
public void onPermissionRequestCanceled(PermissionRequest request) {
Log.i("bqt", "【onPermissionRequestCanceled】");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Log.i("bqt", "Origin=" + request.getOrigin().toString() + " Resources=" + Arrays.toString(request.getResources()));
}
super.onPermissionRequestCanceled(request);
}
@Override
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
Log.i("bqt", "【onGeolocationPermissionsShowPrompt】" + origin);
//callback只有一个方法【public void invoke(String origin, boolean allow, boolean retain)】
super.onGeolocationPermissionsShowPrompt(origin, callback);
}
@Override
public void onGeolocationPermissionsHidePrompt() {
Log.i("bqt", "【onGeolocationPermissionsHidePrompt】");
super.onGeolocationPermissionsHidePrompt();
}
@Override
public void onRequestFocus(WebView view) {
Log.i("bqt", "【onRequestFocus】" + (view == activity.getWebview()));
super.onRequestFocus(view);
}
@Override
public Bitmap getDefaultVideoPoster() {
Log.i("bqt", "【getDefaultVideoPoster】");
return BitmapFactory.decodeResource(activity.getResources(), R.drawable.ic_launcher);
//return super.getDefaultVideoPoster(); //return null;
}
private ImageView loadingView;
// 视频加载时进程loading
@Override
public View getVideoLoadingProgressView() {
Log.i("bqt", "【getVideoLoadingProgressView】");
if (loadingView != null) return loadingView;
else {
loadingView = new ImageView(activity);
loadingView.setImageResource(R.drawable.icon);
return loadingView;
}
//else return super.getVideoLoadingProgressView();//return null;
}
//***************** ↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 以下为全屏播放视频相关代码 ↓↓↓↓↓↓↓↓↓↓↓↓↓ *******************
private View mCustomView;//onShowCustomView传过来的view,其实就是我们构造的View
private CustomViewCallback mCustomViewCallback;//onShowCustomView传过来的callback
private FullscreenHolder videoFullView;//全屏播放视频时的根布局
// 播放网络视频时全屏会被调用的方法
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
Log.i("bqt", "【onShowCustomView】" + view.getClass().getSimpleName());//FrameLayout
if (view instanceof ViewGroup) {
ViewGroup vp = (ViewGroup) view;
Log.i("bqt", "【onShowCustomView】count=" + vp.getChildCount()
+ " type=" + vp.getChildAt(0).getClass().getSimpleName());//count=1 type=FullScreenView
}
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
if (mCustomView == null) {
mCustomView = view;
mCustomViewCallback = callback;
videoFullView = new FullscreenHolder(activity);
videoFullView.addView(mCustomView);
((FrameLayout) activity.getWindow().getDecorView()).addView(videoFullView);
} else callback.onCustomViewHidden();
}
// 视频播放退出全屏会被调用的
@Override
public void onHideCustomView() {
Log.i("bqt", "【onHideCustomView】");
// 是全屏播放状态
if (videoFullView != null && mCustomView != null && mCustomViewCallback != null) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
videoFullView.removeView(mCustomView);
videoFullView.setVisibility(View.GONE);//这个不能省
mCustomView = null;
videoFullView = null;
mCustomViewCallback.onCustomViewHidden();
}
}
public boolean isOnShowCustomView() {
return mCustomView != null;
}
//***************** ↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 以上为全屏播放视频相关代码 ↑↑↑↑↑↑↑↑↑↑↑↑↑ *******************
//********************************************以下为5.0以上,上传文件相关代码*******************************************
private ValueCallback<Uri[]> mUploadMessageForAndroid5;
public static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> uploadMsg, FileChooserParams params) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Log.i("bqt", "【onShowFileChooser】 5.0+" + " " + params.getMode() + " " + params.getTitle() + " "//Mode为0
+ params.isCaptureEnabled() + " " + params.getFilenameHint() + " " + Arrays.toString(params.getAcceptTypes()));
}
mUploadMessageForAndroid5 = uploadMsg;
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType("image/*");//文件类型
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
chooserIntent.putExtra(Intent.EXTRA_TITLE, "图片选择");
activity.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
return true;
}
//5.0以上,上传图片成功后的回调
public void mUploadMessageForAndroid5(Intent intent, int resultCode) {
Log.i("bqt", "【上传图片成功后的回调】 5.0+");
if (mUploadMessageForAndroid5 != null) {
Uri result = (intent == null || resultCode != Activity.RESULT_OK) ? null : intent.getData();
if (result != null) mUploadMessageForAndroid5.onReceiveValue(new Uri[]{result});
else mUploadMessageForAndroid5.onReceiveValue(new Uri[]{});
mUploadMessageForAndroid5 = null;
}
}
//*******************************************以下为5.0以下,上传文件相关代码***********************************************
private ValueCallback<Uri> mUploadMessage;
public static int FILECHOOSER_RESULTCODE = 1;
//The undocumented magic method override Eclipse will swear at you if you try to put @Override here
//undocumented:无正式文件的,无事实证明的;与…不协调,咒骂,发誓
// For Android 3.0-
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
openFileChooserImpl(uploadMsg);
}
// For Android 3.0+
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
openFileChooserImpl(uploadMsg);
}
//For Android 4.1
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
openFileChooserImpl(uploadMsg);
}
//低版本上传文件代码
private void openFileChooserImpl(ValueCallback<Uri> uploadMsg) {
Log.i("bqt", "【openFileChooser】");
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
activity.startActivityForResult(Intent.createChooser(i, "文件选择"), FILECHOOSER_RESULTCODE);
}
//5.0以下,上传图片成功后的回调
public void mUploadMessage(Intent intent, int resultCode) {
Log.i("bqt", "【上传图片成功后的回调】 5.0-");
if (mUploadMessage != null) {
if (intent != null && resultCode == Activity.RESULT_OK) mUploadMessage.onReceiveValue(intent.getData());
mUploadMessage = null;
}
}
}
请求参数
WebViewActivity.start(this, WebSettingsModel.newBuilder()
.url("file:///android_asset/h5/WebChromeClient演示.html")
.setJavaScriptEnabled(true)
.setDomStorageEnabled(true)//这句话必须保留,否则无法播放优酷网页视频,其他的可以
.setSupportMultipleWindows(true)
.setMixedContentMode(WebSettingsModel.MIXED_CONTENT_ALWAYS_ALLOW)//我觉得这个设置一般都很有必要
.setGeolocationEnabled(true)
.build());
参考的HTML代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button onclick="alert('alert弹框')">alert</button>
<button onclick="confirm('confirm弹框')">confirm</button>
<button onclick="prompt('prompt弹框','包青天')">prompt</button>
<p></p>
<button onclick="log()">调用console打印日志</button>
<button onclick="window.close()">关闭窗口</button>
<h2>链接</h2>
<a href="file:///android_asset/h5/test.html" title="新窗口" target="_blank">有target属性的链接</a>
<p></p>
<a href="http://v.youku.com/v_show/id_XODkxMjcyNzAw.html?spm=a2h1n.8251845.0.0" title="网页视频">没有target属性的链接,网页视频</a>
<p></p>
<a href="file:///android_asset/h5/uploadPic.html" title="图片上传预览">图片上传预览</a>
<h2>视频</h2>
ogod视频,没有poster属性
<p></p>
<video width="100%p" controls="controls">
<source src="http://qnfile.ogod.xin/_F-UBALyBRgEC.mp4" type="video/mp4"/>
</video>
ogod视频,有poster属性
<p></p>
<video width="100%p" controls="controls" poster="../icon.jpg">
<source src="http://qnfile.ogod.xin/_F-UBAMKr_AID.mp4"
type="video/mp4"/>
</video>
</body>
<script type="text/javascript">
function log(){
console.log("log日志");
console.warn("warn日志");
console.error("error日志");
}
</script>
</html>
2017-8-18