分享

phonegap源码分析(一)------ android

 quasiceo 2014-01-05

phonegap源码分析(一)------ android

分类: 移动云 11926人阅读 评论(21) 收藏 举报

Phonegap已把源码提交到apache,成为一个非常受关注的开源框架cordova,它的跨平台的特性有点当年Java的味道和势头,成为移动平台上比较主流的解决方案。今日品味了一下它在android端的源码,看看它到底是如何结合native和web的。

首先我们总体上看看phonegap给我们提供的主要特性:

  • 在本地存储和渲染HTML
  • 以Native App的方式来运行
  • 用JS调用Native功能
说白了一句话,它就是想让我们只用web技术就能构建本地化移动应用。它比HTML5好的地方是可以预先打包好所需要的所有元素(如图片和脚本),并且可以更大限度地集成Native特性,当然,它完全兼容HTML5。

对于这样一款产品,如果让我开发,我感觉最优先要解决的问题是:

  • 如何执行和渲染HTML
  • web端如何高效地调用Native API(同步和异步)
  • 提供怎样的扩展机制来兼容新出的Native功能
带着这三个问题我分析了它在android端的源码,它在其他平台上实现有待后续深究,不过我估计大体结构是一致的。下面是我分析后的架构图:


从上图我们看出它架构上的关键点:

  • 基于WebView来渲染HTML
  • 基于Plugin的模式来封装Native API,包括Phonegap本身提供的和开发者自己定制的
  • 以覆盖prompt方法的形式来实现Web端对Android端的调用
  • 以XHR或JSONP的方式来实现Android端向Web端返回异步调用的结果
下面我们来分别看看这几个关键点

1)基于WebView来渲染HTML

这点比较简单,大家都想得到,它实际上就是个内嵌的浏览器,各个移动平台也提供了这样的组件,在Android上就是WebView。

但Phonegap对WebView做了些改造,它通过扩展WebViewClient和WebChromeClient改变了些标准行为,它用 CordovaWebViewClient扩展WebViewClient,并复写shouldOverrideUrlLoading、 onPageStarted、onPageFinished等方法,使得它扩展了web纯url网页调用的行为,具备了通过geo:xxx调用 intent,通过sms:xxx发短信等能力。另外,它用CordovaChromeClient扩展WebChromeClient,并复写了 onJsAlert、onJsConfirm等方法,用Native的风格的窗口来相应js端alert、confirm的调用,使其更像是一个 native的程序。更关键的是它复写了prompt,并通过这个方法来实现js对android端的调用,下面会详细谈这点。

总之,它就是基于CordovaWebViewClient和CordovaChromeClient扩展了WebView,使其具备标准的HTML执行渲染能力外,更具备Native化的样式和能力。这块的代码我就不具体去讲了,比较简单。

2)基于Plugin的模式来封装Native API

这点也比较简单,但凡想让用户去扩展,都会想到以Plugin的模式来构架,http://wiki./w/page/36752779/PhoneGap%20Plugins,这篇官方文档比较详细地讲解了如何使用和开发Plugin。

它的结构也比较扁平,总共三个类Plugin, PluginManager和PluginResult,一个配置文件plugin.xml,具体职责我就不多说,看看名字就知道了。

3)以覆盖prompt方法的形式来实现Web端对Android端的调用

这点是我学这个框架最想看的地方,虽然有点失望(感觉有点猥琐),但还是比较实用和直接。

WebChromeClient提供了一个onJsPrompt方法,这个方法是当web端调用prompt方法时就会调到。于是乎,它就把这个方 法给改了,改成Android向Web端暴露的接口,当Web要调用任何Android(Java)端的方法时,就调prompt,onJsPrompt 被调后,它再去解析参数来代理后续的行为。这时,它就主要是调用Plugin,通过Plugin来满足Web端的需求。时序图如下图所示:


前面比较好理解,但最后一步为啥要向一个server sendJavascript呢?这就是它实现异步调用的机制。

4)以XHR或JSONP的方式来实现Android端向Web端返回异步调用的结果

同步调用就不多说了,它没有上面时序图的最后一步,plugin.execute后就直接返回结果,通过JsPromptResult.confirm向js回调。

