由于文章太长,官方不让发布,所以文章就一分为二了
WebViewClient相关方法
- shouldOverrideUrlLoading(WebView view, String url)和shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
shouldOverrideUrlLoading执行时机是重定向时(网页自动重定向或手动点击网页内部链接)
mywebview.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//过时
Log.d("yunchong", "shouldOverrideUrlLoading1:"+url);
if(!TextUtils.isEmpty(url)){
if(url.startsWith("http://") || url.startsWith("https://")){
view.loadUrl(url);
} else{
//跳转到第三方
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
MainActivity.this.startActivity(intent);
}catch (Exception e){
//如果抛出异常,则说明本地没有安装第三方
e.printStackTrace();
}
}
return true;
}
return false;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
Log.d("yunchong", "shouldOverrideUrlLoading2:"+url);
String url = view.getUrl();
if(!TextUtils.isEmpty(url)){
if(url.startsWith("http://") || url.startsWith("https://")){
view.loadUrl(url);
} else{
//跳转到第三方
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
MainActivity.this.startActivity(intent);
}catch (Exception e){
//如果抛出异常,则说明本地没有安装第三方
e.printStackTrace();
}
}
return true;
}
return false;
}
主要注意以下几点:
(1)前者在API24(Android 7.0)之后已经过时,虽然已经过时了,但是我们还是需要用到这个方法的,原因是API24之前的手机只执行前者,后者只有在API24之后才会执行;
(2)以上代码中,后者的返回值有true和false两种, 第三种返回值是默认返回值
return super.shouldOverrideUrlLoading(view, request);
这个返回值最终还是会执行前者方法。
2.shouldInterceptRequest(WebView view, String url)和shouldInterceptRequest(final WebView view, WebResourceRequest request)
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
//过时
Log.d("yunchong", url);
return super.shouldInterceptRequest(view, url);
}
捕获的日志如下:
此方法废弃于API21,调用于非UI线程,虽然废弃了,但是我在Android4.4、Android6.0、Android8.0的手机经过测试,依然可以拦截到加载资源
拦截资源请求并返回响应数据,返回null时WebView将继续加载资源
注意:API21以下的AJAX请求会走onLoadResource,无法通过此方法拦截
在API21之后新增一个新的方法:
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(final WebView view, WebResourceRequest request) {
view.post(new Runnable() {
@Override
public void run() {
Log.d("yunchong", "shouldInterceptRequest2:"+view.getUrl());
}
});
return super.shouldInterceptRequest(view, request);
}
用于替代前者。
3.onLoadResource(WebView view, String url)
这个方法和shouldInterceptRequest一样,同样可以拦截到加载资源,他们的区别是:shouldInterceptRequest已过时,onLoadResource没有过时。
3.onTooManyRedirects(WebView view, Message cancelMsg, Message continueMsg)
这个方法是为了解决一些重定向问题,已经彻底废弃,现在处理重定向问题是用shouldOverrideUrlLoading方法了。
4.onPageStarted和onPageFinished
网页的加载开始和加载结束。
5.onPageCommitVisible(WebView view, String url)
// 这个回调添加于API23,仅用于主框架的导航
// 通知应用导航到之前页面时,其遗留的WebView内容将不再被绘制。
// 这个回调可以用来决定哪些WebView可见内容能被安全地回收,以确保不显示陈旧的内容
// 它最早被调用,以此保证WebView.onDraw不会绘制任何之前页面的内容,随后绘制背景色或需要加载的新内容。
// 当HTTP响应body已经开始加载并体现在DOM上将在随后的绘制中可见时,这个方法会被调用。
// 这个回调发生在文档加载的早期,因此它的资源(css,和图像)可能不可用。
// 如果需要更细粒度的视图更新,查看 postVisualStateCallback(long, WebView.VisualStateCallback).
// 请注意这上边的所有条件也支持 postVisualStateCallback(long ,WebView.VisualStateCallback)
@Override
public void onPageCommitVisible(WebView view, String url) {
Log.d("yunchong", "onPageCommitVisible:"+url);
super.onPageCommitVisible(view, url);
}
跟踪日志发现任意的网页跳转都会执行onPageCommitVisible方法,参数url就是当前页面的url。
6.onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse)
// 此方法添加于API23
// 在加载资源(iframe,image,js,css,ajax...)时收到了 HTTP 错误(状态码>=400)
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
super.onReceivedHttpError(view, request, errorResponse);
}
7.onFormResubmission(WebView view, Message dontResend, Message resend)
// 是否重新提交表单,默认不重发
@Override
public void onFormResubmission(WebView view, Message dontResend, Message resend) {
super.onFormResubmission(view, dontResend, resend);
}
8.doUpdateVisitedHistory(WebView view, String url, boolean isReload)
// 通知应用可以将当前的url存储在数据库中,意味着当前的访问url已经生效并被记录在内核当中。
// 此方法在网页加载过程中只会被调用一次,网页前进后退并不会回调这个函数。
@Override
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
super.doUpdateVisitedHistory(view, url, isReload);
}
9.onReceivedClientCertRequest(WebView view, ClientCertRequest request)
// 此方法添加于API21,在UI线程被调用
// 处理SSL客户端证书请求,必要的话可显示一个UI来提供KEY。
// 有三种响应方式:proceed()/cancel()/ignore(),默认行为是取消请求
// 如果调用proceed()或cancel(),Webview 将在内存中保存响应结果且对相同的"host:port"不会再次调用 onReceivedClientCertRequest
// 多数情况下,可通过KeyChain.choosePrivateKeyAlias启动一个Activity供用户选择合适的私钥
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
super.onReceivedClientCertRequest(view, request); //默认
//request.cancel();//取消
//request.ignore();//忽视
//request.proceed(param1, param2);//接受
}
10.onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm)
// 处理HTTP认证请求,默认行为是取消请求
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
Log.d("yunchong", "onReceivedHttpAuthRequest");
super.onReceivedHttpAuthRequest(view, handler, host, realm);//默认
//handler.cancel();//取消
//handler.proceed(username, password);//接受
}
11.shouldOverrideKeyEvent(WebView view, KeyEvent event)
// 给应用一个机会处理按键事件
// 如果返回true,WebView不处理该事件,否则WebView会一直处理,默认返回false
@Override
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
return super.shouldOverrideKeyEvent(view, event);
}
12.onUnhandledKeyEvent(WebView view, KeyEvent event)
// 处理未被WebView消费的按键事件
// WebView总是消费按键事件,除非是系统按键或shouldOverrideKeyEvent返回true
// 此方法在按键事件分派时被异步调用
@Override
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
super.onUnhandledKeyEvent(view, event);
}
13.onScaleChanged(WebView view, float oldScale, float newScale)
// 通知应用页面缩放系数变化
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
super.onScaleChanged(view, oldScale, newScale);
}
当webview支持双指缩放时,onScaleChanged回调方法可以监听webview的缩放系数。
14.onReceivedLoginRequest(WebView view, String realm, @Nullable String account, String args)
//通知应用有个已授权账号自动登陆了
@Override
public void onReceivedLoginRequest(WebView view, String realm, @Nullable String account, String args) {
super.onReceivedLoginRequest(view, realm, account, args);
}
15.onRenderProcessGone(WebView view, RenderProcessGoneDetail detail)
//这个API处理一个WebView对象的渲染程序消失的情况,要么是因为系统杀死了渲染器以回收急需的内存,要么是因为渲染程序本身崩溃了。
// 通过使用这个API,您可以让您的应用程序继续执行,即使渲染过程已经消失了。
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
//RenderProcessGoneDetail : 此类提供了有关渲染进程退出原因的更具体信息。应用程序可以使用它来决定如何处理这种情况。
//RenderProcessGoneDetail提供了两个方法
//didCrash():指示是否观察到呈现进程崩溃,或者是否被系统终止。
//rendererPriorityAtExit():返回在渲染器退出时设置的渲染器优先级。
if(!detail.didCrash()){
//由于系统内存不足,渲染器被终止。
//通过创建新的WebView实例,应用程序可以正常恢复
if (mywebview != null) {
ViewGroup webViewContainer = (ViewGroup) findViewById(R.id.mywebview);
webViewContainer.removeView(mywebview);
mywebview.destroy();
mywebview = null;
}
return true;
}
return false;
}
16.onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponse callback)
//通知主应用程序加载URL已被安全浏览标记。
@Override
public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponse callback) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
callback.backToSafety(true);//类似于用户点击了“返回安全”按钮
callback.proceed(true);//类似于用户单击了“访问此不安全站点”按钮一样。
callback.showInterstitial(true);//如果为true则显示报告复选框
}
super.onSafeBrowsingHit(view, request, threatType, callback);
}
以上的注释是由谷歌官方文档翻译而来,可悲的是我尝试了7.0、8.0、8.1的手机仍然没有执行到该方法。没有发现此方法的执行时机。
17.onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)
当加载https网页的情况下,默认不进行证书校验,当服务器要求webview必须进行证书校验的时候,默认情况下加载网页是一片空白,这个时候我们需要在onReceivedSslError方法里做一些处理
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
也就是说,接受所有的https证书。
然而这样做是不安全的,我们需要对证书做ssl校验,代码如下:(我们事先将crt证书放入assets目录下)
@Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
String currentUrl = view.getUrl();
if(TextUtils.isEmpty(currentUrl)){//个别手机获取到的url是空的
currentUrl = homeUrl;//这里的homeUrl是从其他地方获取到的
}
SslManager.webviewSsl(currentUrl, new SslListener() {
@Override
public void onResponse() {
handler.proceed();
}
@Override
public void onFailure() {
handler.cancel();
}
});
}
public class SslManager {
/**
* webview ssl校验
* @param url
*/
public static void webviewSsl(String url, final SslListener sslListener) {
if(!TextUtils.isEmpty(url)){
getOkHttpBuilder(url).build().newCall( new Request.Builder().url(url).build()).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
sslListener.onFailure();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
sslListener.onResponse();
}
});
}
}
public static OkHttpClient.Builder getOkHttpBuilder(String url){
OkHttpClient.Builder builder = null;
String crt;
if(!TextUtils.isEmpty(url)){
crt = "crt/crtname.crt";
try {
builder = setCertificates(new OkHttpClient.Builder(), IMApp.getAppContext().getResources().getAssets().open(crt));
} catch (IOException e) {
builder = new OkHttpClient.Builder();
}
}
return builder;
}
private static OkHttpClient.Builder setCertificates(OkHttpClient.Builder client, InputStream... certificates) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
keyStore.load(null);
int index = 0;
for (InputStream certificate : certificates) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
try {
if (certificate != null)
certificate.close();
} catch (IOException e) {
e.printStackTrace();
}
}
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory);
client.sslSocketFactory(sslSocketFactory, trustManager);
//hostName验证
client.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
String peerHost = session.getPeerHost();//服务器返回的域名
try {
X509Certificate[] peerCertificates = (X509Certificate[]) session.getPeerCertificates();
for (X509Certificate c : peerCertificates) {
X500Principal subjectX500Principal = c.getSubjectX500Principal();
String name = new X500p(subjectX500Principal).getName();
String[] split = name.split(",");
for (String s : split) {
if (s.startsWith("CN")) {
if (s.contains(hostname) && s.contains(peerHost)) {
return true;
}
}
}
}
} catch (SSLPeerUnverifiedException e) {
e.printStackTrace();
}
return false;
}
});
} catch (Exception e) {
e.printStackTrace();
}
return client;
}
}
WebChromeClient相关方法
1.onProgressChanged(WebView view, int newProgress)
这是webview加载网页的进度监听,常用作于添加浏览器的进度条。
//监听加载网页的进度情况
mywebview.setWebChromeClient(new WebChromeClient(){
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
}
2.onReceivedTitle(WebView view, String title)
//获取网页的标题
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
}
3.onReceivedIcon(WebView view, Bitmap icon)
//字面上的意思是:当前网页接收到icon的时候执行此方法
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
super.onReceivedIcon(view, icon);
}
4.onReceivedTouchIconUrl(WebView view, String url, boolean precomposed)
//字面上的意思是:触摸当前网页接收到icon的时候执行此方法
@Override
public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
super.onReceivedTouchIconUrl(view, url, precomposed);
}
5.onShowCustomView和onHideCustomView
这两个方法往往是一起使用,常用于解决webview不能全屏播放视频的问题。(有必要的话对当前activity开启硬件加速)
代码如下:
private View customView;//默认view
private FrameLayout fullscreenContainer;//全屏view
/** 视频全屏参数 */
private FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
private WebChromeClient.CustomViewCallback customViewCallback;
//显示自定义View:常用于视频全屏展示
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
if (customView != null) {
callback.onCustomViewHidden();
return;
}
//MainActivity.this.getWindow().getDecorView();
FrameLayout decor = (FrameLayout) getWindow().getDecorView();
fullscreenContainer = new FullscreenHolder(MainActivity.this);
fullscreenContainer.addView(view, COVER_SCREEN_PARAMS);
decor.addView(fullscreenContainer, COVER_SCREEN_PARAMS);
customView = view;
setStatusBarVisibility(false);
customViewCallback = callback;
}
//显示自定义View:常用于视频全屏展示
@Override
public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {
onShowCustomView(view, callback);
}
//隐藏自定义View:
@Override
public void onHideCustomView() {
hideCustomView();
}
//视频从全屏还原到原来
private void hideCustomView(){
if (customView == null) {
return;
}
setStatusBarVisibility(true);
FrameLayout decor = (FrameLayout) getWindow().getDecorView();
decor.removeView(fullscreenContainer);
fullscreenContainer = null;
customView = null;
customViewCallback.onCustomViewHidden();
mywebview.setVisibility(View.VISIBLE);
}
这里还需要注意的是,点击返回键应该退出全屏
@Override
public void onBackPressed() {
if(customView != null){
hideCustomView();
}else if(mywebview != null && mywebview.canGoBack()){
mywebview.goBack();
}else{
finish();
}
}
6.onCreateWindow和onCloseWindow
//请求主机应用创建一个新窗口。
//如果主机应用选择响应这个请求,则该方法返回true,并创建一个新的WebView,
//将其插入到视图系统中,并将其提供的resultMsg作为参数提供给新的WebView。
//如果主机应用选择不响应这个请求时,则该方法返回false。
//默认情况下,该方法不做任何处理并返回false。
@Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
}
//通知主机主机应用WebView关闭了,并在需要的时候从view系统中移除它。
//此时,WebCore已经停止窗口中的所有加载进度,并在javascript中移除了所有cross-scripting的功能。
@Override
public void onCloseWindow(WebView window) {
super.onCloseWindow(window);
}
7.onRequestFocus(WebView view)
@Override
public void onRequestFocus(WebView view) {
super.onRequestFocus(view);
}
不太清楚它的执行时机,其他博客的解释是:请求获得WebView的焦点,这可能由于另一个WebView打开一个连接,需要被展示.
8.三种提示框Alert、Confirm、Prompt
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return super.onJsPrompt(view, url, message, defaultValue, result);
}
常用的提示框是Alert和Confirm。
网上也是有相关博客 js中三种弹出框。
9.onJsBeforeUnload(WebView view, String url, String message, JsResult result)
//JS相关操作,会在页面关闭或刷新调用,触发事件时,弹出对话框是否关闭,确定则关闭页面,取消则保持该页面。
@Override
public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
return super.onJsBeforeUnload(view, url, message, result);
}
10.onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater)
//使webview支持建立html5本地缓存数据库
@Override
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater) {
quotaUpdater.updateQuota(estimatedDatabaseSize * 2);
}
11.onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater)
//已被废弃,不用管
@Override
public void onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater) {
super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
}
12.webview定位权限提示框
@Override
public void onGeolocationPermissionsShowPrompt(final String origin, final GeolocationPermissions.Callback callback) {
final boolean remember = false;//是否记住
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("位置信息");
builder.setMessage(origin + "允许获取您的地理位置信息吗?")
.setCancelable(true)
.setPositiveButton("允许", new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int id) {
callback.invoke(origin, true, remember);
} })
.setNegativeButton("不允许", new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int id) {
callback.invoke(origin, false, remember);
}
});
AlertDialog alert = builder.create();
alert.show();
}
@Override
public void onGeolocationPermissionsHidePrompt() {
super.onGeolocationPermissionsHidePrompt();
}
13.来自网页的权限请求
@Override
public void onPermissionRequest(PermissionRequest request) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
request.deny();//调用此方法以拒绝请求(默认)
request.getOrigin();//调用此方法以获取试图访问受限资源的网页的来源。
request.getResources();//调用此方法获取网页试图访问的资源。
request.grant(request.getResources());//调用此方法授予Origin访问给定资源的权限。
}
}
@Override
public void onPermissionRequestCanceled(PermissionRequest request) {
super.onPermissionRequestCanceled(request);
}
默认是拒绝所有请求,开发者可以根据需求授予指定网页的权限请求。
14.onJsTimeout()
//已过时,不用管
@Override
public boolean onJsTimeout() {
return super.onJsTimeout();
}
15.onConsoleMessage
//三个参数的方法已经废弃,被一个参数的方法替代
@Override
public void onConsoleMessage(String message, int lineNumber, String sourceID) {
super.onConsoleMessage(message, lineNumber, sourceID);
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
return super.onConsoleMessage(consoleMessage);
}
可以捕获到网页打印在控制台的日志,有利于日志的跟踪。
16.getDefaultVideoPoster()
//Html中,视频(video)控件在没有播放的时候将给用户展示一张“海报”图片(预览图)。
// 其预览图是由Html中video标签的poster属性来指定的。如果开发者没有设置poster属性, 则可以通过这个方法来设置默认的预览图。
@Override
public Bitmap getDefaultVideoPoster() {
Bitmap bitmap = super.getDefaultVideoPoster();
if(bitmap == null){
return BitmapFactory.decodeResource(getApplicationContext().getResources(), R.mipmap.ic_launcher);
}
return super.getDefaultVideoPoster();
}
17.getVideoLoadingProgressView()
//播放视频时,在第一帧呈现之前,需要花一定的时间来进行数据缓冲。
//ChromeClient可以使用这个函数来提供一个在数据缓冲时显示的视图。
// 例如,ChromeClient可以在缓冲时显示一个转轮动画。
@Override
public View getVideoLoadingProgressView() {
return super.getVideoLoadingProgressView();
}
18.getVisitedHistory(ValueCallback<String[]> callback)
//获得所有访问历史项目的列表,用于链接着色。
@Override
public void getVisitedHistory(ValueCallback<String[]> callback) {
super.getVisitedHistory(callback);
}
19.让webview支持inputfile控件
public ValueCallback<Uri> mUploadMessage;
public ValueCallback<Uri[]> mUploadMessageForAndroid5;
public final static int FILECHOOSER_RESULTCODE = 1;
public final static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;
//android3.0以前
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
}
//android3.0到android4.0
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
}
//android4.0到android4.3
public void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
mUploadMessage = filePathCallback;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
}
//android5.0以上
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
if (mUploadMessageForAndroid5 != null) {
mUploadMessageForAndroid5.onReceiveValue(null);
mUploadMessageForAndroid5 = null;
}
mUploadMessageForAndroid5 = filePathCallback;
Intent intent = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
intent = fileChooserParams.createIntent();
}
try {
startActivityForResult(intent, FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
} catch (ActivityNotFoundException e) {
mUploadMessageForAndroid5 = null;
return false;
}
return true;
}
});
@Override
protected void onActivityResult(int requestCode, int resultCode,Intent intent) {
if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(null);
mUploadMessage = null;
}
if (mUploadMessageForAndroid5 != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mUploadMessageForAndroid5.onReceiveValue(null);
mUploadMessageForAndroid5 = null;
}
}
if (requestCode == FILECHOOSER_RESULTCODE) {
if (null == mUploadMessage){
return;
}
Uri result = intent == null || resultCode != RESULT_OK ? null:intent.getData();
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
} else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5){
if (null == mUploadMessageForAndroid5){
return;
}
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;
}
}
Android有个很大的坑,添加以上代码几乎可以支持Android的所有版本了,唯独Android4.4.*不支持,这里推荐H5和客户端判断Android的版本,采用JS的方式支持inputfile。
webview之JS交互
- Android调用JS代码
(1)方法一:采用loadUrl方式
第一步:准备好html静态页面,写好JS方法
<!DOCTYPE html>
<html>
<head> <meta charset="utf-8">
<title>Carson_Ho</title> // JS代码
<script>
function callJS(){
alert("Android调用了JS的callJS方法");
}
</script>
</head>
</html>
第二步:加载该页面
mywebview.loadUrl("file:///android_asset/htmldemo.html");
第三步:点击某按钮,加载JS代码
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bt_js:
mywebview.loadUrl("javascript:callJS()");
break;
}
}
效果展示:
弊端:会刷新当前页面,执行效率慢。
(2)方法二:采用evaluateJavascript方式
其步骤和展示效果和方法一一样
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bt_js:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mywebview.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
}
});
}else {
mywebview.loadUrl("javascript:callJS()");
}
break;
}
}
优点:不会刷新当前页面
弊端:Andorid4.4之后才能使用(不过现在4.4之前的手机几乎已被淘汰)
- JS调用Android代码
方法一:采用addJavascriptInterface映射
步骤一:定义接口
public class AndoridToJsInterface {
@JavascriptInterface
public void hello(){
System.out.println("hello");
}
}
AndoridToJsInterface 类专门负责声明接口,接口方法必须添加“@JavascriptInterface”修饰,该注解将接口暴露给JS。其作用是为JS代码提供调用的接口。JS代码想要调用Android代码声明的接口,就必须给JS创建一个映射
//AndroidtoJS类对象映射到js的test对象
mywebview.addJavascriptInterface(new AndoridToJsInterface(), "android");
第一个参数是AndoridToJsInterface 对象,第二个参数就是AndoridToJsInterface对象的别名,也就是给JS使用的映射。
现在开始编写JS代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson</title>
<script>
function callAndroid(){
android.hello();
}
</script>
</head>
<body>
<button type="button" id="button1" onclick="callAndroid()">点击调用Android的hello方法</button>
</body>
</html>
优点:使用简单
缺点:注解使Android的接口暴露出来,是一个高危漏洞
方法二:采用shouldOverrideUrlLoading拦截URl
编写好JS代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
<script>
function callAndroid(){
//事先预定的URI
document.location = "android://webview?name=zhangsan&age=22";
}
</script>
</head>
<body>
<button type="button" id="button1" onclick="callAndroid()">点击调用Android代码</button>
</body>
</html>
其中"android://webview?name=zhangsan&age=22"是事先预定好的格式,然后在点击网页上的按钮触发JS代码,并在Android端捕获
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
if(uri.getScheme().equals("android") && uri.getAuthority().equals("webview")){
Set<String> paraNames = uri.getQueryParameterNames();
Iterator it = paraNames.iterator();
while (it.hasNext()){
String name = (String) it.next();
Log.d("aaa", "name:"+name);
}
}
return super.shouldOverrideUrlLoading(view,url);
}
优点:没有方法一的安全漏洞
缺点:使用复杂,并且将所有的JS处理都放在shouldOverrideUrlLoading中略显臃肿
方法三:采用onJsAlert、onJsConfirm、onJsPrompt捕获URL
和方法二原理类似,这里就不在描述
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return super.onJsPrompt(view, url, message, defaultValue, result);
}
优点:没有方法一的安全漏洞
缺点:使用复杂,并且将所有的JS处理都放在onJsAlert、onJsConfirm、onJsPrompt中略显臃肿。
JS交互总结:
- Android调用JS代码推荐使用evaluateJavascript,他的好处显而易见,既不会重新刷新页面,也可以获取返回值
- 三种JS调用Android代码的方法都不推荐使用,addJavascriptInterface映射的方式是一个高危漏洞,其他两种使用太过复杂,这里推荐使用JsBridge实现JS交互。
最后分享一个github上开源的JsBridge案例: