|
混合开发的App(Hybrid App)就是在一个App中内嵌一个轻量级的浏览器,一部分原生的功能改为Html 5来开发,这部分功能不仅能够在不升级App的情况下动态更新,而且可以在Android或iOS的App上同时运行,让用户的体验更好又可以节省开发的资源。
下面来谈谈Hybrid App开发中的技术问题。iOS方面的我不太了解,我就主要谈谈Android开发中的,其中可能会有很多说错的,请大家轻喷
Hybrid开发中关键问题是什么
想要在一个App中显示一个Html 5网页的功能,其实很简单,只要一个WebView就可以了。你可以点击链接来跳转网页。像这样的功能就能叫做Hybrid 开发了嘛?显然不是的。
我觉得一个Hybrid开发的App中必须要要有的功能就是Html 5页面和Native App怎么进行交互。比如,我点了一个Html 5页面上的一个按钮或链接,我能不能够跳转到Native App的某个页面;比如我点了Html 5页面上的分享按钮,我能不能调用Native App的分享功能;比如Html加载的时候能不能获取Native App的用户信息等等。
看下图,在网易云音乐中进入这个Html 5页面时,你点击 作者:空虚小编 你会进入他的主页,这个主页是Native页面,而你点击上面那个播放按钮时,云音乐会启动Native的播放界面播放音乐,你点击评论时,你会进入Native的评论页

Html 5和Native的交互
WebView 本来就支持js和Java相互调用,你只需要开启 WebView 的JavaScript脚本执行,然后通过代码 mWebView.addJavascriptInterface(new JsBridge(), "bxbxbai"); 向Html 5页面时注入一个Java对象,然后就可以在Html 5页面中调用Native的功能了
微信怎么做的
微信应该是Hybrid 开发做的最好的App之一,它是怎么做交互的呢?
答案就是 微信JS-SDK ,去微信开发者文档中可以看到,微信JS-SDK封装了各种微信的功能,比如分享到朋友圈,图像接口,音频接口,支付接口地理位置接口等等。开发者只需要调用微信JS-SDK中的函数,然后统一由JS-SDK来调用微信中的功能,这样好处就是我写了一个Html 5的应用或网页,在Android和iOS的微信中都可以正常运行了
下面会详细讲到
网易云音乐怎么做的
那么网易云音乐是怎么做的呢?我用黑科技知道了上图云音乐的界面Activity是 CommonSubjectActivity (名字好奇怪,如果要我从代码里找,我肯定找不到,因为还有一个类叫做 EmbedBrowserActivity ),我就在反编译后的云音乐代码中找相应的功能实现代码,实在没找到。不过我拿到了那个Html 5页面的地址: http://music.163.com/m/topic/194001
用Chrome打开后发现和App中显示的不一样,然后我用Charles截了进入那个Html 5的请求,发现云音乐加载的地址是 http://music.163.com/m/topic/194001?type=android ,就是加了手机系统类型
然后在我自己的App中加载这个Html 5页面就可以看到下图, @小比比说 这样的文字是可以点击跳转到个人,点击播放按钮是可以播放音乐的

从Html源代码中可以看到如下信息:

也就是说,当我点击一个用户名的时候就请求跳转到 orpheus://user/30868859 ,因为WebView可以拦截跳转的url,所以App在拦截每一个url,如果host是 orpheus 的话就启动用户首页
反编译代码后,在云音乐的代码中找到了 this.mWebView.setWebViewClient(new cf(this)); 这么一句代码,进入 cf 类,发现下面代码:

果然如此,再进入 RedirectActivity ,这是一个没有任何界面的Activity,专门用于处理页面跳转信息,它会调用一个方法 NeteaseMusicUtils.redirect(this, getIntent().getData().toString(), false) 来处理url, redirect 方法的名字是我自己写的,部分代码如下:

可以看到 orpheus://user/30868859 中的用户id被传入了 ProfileAcvitiy ,因此启动了用户首页显示了用户信息
然后我自己写了代码拦截Html 5的跳转,打印出的Log如下:

可以看到Html 5页面可以跳转到各种页面,比如用户首页、播放音乐、MV界面、评论页、电台节目等等
总结
一般来讲,也是我目前知道的两种主流的方式就是
js调用Native中的代码
Schema:WebView拦截页面跳转
第2种方式实现起来很简单,但是一个致命的问题就是这种交互方式是单向的,Html 5无法实现回调。像云音乐App中这种点击跳转到具体页面的功能,Schema的方式确实可以简单实现,而且也非常适合。如果需求变得复杂,假如Html 5需要获取Native App中的用户信息,那么最好使用js调用的方式。
js和Native进行交互
上面讲到WebViewbe本身就是支持js调用Native代码的,不过WebView的这个功能在Android 4.2(API 17)一下存在高危的漏洞。这个漏洞的原理就是Android系统通过 WebView.addJavascriptInterface(Object o, String interface) 方法注册可供js调用的Java对象,但是系统并没有对注册的Java对象方法调用做限制。导致攻击者可以利用反射调用未注册的其他任何Java对象,攻击者可以根据客户端的能力做任何事情。 这篇文章 详细的介绍了这个漏洞
出于安全考虑,Android 4.2以后的系统规定允许被js调用的Java方法必须以 @JavascriptInterface 进行注解
Cordova的解决方案
Cordova是一个广泛使用的Hybrid开发框架,它提供了一套js和Native交互规范
在Cordova的 SystemWebViewEngine 类中可以看到