而异步调用是这个框架里难度最大的一个,而却是Native API调用大部分的适用场景,比如Camera,这都会启动Native端其他程序,等这些程序操作完了后,还需要得到它们的执行结果,比如拿到拍照后的相片。

Phonegap实际上是基于长背包的方式来实现Android端向web端的反推。长背包我就不多解释了,查查comet就能了解,它的机制就是 在web端发起ajax的周期性调用,Android端起一个本地Socket Server,并保持一个JS队列,如果有请求来,它就把队列里的JS返回,web端再执行这个JS,通过这个方式模拟了Android端向web端推动 执行结果。而上图中的sendJavascript实际上就是把执行结果用JSON的形式存在队列里等着web端来取。

但XHR(或Ajax)有跨域的限制,比如如果web端的html不是本地的file而是从远端(URL)下载下来的,那么它就不能向本地的 server发起ajax请求(因为不同域),所以它提供了一个备选方案:JSONP,这也是一个标准的解决ajax跨域的方案,实际上就是把JS下载下 来执行,这个就不多说了,可以通过关键字JSONP继续深究。

总的来说,这块采用的还是比较通用的解决方案,不过值得考量的是,这样频繁的轮询ajax是否会对性能和电池有所影响,除此之外,基本和直接Native程序是差不多的,这比直接调用HTML5确实优化不少。

这是我对phonegap在android端的源码分析,后面还想再看看在IOS和Windows Phone上是如何实现的,不过我得赶紧熟悉一下Objective-C和C#,分析完后再后续跟上。

更多
6
0
查看评论
8楼 hxy01245120 2013-01-28 18:53发表 [回复]
我还没有看源码.刚接触这个框架.目前遇到一个问题.启动之前的黑屏就不说了.但是启动apk之后.每一个页面的跳转都会黑屏.请问是怎么回事.是不是在缓冲数据阶段都是黑屏呢,还是怎么回事.请前辈指教...
7楼 feng283797821 2012-11-26 18:05发表 [回复]
只有一篇对phoneGap的android端的分析。。失望
6楼 kuaihuoxian 2012-08-27 16:35发表 [回复]
学习了
5楼 wuruixn 2012-03-23 09:37发表 [回复]
你好,在看phonegap源码过程中有几个问题想咨询下:
1.请问在phonegap.js的PhoneGap.exec函数中,通过prompt去触发调用android端,android端执行结果是如何传给下面的var r这个变量的?
var r = prompt(JSON.stringify(args), "gap:"+JSON.stringify([service, action, callbackId, true]));
我 的理解:android端通过XHR server将执行结果返回传到PhoneGap.JSCallback函数的xmlhttp.onreadystatechange中,但怎么跟上面的 var r变量联系起来的呢?我打印过var r的值,其值就是android端执行结果。
2.还有,如果我把phonegap.js文件里 PhoneGap.JSCallback函数中xmlhttp.status === 200和xmlhttp.status === 404下的回调函数setTimeout(PhoneGap.JSCallback, 1/10);注释掉,也能正常获得结果,那还要这个回调函数干嘛?
3.android端没有收到请求时,每隔10秒钟返回一个空的回复,以维持CallbackServer,请问参照问题2的情况为什么要维持?如何维持?
谢谢了!
Re: cobra217_8 2012-03-23 15:44发表 [回复]
回复wuruixn:问题(2)(3),我没法理解为什么注释了以后,还能正确实行。下面是我的理解,请参考。
我认为,PhoneGap.JSCallback 在第一次被调用后,就陷入了自身递归调用的过程;第一次调用是:
PhoneGap.Channel.join(function() {
.............
else {
PhoneGap.UsePolling = false;
PhoneGap.JSCallback();
}
........
}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]);

