phoengap源码解析——插件机制,java和js代码互调用详解
2012-01-28 03:46 onm 阅读(365) 评论(0) 编辑 收藏 举报看了眼phonegap的android实现的源码,其实还是挺简单的。出乎意料的是,我原以为js这套api的封装是使用addJavascriptInterface这个接口完成的,但是它是用了一种奇异的方法来实现的。我们一步一步慢慢说。
http://docs.phonegap.com/en/1.3.0/index.html这个链接是phonegap的js api,可以看到api是分模块的,有Accelerometer,Camera,Capture,Compass,Connection,Contacts,Device,File,Geolocation,Media,Notification,Storage,Events,而每一个模块恰好是phonegap的一个plugin。而每一个plugin恰好大体对应一个java文件和一个js文件。在实际用phonegap开发的时候,我们引用的phonegap.js是经过编译整合后的文件,源码的framework/build.xml中有如下一段可以说明:
<!-- Create uncompressed JS file --> <concat destfile="assets/www/phonegap-${version}.js"> <fileset dir="assets/js" includes="phonegap.js.base" /> <fileset dir="assets/js" includes="*.js" /> </concat>
插件机制对应的java代码,主要在com.phonegap.api这个包里,所有的api对应模块的java实现都扩展了Plugin类,并且在src/com/phonegap/api/PluginManager.java中有public String exec(final String service, final String action, final String callbackId, final String jsonArgs, final boolean async);方法,此方法里面调用plugin.execute方法,相应的plugin插件实现了execute方法,进行具体操作。对应js代码,插件机制以及公有基础的东西主要phonegap.js.base文件,每个单独的模块文件实现具体的操作。js代码里也有相应定义,
/**
* Execute a PhoneGap command. It is up to the native side whether this action is synch or async.
* The native side can return:
* Synchronous: PluginResult object as a JSON string
* Asynchrounous: Empty string ""
* If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError,
* depending upon the result of the action.
*/
public String exec(final String service, final String action, final String callbackId, final String jsonArgs, final boolean async);
可以看到api操作还提供了同步异步两种模式。这里的一些东西到这里可能还不是很明朗,稍后了解了java和js互掉的原理后就清楚了。
但是这里,特别的对于Events有些特别,java与js交互上是不同于其它模块的方法且相对简单的,所以先说这个。在src/com/phonegap/DroidGap.java文件中可以看到,所有js的回调事件都是通过loadUrl然后执行一段javascript脚本来实现的,如
生命周期回调:this.appView.loadUrl("javascript:try{PhoneGap.fireDocumentEvent('resume');}catch(e){};");
按钮响应:this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('backbutton');");
assets/js/phonegap.js.base相应代码为:
PhoneGap.fireDocumentEvent = function(type, data) { var e = document.createEvent('Events'); e.initEvent(type); if (data) { for (var i in data) { e[i] = data[i]; } } document.dispatchEvent(e); };
好了,开始最重要的部分了。phonegap使用的java与js互相调用的原理。phonegap实现的模型刚也说了,有同步和异步两种。js实现的api,所以是js先会调用java代码,然后再返回给js。对于同步的而言,就是js调用java,然后java返回一个结果作为返回值。对于异步的而言,可能js掉了很多java代码,但是立即返回,然后java代码执行结束后再回调js代码,这里就涉及到js调java,然后java再调用js。
对于js调用java:
js调用java的入口是通过在js中调用prompt方法,这很奇怪吧,这个方法本来是让浏览器弹出个输入框的。我当初找了好久也没发现phonegap到底怎么搞得的让js调用java的代码,后来看到一会觉得该是这个方法,但是这是一个浏览器的客户端自己的东西,而且怪异的是浏览器并没有弹出输入框,后来终于发现。
在DroidGap.java中有个hack,重载了WebviewClient的onJsPrompt方法,然后执行了自己的逻辑。 也就是js调用prompt的时候,java端浏览器代码接受到这个,然后在响应的处理函数中根据传过来的参数,实现了一些特别的逻辑。可以从这个方法的注释上看出一二。
/** * Tell the client to display a prompt dialog to the user. * If the client returns true, WebView will assume that the client will handle the prompt dialog * and call the appropriate JsPromptResult method. * Since we are hacking prompts for our own purposes, we should not be using them for this * purpose, perhaps we should hack console.log to do this instead! @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { ... }
方法里面的代码就如刚所说会根据传过来的参数,做相应处理,它会先判断是不是本地网页的请求,如果是,然后分几种情况。
// Calling PluginManager.exec() to call a native service using prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true])); gap: // Polling for JavaScript messages gap_poll: // Calling into CallbackServer gap_callbackServer: // PhoneGap JS has initialized, so show webview(This solves white flash seen when rendering HTML) gap_init:
分别是,如果是prompt传过来的是gap:这样开头的字符串,那么就执行相应的java调用,Calling PluginManager.exec() to call a native service using prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true]))。gap_poll:和gap_callbackServer:是稍后要说到的java回调js使用的,gap_init:初始化处理相关的代码。如果其它情况,则构造一个Android的AlertDialog显示。
然后phonegap就是这样,通过设置setWebChromeClient和setWebViewClient,重载了一些实现,控制了浏览器行为,实现了自己的很多逻辑。
对于java回调js:
phonegap实现了一个回调服务器,服务器就是负责回调js代码的,服务器有一个js代码的队列,在src/com/phonegap/CallbackServer.java文件中
/** * The list of JavaScript statements to be sent to JavaScript. */ private LinkedList javascript;
服务器保存要回调的js的代码,供js客户端取回,这里java端是服务器端,js端是客户端,服务器端不可能请求客户端做啥,是b/s模型,所以phonegap实现了两种服务模型,一种是轮询,一种是XHR异步回调,也就是Ajax的模型。src/com/phonegap/CallbackServer.java是回调服务器的代码所在处。从类的注释中可以看到。
This class provides a way for Java to run JavaScript in the web page that has loaded PhoneGap. The CallbackServer class implements an XHR server and a polling server with a list of JavaScript statements that are to be executed on the web page.
CallbackServer提供的这两种模型,一种是XHR,一种是轮询,轮询很简单了,callbackserver服务器端,有一个保存回调js的列表,前面所说,然后每隔一段时间客户端的js会询问一次服务器,是否有需要回调的js,如果有则调用,然后每隔一段时再查询一次服务器。而基于XHR的,其实这个就是ajax用的机制了,js发起一个异步请求,然后服务器会在返回数据之前保持住这个连接,当返回数据就位后,服务器给请求客户端返回数据,然后关闭连接。然后客户端接受并且处理。
刚说了服务器端的代码实现,现在来看一下客户端js的相关代码。
/** * Internal function that uses XHR to call into PhoneGap Java code and retrieve any JavaScript * code that needs to be run. This is used for callbacks from Java to JavaScript. */ PhoneGap.JSCallback = function() { ... xmlhttp.open("GET", "http://127.0.0.1:"+PhoneGap.JSCallbackPort+"/"+PhoneGap.JSCallbackToken , true); xmlhttp.send(); }
这个是XHR模型的代码,客户端js使用xhr请求服务器来获取js代码,进行回调。
/** * Internal function that uses polling to call into PhoneGap Java code and retrieve * any JavaScript code that needs to be run. This is used for callbacks from Java to JavaScript. */ PhoneGap.JSCallbackPolling = function() { ... var msg = prompt("", "gap_poll:"); if (msg) { setTimeout(function() { try { var t = eval(""+msg); } catch (e) { console.log("JSCallbackPolling: Message from Server: " + msg); console.log("JSCallbackPolling Error: "+e); } }, 1); setTimeout(PhoneGap.JSCallbackPolling, 1); } else { setTimeout(PhoneGap.JSCallbackPolling, PhoneGap.JSCallbackPollingPeriod); } }
这个是轮询方式的,可以看到客户端每隔PhoneGap.JSCallbackPollingPeriod段时间,就请求一次服务器(通过prompt("", "gap_poll:");)。
至此js和java互调用,phonegap的plugin插件机制和api的实现原理就清楚了。另外phonegap源码的注释是很不错的。