因此当Android系统高于4.2时,Cordova还是使用 addJavascriptInterface 这种方式,因为这个方法在高版本上安全而且简单,低于4.2的时候,用什么方法呢?
答案是 WebChromeClient.onJsPrompt 方法
WebView可以设置一个 WebChromeClient 对象,它可以处理js的3个方法
onJsAlert
onJsConfirm
onJsPrompt
这3个方法分别对应js的 alert 、 confirm 、 prompt 方法,因为只有 prompt 接收返回值,所以js调用一个Native方法后可以等待Native返回一个参数。下面是 cordova.js 中的一段代码:

一种开源的解决方案
Cordova是Apache的一个开源解决方案,不过它需要xml配置 CordovaPlugin 信息,使用会比较麻烦,而且这个框架很重,具体请自行搜索Cordova使用教程
下面这个开源项目是我个人觉得比较合理的解决方案,也比较轻量级,下图就是一个Demo
https://github.com/pedant/safe-java-js-webview-bridge

这个项目的原理就是使用 WebChromeClient.onJsPrompt 方法来进行交互,本质上都是js调用 prompt 函数,传输一些参数, onJsPrompt 方法拦截到prompt动作,然后解析数据,最后调用相应的Native方法
HostJsScope类中定义了所有可以被js调用的方法,这些方法都必须是静态方法,并且所有的方法第一个参数必须是WebView

上面代码列举了最基本的点击Html 5按钮弹出对话框的功能
这个库中一个最关键的叫做 JsCallJava ,这个实现的就是js来调用Java方法的功能,这个类只用于 InjectedWebChromeClient 类


这个 InjectedWebChromeClient 是设给WebView的,这里一个非常重要的细节需要注意一下,在 onProgressChange 方法中,向WebView注入了一段js代码,这段js代码如下:



那么这段js代码是如何生成的呢?答案就在 JsCallJava 类的构造函数方法中,这个构造方法做的事情就是解析 HostJsScope 类中的方法,把每一个方法的签名都保持到 private Map<String, Method> mMethodsMap 中,再看上面那段js代码中
a.alert = a.alert = a.alert = a.delayJsCallBack = a.getIMSI = a.getOsSdk = a.goBack = a.overloadMethod = a.overloadMethod= a.passJson2Java = a.passLongType = a.retBackPassJson = a.retJavaObject = a.testLossTime = a.toast = a.toast = function()
这些都是 HostJsScope 类中定义的方法名
那么这个库的整个执行流程是这样的:
JsCallJava 类解析了 HostJsScope 类中所有的静态方法,将它们放到一个Map中,并且生成一段js代码
向WebView设置 InjectedChromeClient ,在 onProgressChanged 方法中将那段js代码注入到Html5页面中,这个过程通俗点讲就是, Native告诉Html 5页面,我开放了什么功能给你,你就来调用我
这样js就可以调用Native提供的这些方法,那段js代码还会将js执行的方法转换成一段json字符串,通过js的prompt方法传到 onJsPrompt 方法中, JsCallJava 调用 call(WebView view, String msg) 解析json字符串,包括要执行的 方法名字 , 参数类型 和 方法参数 ,其中还会验证json中的方法参数类型和 HostJsScope 中同名方法参数类型是否一致等等。
最后,如果方法正确执行, call 方法就返回一个json字符串code=200,否则就传code=500,这个信息会通过 prompt 方法的返回值传给js,这样Html 5 代码就能知道有没有正确执行了
以上就是这个开源库的整个原理,我个人觉得非常适合用于Hybrid开发,这个解决方案中js可以收到Native的返回值,而且没有使用 addJavascriptInterface 方法,在低版本手机上也不会有安全问题,这个方法比Cordova的实现和配置简单
那么当我点击Html 5页面上的一个按钮,比如弹出对话框,这个过程的整体流程是怎么样的呢
微信的解决方案?
什么?你问我微信是怎么解决的?我也反编译了微信的代码,想研究一下他们是解决的,其实我非常好奇微信的这种js 调用Native,并且又返回的调用方法
首先,我去微信的js sdk官网看了一下js sdk提供的功能,提供了各种强大的功能,各位可以自己去看一下。那么问题来了,微信是怎么做到js 调用Native并且能够成功返回的呢?
带着疑问我反编译了微信Android客户端,在assers/jsapi中看到了 wxjs.js 文件,我想这个就是微信js sdk的源码了吧。。。
我首先说一下,我不太懂js的代码, 我只能连蒙带猜的看微信的js代码, 如果有js大神对这方面也感兴趣,希望可以一起(jian)探(fei)讨(zao)
在 wxjs.js 中看到了一下代码,我猜微信就是用这个 __WeixinJSBridge 当时js和Native进行通信的数据结构吧?