也 就是说,等待PhoneGap.onDOMContentLoaded,PhoneGap.onNativeReady执行完以后,其自身开始递归调用。 如果没有任何plugin调用,CallbackServer每隔10秒向JS端发送404 response;如果有plugin的异步函数调用, CallbackServer 就会发送200 response。PhoneGap.JSCallback 在xmlhttp.onreadystatechange 中接收response后,都会递归调用其自身。
所以,你说注释掉setTimeout(PhoneGap.JSCallback, 1/10)还可以正常运行,我实在是不能理解。
cutesource在给我的回复中提到,“从服务端传回的JS代码里写死了调用JSCallback”, 我并没有在java代码中找到类似的回调,你可以看看我给他的回复,看看和你的观点是否一样。
Re: cobra217_8 2012-03-23 15:27发表 [回复]
回复wuruixn:问题(1)
var r = prompt(JSON.stringify(args), "gap:"+JSON.stringify([service, action, callbackId, true]));最后的参数“true”代表异步调用,但是对于某些plugin的某些函数,是同步调用的。注意pluginmanager.exec 中的下面代码:
runAsync = async && !plugin.isSynch(action);
if(runAsync){}
else{
cr = plugin.execute(action, args, callbackId);
}
。。。。。
return ( cr != null ? cr.getJSONString() : "{ status: 0, message: 'all good' }" );
其中async是从脚本传过来的,plugin.isSynch(action)是判断这个action是不是要求同步的,也就是说必须两个条件同事成立,才能启用新线程异步调用。

我怀疑你测试的函数是一个必须同步执行的,所以可以输出var r 的结果。
Re: wuruixn 2012-03-24 16:58发表 [回复]
回复cobra217_8:XHR Server在收到XHR请求但没有数据就会等待10s返回空数据,前台再次发送下一个XHR请求,我觉得这种机制是导致phoneGap页面响应慢的一个原因,不知你怎么看待?
比 如:XHR Server在收到XHR请求但没有数据时,将进入等待10s过程,此时(10s等待过程的开始)前台页面刚好发送了一个phoneGap API异步请求,则该请求需要等待至少10s钟才能收到回应,因为需要等待前面的10s结束才能驱动前台发送下一个XHR请求,后台才会通过XHR返回数 据给前台。
Re: cobra217_8 2012-03-25 17:19发表 [回复]
回复wuruixn:修正一下刚才的回复:
刚才提到“ 如果在这个wait时间段中,前台产生了一个plugin的异步调用,phonegap的主线程会调用plugin的相应方法,并且生成相应的回调JS方法,随后将JS插入CallBackServer的js Array ”
有一点问题,具体过程是:
(1)phonegap主线程调用pluginManager.exec
(2)PluginManager 创建一条新的线程,在该线程中调用plugin.execute,并通过ctx.sendJavascript将返回值放入CallBackServer的js array中;
(3)CallBackServer.sendJavascript 函数中的notify()调用唤醒处于wait状态的CallBackServer thread。
总结:
一个plugin的异步函数调用涉及三条线程:
(1)主线程,执行pluginManager.exec
(2)线程A,由PluginManager创建,用于执行特定plugin的action,并且负责将异步调用的js代码放入CallBackServer的 js Array 中;
(3)CallBackServer 用于等待xhr request的线程,在CallBackServer.startServer()产生,也就是CallBackServer.run()运行的 thread。该线程在没有xhr request时陷入wait状态,被其它线程中调用CallBackServer.sendJavascript 函数所唤醒,回调JS 函数。
Re: cobra217_8 2012-03-25 15:51发表 [回复]
回 复wuruixn:不是这样的。前台发送xhr请求后,xhr server的thread确实会进入wait状态,默认时常为10s。如果在这个wait时间段中,前台产生了一个plugin的异步调 用,phonegap的主线程会调用plugin的相应方法,并且生成相应的回调JS方法,随后将JS插入CallBackServer的js Array。

CallBackServer的sendJavascript方法如下:
public void sendJavascript(String statement) {
this.javascript.add(statement); synchronized (this) {
this.empty = false;
this.notify();
}
}

其中的this.notify();将会唤醒处于wait状态的CallBackServer thread,进而结束等待,向前端发送回调函数,执行xmlhttprequest.onreadystatechange;

