前言
现如今,大部分APP都是原生+h5形式的,原生的流畅+h5的内容多样性,使得APP内容丰富多彩,因此android开发中,常会遇到与H5交互的形式,其原理说白了无非是:原生方法和JS方法的互调,该文主要简单谈谈通用web展示界面的封装,APP内需要用到h5的地方直接用它就行了。
常用的交互场景
有哪些常用交互场景呢?这里简单做了个总结,可能会有不全面的地方。
场景名称 | 场景介绍 | js->原生 | 原生->js |
---|---|---|---|
标题内容以及隐藏和展示 | 有的界面需要原生标题 有的界面需要展示web端的标题 |
● | ○ |
标题栏和状态栏颜色修改 | 不同web界面可能有不同的主题效果 可以参考《京东金融》app |
● | ○ |
社交分享 | 带弹窗的 直接分享的(web提供弹窗) |
● | ○ |
跳转内部 | 如跳转到登陆界面 | ● | ○ |
跳转外部 | 如选择用什么地图打开 | ● | ○ |
类微信更多弹窗 | 刷新、复制链接、浏览器打开等 | ● | ○ |
控制更多弹窗的内容数量 | a界面只有分享 b界面有浏览器打开 |
● | ○ |
地图转换 | 点击web地图模块跳转到原生地图界面 | ● | ○ |
支付 | 调用原生微信、支付宝支付 | ● | ○ |
图片预览 | 调用原生图片预览控件效果更佳 | ● | ○ |
返回机制 | 该情况只有在隐藏原生标题栏时用得到 | ● | ○ |
隐藏底部导航栏 | 如首页fragment内容点击详情时 需要隐藏底部的tab,防止遮挡 |
● | ○ |
拨号、系统弹窗 | 很常见的场景 | ● | ○ |
文件操作 | 上传图片、文件等 | ● | ○ |
同步登陆信息 | 原生登陆登出,web登陆状态变更 | ○ | ● |
同步定位信息 | h5直接用原生定位信息 | ○ | ● |
... | ... | ... | ... |
准备工作
既然是js和原生的交互,webview要设置一番才行。
WebSettings webSettings = webView.getSettings();
//支持javascript
webSettings.setJavaScriptEnabled(true);
//将图片调整到适合webview的大小
webSettings.setUseWideViewPort(true);
// 缩放至屏幕的大小
webSettings.setLoadWithOverviewMode(true);
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
//缩放操作
//支持缩放,默认为true。是下面那个的前提。
webSettings.setSupportZoom(true);
//设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.setBuiltInZoomControls(true);
//隐藏原生的缩放控件
webSettings.setDisplayZoomControls(false);
// 文件操作
webSettings.setAllowFileAccess(true);
//启用数据库
webSettings.setDatabaseEnabled(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//不设置可能会导致js调用失败
webSettings.setDomStorageEnabled(true);
//加载速度优化:先加载文字后加载图片
webSettings.setBlockNetworkImage(true);
webSettings.setLoadsImagesAutomatically(true);
//设置UA,让浏览器知道使用者设备
String ua = webSettings.getUserAgentString();
if (!ua.contains(YYNATIVE)) {
webSettings.setUserAgentString(webSettings.getUserAgentString() + "; " + YYNATIVE);
}
webSettings.setAppCacheEnabled(true);
webSettings.setAppCacheMaxSize(1024 * 1024 * 8);
webSettings.setAppCachePath(getApplication().getCacheDir().getAbsolutePath());
//向web端注入对象
webView.addJavascriptInterface(this, YYNATIVE);
到此,webview的参数配置完了,需要注意的是webSettings.setJavaScriptEnabled(true);
和webSettings.setUserAgentString(webSettings.getUserAgentString() + "; " + YYNATIVE);
以及webView.addJavascriptInterface(this, YYNATIVE);
这三行代码是必不可少的。第一行,设置js支持,不设置js不鸟你;第二行,设置UA标识,不设置web端不知道这是android还是ios或者微信web端;第三行不注入对象,web端也是无法唤醒Java方法的。
实现场景案例
js调用android
标题栏的隐藏和展示
android端代码方法的命名需要和web端约定,web端调用方式是:window.约定对象名.方法名(参数)
如window.yynative.setTopVisiable(true)
:
/**
* 设置标题
*
* @param title
*/
@JavascriptInterface
public void setTopTitle(String title) {
AppUtils.runOnUIThread(() -> tvTitle.setText(title));
}
/**
* 设置标题栏可见与否
*
* @param visiable
*/
@JavascriptInterface
public void setTopVisiable(boolean visiable) {
AppUtils.runOnUIThread(() -> llTop.setVisibility(visiable ? View.VISIBLE : View.GONE));
}
注意:@JavascriptInterface
该注解必不可少,否则js会提示找不到方法,另外UI操作需要在UI线程里进行。
标题栏和状态栏颜色修改
/**
* 设置标题背景色
*
* @param
*/
@JavascriptInterface
public void setTopBgColor(String color) {
AppUtils.runOnUIThread(() -> llTop.setBackgroundColor(Color.parseColor(color)));
....//修改标题字体颜色
....//修改icon
}
/**
* 设置状态栏背景色
*
* @param
*/
@JavascriptInterface
public void setStatusBgColor(String color) {
AppUtils.runOnUIThread(() ->StatusBarUtils.setColor(Color.parseColor(color)));
}
注意:状态栏的颜色修改,避免状态栏字体看不清;标题栏颜色修改时,要防止背景色和图标、字体颜色重复。
以上2种场景为大多数js调用android的模式,还有一些是不需要我们自己定义方法,系统帮给我们方法或者另辟蹊径,需要我们用到WebViewClient
和WebChromeClient
,他们的区别是:
- WebViewClient:在影响【View】的事件到来时,会通过WebViewClient中的方法回调通知用户,主要帮助WebView处理各种通知、请求事件的;
- WebChromeClient:当影响【浏览器】的事件到来时,就会通过WebChromeClient中的方法回调通知用法,主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度,文件处理等。
拨号处理
使用WebViewClient
实现shouldOverrideUrlLoading
方法
@Override
public boolean shouldOverrideUrlLoading(WebView webView, String s) {
//拨号处理
if (s.startsWith(TEL)) {
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(s));
startActivity(intent);
return true;
}
webView.loadUrl(s);
return true;
}
js系统弹窗
使用WebChromeClient
实现onJsAlert
、onJsConfirm
、onJsPrompt
方法,这里给出onJsAlert
的实现代码:
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
// return super.onJsAlert(view, url, message, result);
final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
builder.setTitle("温馨提示")
.setMessage(message)
.setPositiveButton("确定", null);
// 不需要绑定按键事件
// 屏蔽keycode等于84之类的按键
builder.setOnKeyListener((dialog, keyCode, event) -> true);
// 禁止响应按back键的事件
builder.setCancelable(false);
AlertDialog dialog = builder.create();
dialog.show();
result.confirm();// 因为没有绑定事件,需要强行confirm,否则页面会变黑显示不了内容。
return true;
}
文件处理
同样,用到WebChromeClient
,实现onShowFileChooser
方法(android5.0+,android4.1.1+实现openFileChooser
),然后自己实现文件处理逻辑。
//回调的接口
public interface OpenFileChooserCallBack {
void openFileChooserCallBack(ValueCallback uploadMsg, String acceptType);
void openFileChooser5CallBack(WebView webView, ValueCallback valueCallback,
FileChooserParams fileChooserParams);
}
//针对 Android 5.0+
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback valueCallback,
FileChooserParams fileChooserParams) {
mOpenFileChooserCallBack.openFileChooser5CallBack(webView, valueCallback, fileChooserParams);
return true;
}
//在实现里做自己的逻辑
@Override
public void openFileChooser5CallBack(WebView webView, ValueCallback valueCallback, WebChromeClient.FileChooserParams fileChooserParams) {
mValueCallback = valueCallback;
//文件处理函数
showOptions();
}
使用WebChromeClient
还可以处理加载进度,视频横竖屏切换等,这里不做详细介绍。
android调用js
同步信息
假设需要同步的信息是用户的token
,token
有效视为已登陆,失效需要重新登陆。一定要区分清楚h5什么时候需要你给的参数,h5的内容需要登陆后才能展示,那token
传递需要在webview加载时传给h5,不能在WebViewClient
中的onPageFinished
方法中传递,我们需要在加载时调用,应该在onLoadResource
中传递,反之在onPageFinished
中调用;同理,定位信息也是。
@Override
public void onLoadResource(WebView webView, String s) {
if (mFirstLoad) {
String token = getUserIdAuthKey();
HashMap cityMap = new HashMap<>();
cityMap.put("city", CityBean.city);
cityMap.put("lat", CityBean.Latitude + "");
cityMap.put("lon", CityBean.Longitude + "");
String cityInfo = JsonUtils.serialize(cityMap);
webView.evaluateJavascript("javascript:window.getUserIdFromApp(" + "\"" + token + "\"" + ")", new ValueCallback() {
@Override
public void onReceiveValue(String value) {
}
});
webView.evaluateJavascript("javascript:window.getlocationInfoFromApp(" + cityInfo + ")", new ValueCallback() {
@Override
public void onReceiveValue(String value) {
}
});
}
super.onLoadResource(webView, s);
}
注意:evaluateJavascript
在android4.4后才能使用,在此之前可以使用webview.loadUrl
方法。
总结
android和h5的交互场景大致就是这些,很多场景是类似的,因此不做过多分析,可以举一反三,只是其中有些细节需要大家注意,比如:
- 支付结束后需要把支付结果和状态回调给h5,即android调js,让h5来操作后续;
- 又如图片预览,如果传过来的是多组图片,h5还需要传递用户点击的图片索引给webview,我们预览时可以显示当前图片所在位置。
- 通过h5跳转登陆成功后,要记得立马同步登陆信息给h5,否则会继续跳转登陆
....
....
希望这篇博客能给新手少走弯路,如有错误之处还望指正!