然后我又看到了下面的代码,我想应该是提供分享内容到朋友圈功能的吧


请注意最后这句: _call('shareTimeline',data); ,在看看 __WeixinJSBridge 中的 call 属性,接着我找到了 _call 方法。

大致意思应该就是:就是将这个东西 _call('shareTimeline',data); 转换成一个json字符串吧,从这里看到微信的做法和上面那个开源库非常类似,简单并且安全。 _call 方法最后调用 _sendMessage 方法发送消息

从上面代码可以看到微信的js sdk也是将js的方法调用换成一个类似 weixin://dispatch_message/ 这样的url,上面说的json封装的数据。那么我猜测微信的做法是类似网易云音乐的拦截url吗?如果真的是这样的话,就非常不安全了,随便一个Html 5页面可以伪造一个类似: weixin://dispatch_message/ 这样的url来调用微信的功能了,不过好在微信对每个js调用都必须带上appid。
在反编译后的微信代码,我看到了下面代码:

我想这写就是微信想Html 5开放的接口吧?不过对比了一下微信js sdk的官网,我看到好多App提供的功能在js sdk中并没有找到,这样也没有太大关系,以为微信只要升级js sdk就可以使用其他功能了,因为Native已经开放了嘛~
从上面 __WeixinJSBridge 可以看到有一个熟悉 _handleMessageFromWeixin ,这个就是js来处理Native的回调接口,我用这个字符串在微信代码中搜索,结果如下:

因此,我大致猜测,微信中的js调Native功能是用拦截url的方式,而Native回调的话是使用 evaluateJavascript 方法
我也在js sdk中找到了相应的函数:

微信的做法应该说非常基础,使用了原生的功能,但是安全,由于微信客户端对每一个js调用都有验证(appid),因此这也增加了一定的安全性
以上说的都是建立在我的分析正确的情况下。
一些个人的想法
现在各种新的技术也在想办法解决Native开发的效率问题,想用技术来解决一套代码运行在Android和iOS客户端,我相信随着技术的发展这些问题都会解决。我也好期待Facebook即将推出的 React Native Android
Hybrid开发适用于哪些功能
本文讲的Hybrid开发就是Native客户端中嵌入了Html App的功能,这方面微信应该是做的最好的,由于Html 5的效率以及耗电问题,我个人觉得用户是不能满足Web App的体验的,Hybrid App也只适用于某些场景。一些基础的功能,比如调用手机的摄像头,获取地理位置,登录注册功能等等,做成Native的功能,让Html 5来调用更好,这样的体验也更好。
如果你把一个登录和注册功能也做成Html 5,在弱网络环境下,这个体验应该会非常的差,或许你等半天还没加载出页面。你可能会说,我可以预先加载Html 5的代码,打开App时直接加载,那么我说你在给自己找麻烦,如果要这样的话,Native开发或许更快一点。
那么什么情况适合Html 5开发呢?像一些活动页面,比如秒杀、团购等适合做Html 5,因为这些页面可能涉及的非常炫而且复杂,Html 5开发或许会简单点,关键是这些页面时效性短,更新更快,因为一个活动说不定就一周时间,下周换活动,如果这样的话,你还做Native是肯定不行的
总结
有那么一句 古老的箴言 :
如果你手里有一把锤子,所有东西看上去都想钉子
千万不要以为Hybrid开发能够夸平台运行,就使用Hybrid开发任何功能。其实Facebook早期也是这么想的,后来就是因为WebView渲染效率底下,把整个应用改为Native开发,请看 这里
引用Facebook的一段话:
Today, we’re releasing a new version of Facebook for Android that’s been rebuilt in native code to improve speed and performance. To support the unique complexity of Facebook stories across devices, we’re moving from a hybrid native/webview to pure native code, allowing us to optimize the Facebook experience for faster loading, new user interfaces, disk cache, and so on.
本文主要还是从技术上谈谈Hybrid开发中js和Native交互的技术实现原理。抛砖引玉,写的估计也有很多错的,希望技术大牛指出。
最后,我觉得那个开源的库是一个非常不错的解决方案,解决办法巧妙、简单而且安全。当时我debug了半天弄明白其中的原理后,我一拍大腿,这办法真好啊!!网易云音乐的解决办法适用于它的场景,不需要回调,Native只需要处理相应的信息,然后来实现页面跳转、播放音乐、播放MV等功能,这个方法也简单好用。
|