这个10s的等待是个默认的等待时间,是假设如果没有异步调用,而等待的最长时间。
Re: wuruixn 2012-03-24 09:48发表 [回复]
回复cobra217_8:再请问一个XMLHttpRequest的问题:
phonegap.js 文件里PhoneGap.JSCallback函数中xmlhttp.status === 200和xmlhttp.status === 404下的回调函数setTimeout(PhoneGap.JSCallback, 1/10);是为了建立连接而发送的XMLHttpRequest回调请求操作,以保持XHR通道连接正常,那么请问XMLHttpRequest过多久 不发送请求,上述的XHR连接就会中断?[
Re: cobra217_8 2012-03-25 16:30发表 [回复]
回复wuruixn:在Native端,等待web端发送请求的是 ServerSocket :waitSocket.accept();, 根据java文档,并没有一个时间上的限制。
Re: wuruixn 2012-03-24 09:28发表 [回复]
回复cobra217_8:你的猜测是对的,我自己写的插件后台返回的一直都是同步,所以var r总是有正确的返回值。
弄清了一个结论:
前 台请求是同步操作时,就会通过var r = prompt(JSON.stringify(args), "gap:"+JSON.stringify([service, action, callbackId, true]));直接返回结果;是异步操作时,通过XHR返回结果。
4楼 cobra217_8 2012-03-22 16:26发表 [回复]
请 问,phonegap.js 中 PhoneGap.JSCallback 是什么时候被调用的,被谁调用的。根据注释,似乎是被native java代码调用的,但是我找不到在哪里注册的这个回调函数,navite 代码怎么知道回调进去的呢?根据CallbackServer.run() 每隔10秒应该调用一次 PhoneGap.JSCallback。
Re: cutesource 2012-03-22 21:41发表 [回复]
回复cobra217_8:是的,从服务端传回的JS代码里写死了调用JSCallback
Re: cobra217_8 2012-03-23 15:19发表 [回复]
回 复cutesource:请问在哪里回调的PhoneGap.JSCallback?我搜索了native端的代码,没有找到JSCallback。我的 理解是,PluginResult的toSuccessCallbackString、toErrorCallbackString会向客户端发送回调脚 本函数,PhoneGap.callbackSuccess,PhoneGap.callbackError; 然后在 PhoneGap.JSCallback中的xmlhttp.onreadystatechange 自动执行会掉的脚本,也就是
if (xmlhttp.status === 200) {
........

var t = eval(msg);

中执行的回调脚本。

是不是我们下载的phonegap源代码版本不一样?
Re: cobra217_8 2012-03-22 17:34发表 [回复]
回复cobra217_8:刚才仔细看了一下phonegap.js, 其中的:
PhoneGap.Channel.join(function() {
....
// Start listening for XHR callbacks
setTimeout(function() {
if (PhoneGap.UsePolling) {
PhoneGap.JSCallbackPolling();
}
else {
var polling = prompt("usePolling", "gap_callbackServer:");
PhoneGap.UsePolling = polling;
if (polling == "true") {
PhoneGap.UsePolling = true;
PhoneGap.JSCallbackPolling();
}
else {
PhoneGap.UsePolling = false;
PhoneGap.JSCallback();
}
}
}, 1);
}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]);

也就是说,在PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady 完成后,开始进入 PhoneGap.JSCallback();, 监听xhr的循环开始。

不知理解的对不对?
3楼 kx29126390 2012-03-14 10:20发表 [回复]
请问你的phonegap源码在哪下的。
Re: cutesource 2012-03-14 11:29发表 [回复]
回复kx29126390:在github上,比如android版本的:
https://github.com/apache/incubator-cordova-android
2楼 xiaoban0514 2012-03-14 09:51发表 [回复]
应 该是基于webkit的吧,之前也做过尝试Sencha Touch,对于html的渲染效果实在是不尽人意。比如渲染GIS地图等。至于这种方案如果能够像JVM一样我想才能真正的做到跨平台的效果。以上个人 愚见。回帖请至 http://www./topic/1121432 谢谢。
1楼 pillar_liang 2012-03-08 13:51发表 [回复]
不 知道phoneGap前景如何,目前win8内核可以支持HTML5+js编程,wp8势必要走这条路线,chrome os本身也是HTML5+js编程,mozilla的系统更是基于html5,相信不久的将来,android也会直接支持web app,到时候说不定phonegap在ios上独行。
Re: cutesource 2012-03-08 14:19发表 [回复]
回复pillar_liang:但估计大家基于JS开发的标准和API肯定差别很大,同样存在跨平台的问题,phonegap其实就是在做屏蔽JS调用差异的问题
发表评论

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多