在移动端页面中,由于屏幕空间有限,导航条扮演着非常重要的角色,提供了快速导航到不同页面或功能的方式。用户也通常会在导航条中寻找他们感兴趣的内容,因此导航条的曝光率较高。在这样的背景下,提供一个动态灵活的导航条,为产品赋能,变得尤其重要。
拿iOS原生导航条为例,导航条作为页面进出栈的根视图连接器,以及生命周期的管理器。尤其是在作为webView Controller的父容器的时候,面对webview中h5页面灵活的的路由属性,以及一些难料的异常情况,原生很难也不便于频繁操作根试图容器,因此也产生了一些性能差、体验差、开发成本高、测试场景难覆盖等问题。安卓也有类似情况。
?ssr预渲染时,无法对原生导航条进行预加载。对于百亿,便宜包邮等使用ssr预渲染的频道,因为原生导航栏无法进行预加载,导致上屏较慢等问题。
?原生导航条生命周期耦合。原生导航条作为webviewController的根容器,一旦操作时机不当,很可能影响到线上页面,而且最大的问题在于这种场景测试很难覆盖。比如:window.href.url使用这种方式更新当前页面时,由于不同频道操作同一根导航条,会引发不可预知的问题;
?场景有限。站外场景无法使用原生导航条,一些业务方往往需要单独处理站内外,造成开发资源浪费。
?webview初始化时会预置一个默认的导航条,然后根据前端配置,再去设置导航条的不同样式,无法避免的存在一个过渡期,体验较差。
?window.location.reload()刷新当前页面的时候,即便是在js中隐藏了导航条,webview为了兼容一个线上问题,执行reload时此时会先展示原生导航条,直到执行了js的隐藏逻辑,才会被隐藏,体验较差。
?无法扩展交互动效。得益于移动端页面中,导航条得天独厚的位置,产品往往希望有更生动的交互性,来提高曝光、粘性、活动触达率等。比如导航栏上挂载搜索框、以及吸顶、延伸动画、沉浸式、炫酷的营销icon等等。遗憾的是原生系统导航条不能全部支持,其实无论从视图层级上来说,还是从导航条职责上来说,apple并不希望过多操作导航栏上的元素。也就造成了高曝光位置的资源浪费。
?因为要依赖原生JS桥,就一定会存在版本限制问题。造成需求迭代慢,甚至随着时间的推移,版本卡口原因无迹可寻,代码调整战战兢兢,版本审核慢、周期长等问题。
基于原生导航条现状,百亿补贴频道沉淀出了通用H5导航条组件@pango/navigation-bar,具有以下优势:
?支持ssr预渲染,上屏较快。
?人力节省百分之90%以上,以plus 95折为例,对接只需0.5/人日。
?无场景限制。可用于站内外,ssr以及csr场景,无需站内外多次开发。
?可配置。 @pango/navigation-bar使用config的形式配置item,这么做的好处是一旦业务需求改动,只需调整配置,无需调整组件逻辑,极大降低开发和测试成本。另外如果你使用主站的webview并且配置了config,那么只需要简单的改动config,代码迁移成本低。
?该组件发布在JNPM上,大小只有4.1K,接入简单。
?导航条在频道内和其他普通楼层无异,生命周期隔离清晰,不会影响别的页面,测试成本低。
?单向数据流设计,外部数据变化,组件UI及时响应,不存在原生的操作窗口问题,开发体验佳;
?生命周期和其他楼层保持同步,规避了原生容器和H5页面天然的生命周期无法同步的问题,也就不存在两者之间的过渡问题,体验佳。
?采用左、中、右、状态栏、导航栏分层设计的模式,支持传入React.ReactElement,比原生定制性更强,可灵活定制目前站内绝大部分导航条样式以及交互动画,合理高效利用导航条资源;
?参考原生导航栏异形屏适配方案,参考原生绝对布局思路,完美适配折叠屏、异形屏。
?iOS9 - 最新 、Android5 - 最新均兼容性良好,未发现线上兼容异常。
纯手工打造,未使用第三方库,不会对宿主造成依赖冲突,随时改动随时发布不存在版本控制,最大程度的降低和隔断对原生容器的版本依赖。
原生导航条作为根试图容器,容器内子视图异常不会影响根试图的展示,所以不用特殊处理html下载失败,js执行异常,服务挂掉等异常情况。但是H5导航条遇到这些异常情况,也要保证用户可以点击返回按钮返回上一页。
目前方案已和通天塔以及hybrid团队打通,方案如下:
异常场景1:业务js执行异常。
? @pango/navigation-bar组件使用a标签渲染返回按钮,保证js执行异常时依然展示返回按钮,并且能正常响应返回事件。
?业务展示兜底错误页时,会使用导航条兜底数据渲染导航条确保可返回上一级。
异常场景2:webview加载html失败。
为了消除上面提到的过渡问题,业务链接中新增了qurey参数hideNavi=1 ,原生webview会通过该字段在webview出现之前隐藏导航条。但是因此也引发了一个风险:html加载失败时,会造成无头的问题。因此需要webview配合改造,一旦监测到html加载失败,原生webview要展示原生导航条。
异常场景3:通天塔服务异常。
同样是场景2中的问题,需要通天塔配合改造通天塔服务异常的场景:依据链接中hideNavi字段添加返回按钮或者通知webview展示默认导航条。
观察多个竞品以及兄弟频道,发现在上述的异常场景2、3下,均未做特别处理,展示无头错误页。如果此时原生禁用了右滑返回手势,页面将无法返回上一级,这无异是一个非常严重的缺陷(事实上有些竞品页面以及我们某些频道确实无法返回上一级)。
目前使用该组件的项目:百亿补贴、月黑风高、PLUS95折。
参考原生navigationBar的设计思路,把整个导航栏分为左、右、中三个区域,左、右区域根据内容自适应宽度,剩余空间为中间区域。左右区域接受items数组,可根据item接口协议设置左右的items,协议可自定义图片、尺寸、事件、间距、下拉菜单、是否动画响应等,已默认包含了关注、返回、更多、频道logo等常用元素,当然如有需要也可以自定义一个React.ReactElement。中间区域只接受React.ReactElement,你可以自由定制元素,传入navigation-bar即可,一张图片一段文字,或者是一个搜索框……不管是伸缩或者是上滑吸顶,都可自定义。
该组件发布在JNPM上,使用react + ts开发,大小只有4.1K。
文件结构:
npm i @pango/navigation-bar --registry=http://registry.m.jd.com
你可以自由配置items除了"follow", "more","back","logo",这些已知的元素外还可以设置type:"common",是一个通用类型的item;
scrollCallBack会回调上滑比例,可根据该比例做交互动画;
import {
BACK_ICON,
FEEDBACK_ICON,
FEEDBACK_URL,
INavigationParams,
MORE_ICON,
RULE_ICON,
SHARE_ICON,
} from "@pango/navigation-bar";
setH5NavigationButton=(headerData)=> {
const extend=headerData?.navigationBar?.extend;
const followInfo=headerData?.navigationBar?.followInfo;
const follow={
type: "follow",
collectionId: String(followInfo?.themeId),
gapWidth: 12,
width: 55,
height: 22,
};
const moreItem={
type: "more",
menuBackgroundColor: "white",
img: MORE_ICON,
title: "更多",
menuList: [],
};
moreItem.menuList.push({
icon: RULE_ICON,
title: "规则页",
menuEventData: extend?.guideUrl,
});
moreItem.menuList.push({
icon: SHARE_ICON,
title: "分享",
type: "share",
menuEventData: extend?.share,
});
const backItem={
type: "back",
img: BACK_ICON,
canClick: !margicWindow,
title: "返回",
};
const backLogo={
type: "logo",
img: DEFAULT_LOGO,
isAnimation: true,
gapWidth: 5,
width: 176,
height: 34
};
const navBarParams: INavigationParams={
leftItems: [],
rightItems: [],
backgroundColor: "#FD4D00",
navHeight: this.status.navHeight,
};
navBarParams.leftItems.push(backItem, backLogo);
navBarParams.rightItems.push(moreItem, follow);
navBarParams.titleImgItem=TitleSearch({});
navBarParams.scrollCallBack=(scale)=> {
this.setStatus({
navigationBarParams: Object.assign(this.status.navigationBarParams, {
titleImgItem: TitleSearch({ isCollapse: scale===1 })
})
});
}
return navBarParams;
};
特别注意titleImgItem,这个属性是导航条中间区域的展示内容,TitleSearch是百亿补贴的搜索框,你可以参考该元素自定义中间区域。
?title-search-view.tsx?
?title-search-view.scss?
import { INavigationParams, NavigationBar } from "@pango/navigation-bar";
import "@pango/navigation-bar/lib/navigation-bar.scss";
css
.nav-bar {
width: 750px;
z-index: 1;
top: 0px;
}
<NavigationBar
className="nav-bar"
params={data.navParams}
barHeight={200} //自定义导航栏高度
event={do somethings}
/>
Q:若原生导航条隐藏,此时异常怎么办?
异常分为以下3类:
异常场景1:业务js执行异常。
? @pango/navigation-bar组件使用a标签渲染返回按钮,保证js执行异常时依然展示该标签,并且能正常相应出栈事件。
?业务展示兜底错误页时,会使用导航条兜底数据渲染导航条。
异常场景2:webview加载html失败。
为了消除上面提到的过渡问题,业务链接中新增了qurey参数hideNavi=1 ,原生webview会通过该字段在webview出现之前隐藏导航条。但是因此也引发了一个风险:html加载失败时,会造成无头的问题。因此需要webview配合改造,一旦监测到html加载失败,原生webview要展示原生导航条。
异常场景3:通天塔服务异常。
同样是场景2中的问题,需要通天塔配合改造通天塔服务异常的场景:依据链接中hideNavi字段添加返回按钮或者通知webview展示默认导航条。
若发现其他异常,麻烦提醒我。
Q:折叠屏怎么适配?
折叠屏适配一直是前端适配的噩梦,噩梦的根本原因在于:宽度于高度的比例非常值,前端布局是往往会把px转换成vw,因此造成了异形屏适配难的问题。
?参考原生系统导航栏的绝对布局方案:@pango/navigation-bar把导航条拆分为状态栏和导航栏上下两部分, 导航条宽度屏幕自适应,导航条高度跟随设备变化,并采用大写的PX单位来固定元素尺寸。根据协议item宽高、间距仍可自定义,但是大写的PX保证了item不会随着屏幕宽度而异常变化。
navigation-bar {
width: 750px; // 会转换成vw
height: 44PX; // 不会转换成vw
display: flex;
position: absolute;
.left-items-bg {
margin-left: 16PX; // 不会转换成vw
height: 22PX;
margin-top: 11PX;
width: fit-content;
display: flex;
align-items: center;
justify-content: center;
}
}
Q:原生导航条优化?
现状中的几个异常场景,仍需要webview配合一起整改,所以目前整改方案为:
业务链接中新增qurey参数hideNavi=1,此时 webview通过该字段在webview 出现之前隐藏导航条。由webview负责整改,跟版12.1.4。
经安全部门审核之后,会向外开源。
?导航条在移动端页面中的重要性无需多言,我们最终的目的是面向全集团,和通天塔以及hybrid团队,一起打造一根规范通用的H5导航栏,如果你在使用过程中发现一些我们没有考虑到的异常场景或者设计规范,请与我联系,我们共同完善。
?目前该组件下拉刷新还是要依赖原生的下拉刷新事件,后期会定制H5自己的下拉刷新。
?一个规范的UI组件应该是一个有严格UI设计规范的,比如间距,字体大小、图片规范等。但是一期的设计中我们为了灵活,通过协议把UI把控留给了用户,也希望后面的迭代开发中融入更多规范的设计语言。
一个轻量级的tabs菜单组件,支持一二级菜单,联动切场等功能,常用于商品分类展示。
作者:京东零售 张松超
来源:京东云开发者社区 转载请注明来源
根据Google公布的Android 各个系统版本市场占有率(Google Android dashboards), Android 4.0及其以上系统将近90%左右,发展趋势必将是未来市面上几乎是Android 4.0以上系统。本文主要关注Android 4.0及以上系统WebView的实现,从Android WebView实现的Framework层大致可以分为三段Android 4.0系列,Android 4.1---4.3系列,Android 4.4及其以上系列。
WebView差异
WebView是Android系统提供能显示网页的系统控件,它是一个特殊的View,同时它也是一个ViewGroup可以有很多其他子View。在Android 4.4以下(不包含4.4)系统WebView底层实现是采用WebKit(http://www.webkit.org/)内核,而在Android 4.4及其以上Google 采用了chromium(http://www.chromium.org/)作为系统WebView的底层内核支持。在这一变化中Android 提供的WebView相关API并没有发生大变化,在4.4上也兼容低版本的API并且引进了少部分API。这里简单介绍下基于Chromium 的Webview和基于Webkit webview的差异,基于Chromium Webview提供更广的HTML5,CSS3,Javascript支持,在目前最新Android 系统版本5.0上基于chromium 37,Webview提供绝大多数的HTML5特性支持。Webkit JavaScript引起采用WebCore Javascript 在Android 4.4上换成了V8能直接提升JavaScript性能。另外Chromium 支持远程调试(Chrome DevTools)。
WebKit for WebView VS Chromium for WebView性能比对(测试环境 小米2. CM Browser. Android 4.1.1 VS 4.4.3)
Webkit for Webview | Chromium for Webview | 备注 | |
HTML5 | 278 | 434 | http://html5test.com/ |
远程调试 | 不支持 | 支持 | Android 4.4及以上支持 |
内存占用 | 小 | 大 | 相差20-30M左右 |
WebAudio | 不支持 | 支持 | Android 5.0及以上支持 |
WebGL | 不支持 | 支持 | Android 5.0及以上支持 |
WebRTC | 不支持 | 支持 | Android 5.0及以上支持 |
Android 4.0 WebView结构
Android WebView API层主要提供给我们应用程序的接口,为了兼容向下版本Android在高版本中也是对这一层的API进行支持,因此如果底层发生变化,这些API接口层也不会发生太大变化。Android 平台不仅提供应用层编程接口也提供native层编程。下面介绍上图中的三个部分:
1)Android Framework:Android WebView是个特殊控件实现的支持需要Framework的代码主要在./frameworks/base/core/java/android/webkit目录下,在Android 4.0实现主要是在WebViewCore.java,BrowserFrame.java等文件。
2) Android JNI:需要有Native代码支持,因此需要有JNI层实现,Android WebView 4.0的JNI层实现WebView相关代码在./external/webkit/Source/WebKit/android/jni/目录下,这一层起到承上启下的作用,链接Framework层以及WebKit层的桥梁,比如相关的一些实现在WebviewCore.cpp,WebCoreFrameBridge.cpp等。
3) WebKit: WebKit内核,其核心主要是解析W3C标准以及渲染排版网页,他是一个跨平台的内核引擎,那么需要支持各个平台,需要我们的平台实现层,在Android 4.0系统这一部分相关代码主要在./external/webkit/Source/WebKit/android/WebCoreSupport/目录下,比如FrameLoaderClientAndroid.cpp,ChromeClientAndroid.cpp,这一层负责WebCore与系统平台的桥接,具体在不同平台会有不同的实现。实现网页的解析排版及渲染由WebCore来实现在Android 4.0源码当中代码位于./external/webkit/Source/WebCore/下,下面有WebCore实现的各个模块功能支持的相关代码,比如页面视图部分在page目录的chrome.cpp,比如加载页面需要的资源的loader中得FrameLoader.cpp等,这里不在继续深入详解,有兴趣的朋友可以下载Android 4.0源码阅读。
Android 4.1--4.3 WebView结构
Android 4.1--4.3版本WebView内核实现还是基于WebKit,但在WebView的Framework层发生了变化,引入了工厂模式,目地是为了将内核与上层API接口分离开来,分离的意义不仅仅是抽象接口,更重要的是将来能替换内核部分的实现。 在4.1--4.3这一系列版本native结构基本与4.0版本相同,下图呈现新的变化:
Android 4.0--4.3 渲染
尽管之前4.0,与4.1--4.3是在不同的结构系列,其两者之间的差异主要是集中的Framework上的变化,这种变化更多体现在Framework层结构上的变化,WebKit内核极其在Android上的表现机制并没有发生很大变化,他们的渲染机制是相同的。下面介绍Android 4.0--4.3的渲染机制:
在Android 4.0上已经默认开启硬件加速,因此WebView的渲染默认是基于硬件渲染的,通过本人分析其在WebView被隐藏的那一帧是采用软件渲染,目的是减少硬件占用,让其他UI能及时的响应。在硬件渲染情况下WebView通过onDraw方法传递Canvas 并将其转行为HardwareCanvas ,并生成native的 DrawGLFunction指针,通知native做渲染。在软件模式下,WebView通过传递的Canvas 通知内核webkitDraw将内核的一帧生成picture传输到Canvas中,执行Canvas draw bitmap。
Android 4.4 WebView结构
在Android 4.4系统上 Google已经将系统默认的Webkit内核替换成自己的开源项目chromium,通过之前的版本分析,我们可以看到Android 对WebView的Framework 结构进行调整使其更抽象,更重要的目的还是集成自己的开源chromium。下面我们来看看WebView的结构发生了什么样的变化:
目录:
./frameworks/base/core/java/android/webkit
./frameworks/webview/chromium/java/com/android/webview/chromium
./external/chromium_org/android_webview
./external/chromium_org/content
为了将chromium项目集成到Android 中,chromium项目抽象出Android webview这一层,之前的接口抽离这时候已经变得很明显,Android Webview基于chromium content API这一层,第三方浏览器厂商也可以采用这种方式,目前所了解的厂商有Opera使用这种方式。Android 4.4WebView的渲染核心目前也没有发生太大变化,还是基于WebView的Canvas,将Chromium composit 结构绘制到WebView Canvas上。接入chromium内核,WebView浏览性能大幅度提升,但是和chrome for Android还是有些不同,主要体现在一下几点:
1. chrome浏览器是多进程架构,Chromium for Android Webview 是单进程架构。
2. chrome浏览器 内存占用比 Android WebView大的多。
3. chrome支持更多的HTML5 feature。
Android WebView展望:
Chromium项目编译"android_webview_apk“ 目前实现是基于Android SurfaceView,其渲染性能高于Android WebView的Canvas,历史遗留问题以及Android 系统WebView的作用特点,这一块随着Chromium 和 Android项目的整合,相信值得大家期待将来的Android WebView 的渲染性能会再次大幅提升。
本博客会持续更新Android WebView后续版本的变化,敬请关注 谢谢!
Android 5.0 Lollipop WebView
Lollipop版本中WebView的内核实现采用Chromium 37版本,这个版本带来更多的安全性和稳定性。这个版本解决Android 4.4版本网页当中请求访问打开本地文件选择器问题,引入新的回调接口,onShowFileChooser方法,需要此功能的可以在5.0上接上这个回调接口,并实现功能。另外这个版本提供安全许可给用户选择,当网页需要访问特殊资源时,会通知我们的应用程序,请求允许,回调接口为onPermissionRequest。之前我们也提到这个版本使得WebView默认支持WebAudio,WebGL,WebRTC等标准。
另外Google Android 还将webview做为一个能动态更新的app,能不更新Android版本情况下,更新WebView内核。Android 5.0 Webview默认提供减少内存占用支持,并且智能选择需要绘制的HTML document部门来提供性能。 当然开发者可以在自己应用程序需要时关闭这个选项(enableSlowWholeDocumentDraw)。
阅读本文前请先阅读:Android开发:最全面、最易懂的Webview详解
Android与JS通过WebView互相调用方法,实际上是:
二者沟通的桥梁是WebView
对于Android调用JS代码的方法有2种:
1. 通过WebView的loadUrl()
2. 通过WebView的evaluateJavascript()
对于JS调用Android代码的方法有3种:
1. 通过WebView的addJavascriptInterface()进行对象映射
2. 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url
3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息
2.1 Android通过WebView调用 JS 代码
对于Android调用JS代码的方法有2种:
1. 通过WebView的loadUrl()
2. 通过WebView的evaluateJavascript()
方式1:通过WebView的loadUrl()
步骤1:将需要调用的JS代码以.html格式放到src/main/assets文件夹里
需要加载JS代码:javascript.html
// 文本名:javascript
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
// JS代码
<script>
// Android需要调用的方法
function callJS(){
alert("Android调用了JS的callJS方法");
}
</script>
</head>
</html>
步骤2:在Android里通过WebView设置调用JS代码
Android代码:MainActivity.java
注释已经非常清楚
public class MainActivity extends AppCompatActivity {
WebView mWebView;
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView=(WebView) findViewById(R.id.webview);
WebSettings webSettings=mWebView.getSettings();
// 设置与Js交互的权限
webSettings.setJavaScriptEnabled(true);
// 设置允许JS弹窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// 先载入JS代码
// 格式规定为:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/javascript.html");
button=(Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 通过Handler发送消息
mWebView.post(new Runnable() {
@Override
public void run() {
// 注意调用的JS方法名要对应上
// 调用javascript的callJS()方法
mWebView.loadUrl("javascript:callJS()");
}
});
}
});
// 由于设置了弹窗检验调用结果,所以需要支持js对话框
// webview只是载体,内容的渲染需要使用webviewChromClient类去实现
// 通过设置WebChromeClient对象处理JavaScript的对话框
//设置响应js 的Alert()函数
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder b=new AlertDialog.Builder(MainActivity.this);
b.setTitle("Alert");
b.setMessage(message);
b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
b.setCancelable(false);
b.create().show();
return true;
}
});
}
}
特别注意:JS代码调用一定要在 onPageFinished() 回调之后才能调用,否则不会调用。
onPageFinished()属于WebViewClient类的方法,主要在页面加载结束时调用
// 只需要将第一种方法的loadUrl()换成下面该方法即可
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此处为 js 返回的结果
}
});
}
2.1.2 方法对比
2.1.3 使用建议
两种方法混合使用,即Android 4.4以下使用方法1,Android 4.4以上方法2
// Android版本变量
final int version=Build.VERSION.SDK_INT;
// 因为该方法在 Android 4.4 版本才可使用,所以使用时需进行版本判断
if (version < 18) {
mWebView.loadUrl("javascript:callJS()");
} else {
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() 消息
2.2.1 方法分析
方式1:通过 WebView的addJavascriptInterface()进行对象映射
步骤1:定义一个与JS对象映射关系的Android类:AndroidtoJs
AndroidtoJs.java(注释已经非常清楚)
// 继承自Object类
public class AndroidtoJs extends Object {
// 定义JS需要调用的方法
// 被JS调用的方法必须加入@JavascriptInterface注解
@JavascriptInterface
public void hello(String msg) {
System.out.println("JS调用了Android的hello方法");
}
}
步骤2:将需要调用的JS代码以.html格式放到src/main/assets文件夹里
需要加载JS代码:javascript.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson</title>
<script>
function callAndroid(){
// 由于对象映射,所以调用test对象等于调用Android映射的对象
test.hello("js调用了android中的hello方法");
}
</script>
</head>
<body>
//点击按钮则调用callAndroid函数
<button type="button" id="button1" onclick="callAndroid()"></button>
</body>
</html>
步骤3:在Android里通过WebView设置Android类与JS代码的映射
详细请看注释
public class MainActivity extends AppCompatActivity {
WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView=(WebView) findViewById(R.id.webview);
WebSettings webSettings=mWebView.getSettings();
// 设置与Js交互的权限
webSettings.setJavaScriptEnabled(true);
// 通过addJavascriptInterface()将Java对象映射到JS对象
//参数1:Javascript对象名
//参数2:Java对象名
mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS类对象映射到js的test对象
// 加载JS代码
// 格式规定为:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/javascript.html");
仅将Android对象和JS对象映射即可
即JS需要调用Android的方法
以.html格式放到src/main/assets文件夹里
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
<script>
function callAndroid(){
/*约定的url协议为:js://webview?arg1=111&arg2=222*/
document.location="js://webview?arg1=111&arg2=222";
}
</script>
</head>
<!-- 点击按钮则调用callAndroid()方法 -->
<body>
<button type="button" id="button1" onclick="callAndroid()">点击调用Android代码</button>
</body>
</html>
当该JS通过Android的mWebView.loadUrl("file:///android_asset/javascript.html")加载后,就会回调shouldOverrideUrlLoading (),接下来继续看步骤2:
步骤2:在Android通过WebViewClient复写shouldOverrideUrlLoading ()
MainActivity.java
public class MainActivity extends AppCompatActivity {
WebView mWebView;
// Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView=(WebView) findViewById(R.id.webview);
WebSettings webSettings=mWebView.getSettings();
// 设置与Js交互的权限
webSettings.setJavaScriptEnabled(true);
// 设置允许JS弹窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// 步骤1:加载JS代码
// 格式规定为:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/javascript.html");
// 复写WebViewClient类的shouldOverrideUrlLoading方法
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 步骤2:根据协议的参数,判断是否是所需要的url
// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
//假定传入进来的 url="js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)
Uri uri=Uri.parse(url);
// 如果url的协议=预先约定的 js 协议
// 就解析往下解析参数
if ( uri.getScheme().equals("js")) {
// 如果 authority=预先约定协议里的 webview,即代表都符合约定的协议
// 所以拦截url,下面JS开始调用Android需要的方法
if (uri.getAuthority().equals("webview")) {
// 步骤3:
// 执行JS所需要调用的逻辑
System.out.println("js调用了Android的方法");
// 可以在协议上带有参数并传递到Android上
HashMap<String, String> params=new HashMap<>();
Set<String> collection=uri.getQueryParameterNames();
}
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
}
);
}
}
如果JS想要得到Android方法的返回值,只能通过 WebView 的 loadUrl ()去执行 JS 方法把返回值传递回去,相关的代码如下:
// Android:MainActivity.java
mWebView.loadUrl("javascript:returnResult(" + result + ")");
// JS:javascript.html
function returnResult(result){
alert("result is" + result);
}
在JS中,有三个常用的对话框方法:
方式3的原理:Android通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调分别拦截JS对话框
(即上述三个方法),得到他们的消息内容,然后解析即可。
下面的例子将用拦截 JS的输入框(即prompt()方法)说明 :
步骤1:加载JS代码,如下:
javascript.html
以.html格式放到src/main/assets文件夹里
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
<script>
function clickprompt(){
// 调用prompt()
var result=prompt("js://demo?arg1=111&arg2=222");
alert("demo " + result);
}
</script>
</head>
<!-- 点击按钮则调用clickprompt() -->
<body>
<button type="button" id="button1" onclick="clickprompt()">点击调用Android代码</button>
</body>
</html>
当使用mWebView.loadUrl("file:///android_asset/javascript.html")加载了上述JS代码后,就会触发回调onJsPrompt(),具体如下:
步骤2:在Android通过WebChromeClient复写onJsPrompt()
public class MainActivity extends AppCompatActivity {
WebView mWebView;
// Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView=(WebView) findViewById(R.id.webview);
WebSettings webSettings=mWebView.getSettings();
// 设置与Js交互的权限
webSettings.setJavaScriptEnabled(true);
// 设置允许JS弹窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// 先加载JS代码
// 格式规定为:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.setWebChromeClient(new WebChromeClient() {
// 拦截输入框(原理同方式2)
// 参数message:代表promt()的内容(不是url)
// 参数result:代表输入框的返回值
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// 根据协议的参数,判断是否是所需要的url(原理同方式2)
// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
//假定传入进来的 url="js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)
Uri uri=Uri.parse(message);
// 如果url的协议=预先约定的 js 协议
// 就解析往下解析参数
if ( uri.getScheme().equals("js")) {
// 如果 authority=预先约定协议里的 webview,即代表都符合约定的协议
// 所以拦截url,下面JS开始调用Android需要的方法
if (uri.getAuthority().equals("webview")) {
//
// 执行JS所需要调用的逻辑
System.out.println("js调用了Android的方法");
// 可以在协议上带有参数并传递到Android上
HashMap<String, String> params=new HashMap<>();
Set<String> collection=uri.getQueryParameterNames();
//参数result:代表消息框的返回值(输入值)
result.confirm("js调用了Android的方法成功啦");
}
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
// 通过alert()和confirm()拦截的原理相同,此处不作过多讲述
// 拦截JS的警告框
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
// 拦截JS的确认框
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
}
);
}
}
2.2.2 三种方式的对比 & 使用场景
*请认真填写需求信息,我们会在24小时内与您取得联系。