- 浏览: 258803 次
- 性别:
- 来自: 深圳
最新评论
-
whizkid:
[img] private void enableNdefEx ...
android通过NFC读写数据 -
zhangminglife:
您好!不错,最近正在弄这个东西,能否把demo发给我一份谢谢了 ...
SSL双向认证java实现(转) -
water卡:
android如何调用显示和隐藏系统默认的输入法 -
water卡:
android如何调用显示和隐藏系统默认的输入法 -
sjp524617477:
good
生成android使用的BKS证书
前言
上周研究了一下PhoneGap这个技术,主要是对它的API和插件机制的学习,总体来看这种技术还是有一定的市场,特别是对BS为主的开发来讲确实有很多吸引,当然,这种技术也有严重的短板,比如效率和调试。当然这些都是一个概括性的认识,今天打算在仔细的研究一下,把PhoneGap的jar包反编译了一下,在自己的疑问下,借助互联网的帮助,简单的分析了一下PhoneGap的内部实现和依靠的技术以及架构,再次总结一下。本人对Java和Javascript不太熟悉,里面不对的地方欢迎指正和交流。
主要阐述内容:
WebView&JS通信
PhoneGap技术可行性的前提基础
PhoneGap架构
应用层和实现层两方面的架构简述,架构的实现核心以及其扩展性的稳定和成熟度
PhoneGap通信过程
简述PhoneGap通信的过程,调用网页,执行JS,JS调用OS功能,同步异步回调系统返回值的完整过程
其他
结合地图应用的个人简单想法整理
主要使用的资料和工具
phonegap-1.0.0.jar
jd-gui
参考资料
Android-----WebView使用
phonegap源码分析(一)------ android
phoneGap API介绍
eclipse Android开发环境
备注:在学习源码中有一些不太懂和疑惑的地方在博客中得到了很好的解释。文章中有部分文字图片粘贴自上面两个博客,这里为了文章的完整和连贯性,做了整理。
WebView&JS通信
在PhoneGap之前,先简单阐述一下Android下PhoneGap可行性的前提技术:WebView
不同于PC平台下浏览器的多样化,在Mobile平台下,WebKit已经成为主流,在iOS和Android两大主要移动平台都已经比较成熟。WebKit包含一个网页引擎WebCore和一个脚本引擎JavaScriptCore,它们分别对应的是KDE的KHTML和KJS。不过, 随着JavaScript引擎的独立性越来越强,现在WebKit和WebCore已经基本上混用不分。在Android手机中内置了一款高性能webkit内核浏览器,在SDK中封装为一个叫做WebView组件。这样通过WebView组件和WebKit浏览器通信成为了可行。
直接看代码,该代码实现了1WebView控件和网页的绑定2JS调用Java函数,实现JS调用系统函数的能力3Java调用JS,实现Java调用脚本的能力
Java代码
1: package com.webviewtest;
2:
3: // 引用Android SDK,Handler、WebSetting、WebView
4: import android.app.Activity;
5: import android.os.Bundle;
6: import android.os.Handler;
7: import android.webkit.WebSettings;
8: import android.webkit.WebView;
9:
10: public class WebViewTestActivity extends Activity {
11: private WebView mWebView;
12: private Handler mHandler = new Handler();
13: /** Called when the activity is first created. */
14: @Override
15: public void onCreate(Bundle savedInstanceState) {
16: super.onCreate(savedInstanceState);
17: setContentView(R.layout.main);
18: mWebView = (WebView) findViewById(R.id.webview);
19: WebSettings webSettings = mWebView.getSettings();
20: webSettings.setSavePassword(false);
21: webSettings.setSaveFormData(false);
22: // 保证WebView支持调用javascript脚本
23: webSettings.setJavaScriptEnabled(true);
24: webSettings.setSupportZoom(true);
25: // 添加可供Javascript调用的接口mydata,该函数在此模拟调用系统函数,返回数值10,验证js调用Java的方法
26: mWebView.addJavascriptInterface(new Object() {
27: public int mydata(){
28: return 10;
29: }
30: }, "demo");
31: /*
32: // 该接口验证Java调用js脚本的方法,调用demo页面下的js接口wave()
33: mWebView.addJavascriptInterface(new Object() {
34: public void clickOnAndroid() {
35: mHandler.post(new Runnable() {
36: public void run() {
37: mWebView.loadUrl("javascript:wave()");
38: }
39: });
40: }
41: }, "demo");
42: */
43:
44: // WebView控件绑定demo页面,实现Native和Web结合
45: mWebView.loadUrl("file:///android_asset/demo.html");
46: }
47:
48:
49: }
简单解释一下,首先初始化要注意setJavaScriptEnabled函数,设置为True保证WebView支持调用JS脚本
WebView::loadUrl接口:调用网页或js接口,实现Android Java(Native Code)调用Web
WebView::addJavascriptInterface:增加javascript识别接口,实现js调用Android Java
好了,通过简单的示例java实现思路大致如上,下面看一下demo页面的实现,则更为简单
HTML代码
1: <html>
2: <body>
3: <input type="text" id="a" value="test"></input>
4: <a onClick=va()>
5: Click me!
6: </a>
7: <a onClick=window.demo.clickOnAndroid() >
8: <img id="droid" src="01.png" /><br>
9: Click me!
10: </a>
11: </body>
12: </html>
JS代码
1: <script>
2: function wave() {
3: document.getElementById("droid").src="02.png";
4: }
5:
6: function va()
7: {
8: document.getElementById("a").value=window.demo.mydata();
9: }
10: </script>
HTML页面简单解释:第一个click事件:va调用Java接口demo.mydata(),返回10,修改text内容,验证js调用java;第二个事件:调用Javademo.clickOnAndroid,该函数调用javascript:wave(),从而验证Java调用js
不用任何第三方jar包,在Android下粘贴此代码,可以很好的在真机验证,还有,demo.html页面直接粘贴在assets文件夹下即可
PhoneGap架构简述
接下来,我们来看一下PhoneGap的架构组成。
PhoneGap在应用层上很简单清晰,将Native Code封装成13个类,通过上面的技术转化为可以调用的phonegap.js脚本,来供Web开发者在网页开发中应用。Phonegap提供的整体方案,下图阐述的非常好,盗用一下,一图胜千言啊,呵呵。
而在内部实现的架构,其实也并不复杂,分析jar包,里面的class如下:
分析一下PhoneGap类库架构,结合PhoneGap示例,首先我们最感兴趣的,就是DroidGap这个class,源码截取部分如下 :
1: package com.phonegap;
2:
3: public class DroidGap extends PhonegapActivity
4: {
5: protected WebView appView;
6: protected WebViewClient webViewClient;
7: protected PluginManager pluginManager;
8: }
通过类结构结合WebView的了解,相信就如梦初醒般的释疑了吧,其实这厮也是在WebView的基础上进行了一把封装,就起神奇性来说,还是远远无法和这个国度来媲美滴。虽然截取不多,我们也能得到PhoneGap结构第一个结论:PhoneGap整体技术思路建立在WebView的基础上,是结合WebView、Native Code和HTML技术的中间层封装。
接下来,我们打开Plugin和一个具体的Plugin类,截取代码如下,有兴趣自己写一个的可以参考如下链接,可以更好的理解:
1: public abstract class Plugin
2: implements IPlugin
3: {
4: public String id;
5: public abstract PluginResult execute(String paramString1, JSONArray paramJSONArray, String paramString2);
6: }
7:
8: public class DirectoryListPlugin extends Plugin {
9: /** List Action */
10: public static final String ACTION = "list";
11:
12: @Override
13: public PluginResult execute(String action, JSONArray data, String callbackId) {
14: return result;
15: }
16: }
通过如上的Plugin抽象类和DirectoryListPlugin实体类,我们可以看到,PhoneGap采用插件化的方式来管理各个模块,这个你可以通过分析class可以验证,所有的模块封装,都是继承自Plugin,而纯虚函数execute则定义各个模块函数功能的规范,让具体的实体类来负责各自的实现。于是,我们得到了PhoneGap结构的第二个结论:PhoneGap是通过插件机制来管理自己的架构,从而将整个框架支撑在一起。当然,这也是类库设计的一种经典方法了,及可以自用实现功能,也能扩展插件让用户自己开发,其实我们的类库设计还是很不错的,就是质量败给了数量。
下面,我们再看一下插件管理机制,主要是Plugin、PluginManager、PluginResult。,截取代码如下:
1: public final class PluginManager
2: {
3: private HashMap<String, IPlugin> plugins = new HashMap();
4: private HashMap<String, String> services = new HashMap();
5:
6: public PluginManager(WebView app, PhonegapActivity ctx)
7: {
8: loadPlugins();
9: }
10:
11: public void loadPlugins()
12: {
13: int id = this.ctx.getResources().getIdentifier("plugins", "xml", this.ctx.getPackageName());
14: if (id == 0) pluginConfigurationMissing();
15: XmlResourceParser xml = this.ctx.getResources().getXml(id);
16: int eventType = -1;
17: while (eventType != 1) {
18: if (eventType == 2) {
19: String strNode = xml.getName();
20: if (strNode.equals("plugin")) {
21: String name = xml.getAttributeValue(null, "name");
22: String value = xml.getAttributeValue(null, "value");
23:
24: addService(name, value);
25: }
26: }
27: try {
28: eventType = xml.next();
29: } catch (XmlPullParserException e) {
30: e.printStackTrace();
31: } catch (IOException e) {
32: e.printStackTrace();
33: }
34: }
35: }
36:
37: public String exec(String service, String action, String callbackId, String jsonArgs, boolean async)
38: {
39: }
40: }
1: public class DroidGap extends PhonegapActivity
2: {
3: public void init()
4: {
5: bindBrowser(this.appView);
6: }
7:
8: private void bindBrowser(WebView appView)
9: {
10: this.pluginManager = new PluginManager(appView, this);
11: }
12: }
13:
首先DroidGap类在初始化的时候会创建PluginManager类,可以简单DroidGap和PluginManager是一一对应的关系,全局变量(这种理解绝对上是不对的)。而PluginManager通过loadPlugins解析plugin.xml,将引用的插件加载进来,而在调用时则通过exec函数来具体到具体的Plugin插件来实现。于是,得到了PhoneGap架构的第三个结论:PhoneGap插件管理采用标准的工厂模式,通过xml进行解析和扩展,从而完成整个工作流的架构,对了,忘记提PluginResult了,这个就是结果返回类的标准。于是,我们提取简单的PhoneGap简单实现架构如下:
PhoneGap通信过程
好了,理解完上面的WebView思路和PhoneGap架构,我们就有一个好的基础来研究PhoneGap具体实现,比如一个完整的通信过程是如何实现,这就需要细化到具体的函数内部实现。。
当我们写的第一个PhoneGap程序时,我想你的第一个代码肯定是这样的吧:super.loadUrl(file:///android_asset/www/index.html);于是,在PhoneGap的帮助下,一个Hello World就展现在我们的眼前,这时候你就会眼前一亮,loadUrl,这个函数在WebView控件中也有,我们就来看一下PhoneGap的实现代码如下:
1: public class DroidGap extends PhonegapActivity
2: {
3: protected WebView appView;
4: public void init()
5: {
6: this.appView = new WebView(this);
7:
8:
9: if (Build.VERSION.RELEASE.startsWith("1.")) {
10: this.appView.setWebChromeClient(new GapClient(this));
11: }
12: else {
13: this.appView.setWebChromeClient(new EclairClient(this));
14: }
15:
16: settings.setJavaScriptEnabled(true);
17:
18: String url = getStringProperty("url", null);
19: if (url != null) {
20: System.out.println("Loading initial URL=" + url);
21: loadUrl(url);
22: }
23: }
24: public void loadUrl(String url)
25: {
26: this.val$me.appView.loadUrl(this.val$url);
27: }
28: }
如上可见,DroidGap在初始化时调用WebView支持JS脚本解析,在loadUrl中,也是经过一些判断,最终也是调用WebView的loadUrl来实现最终功能。
下面就该具体的通信过程了,以DirectoryListPlugin插件的应用为例子,这个事PhoneGap自己提供的自定义插件开发的示例,用户在页面开发中代码如下:
1: <script type="text/javascript" src="phonegap.js"></script>
2: <script type="text/javascript" src="directorylisting.js"></script>
3: <script type="text/javascript" >
4: document.addEventListener('deviceready', function () {
5: var btn = document.getElementById("list-sdcard");
6: btn.onclick = function () {
7: DirectoryListing.list("/sdcard",
8: function (r) { printResult(r) },
9: function (e) { alert("error") }
10: );
11: }
12: btn.disabled = false;
13: }, true);
14: </script>
1: // directorylisting.js
2: var DirectoryListing = {
3: list: function(directory,successCallback, failureCallback) {
4: return PhoneGap.exec(successCallback, //Success callback from the plugin
5: failureCallback, //Error callback from the plugin
6: 'DirectoryListPlugin', //Tell PhoneGap to run "DirectoryListingPlugin" Plugin
7: 'list', //Tell plugin, which action we want to perform
8: [directory]); //Passing list of args to the plugin
9: }
10: };
如上当脚本调用DirectoryListing.list,最终是调用的PhoneGap.exec函数来进行的具体实现,接下来要出场的是PhoneGap.js这个文件了,看实现:
1: PhoneGap.exec = function(success, fail, service, action, args) {
2: try {
3: var callbackId = service + PhoneGap.callbackId++;
4: if (success || fail) {
5: PhoneGap.callbacks[callbackId] = {success:success, fail:fail};
6: }
7:
8: var r = prompt(PhoneGap.stringify(args), "gap:"+PhoneGap.stringify([service, action, callbackId, true]));
9: } catch (e2) {
10: console.log("Error: "+e2);
11: }
12: };
这个脚本其实我也不太理解,哎,没文化真可怕啊,只看见五个参数,两个callback回调,一个类指针,一个类函数指针(允许我用指针来说吧,顺口一点),一个用户参数,然后里面的核心函数就是皮肉,prompt()这时候网上给的资料让我理解了:WebChromeClient提供了一个onJsPrompt方法,这个方法是当web端调用prompt方法时就会调到。于是乎,它就把这个方法给改了,改成Android向Web端暴露的接口,当Web要调用任何Android(Java)端的方法时,就调prompt,onJsPrompt被调后,它再去解析参数来代理后续的行为。这时,它就主要是调用Plugin,通过Plugin来满足Web端的需求。时序图如下图所示:
换句话说,WebKit的这个函数,接下来会调用onJsPrompt()函数,onJsPrompt()调用exec函数,那让我们来看一下他们又做了什么事情,依次代码如下:
1: public class DroidGap extends PhonegapActivity
2: {
3: public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result)
4: {
5: String r = DroidGap.this.pluginManager.exec(service, action, callbackId, message, async);
6: }
7: }
8:
9: public final class PluginManager
10: {
11: public String exec(String service, String action, String callbackId, String jsonArgs, boolean async)
12: {
13: String clazz = (String)this.services.get(service);
14: if (isPhoneGapPlugin(c)) {
15: IPlugin plugin = addPlugin(clazz, c);
16: if (runAsync)
17: {
18: Thread thread = new Thread(new Runnable(plugin, action, args, callbackId, ctx)
19: {
20: public void run() {
21: try {
22: PluginResult cr = this.val$plugin.execute(this.val$action, this.val$args, this.val$callbackId);
23: int status = cr.getStatus();
24:
25: if ((status != PluginResult.Status.NO_RESULT.ordinal()) || (!(cr.getKeepCallback())))
26: {
27: if ((status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal())) {
28: this.val$ctx.sendJavascript(cr.toSuccessCallbackString(this.val$callbackId));
29: }
30: else
31: {
32: this.val$ctx.sendJavascript(cr.toErrorCallbackString(this.val$callbackId)); }
33: }
34: } catch (Exception e) {
35: PluginResult cr = new PluginResult(PluginResult.Status.ERROR, e.getMessage());
36: this.val$ctx.sendJavascript(cr.toErrorCallbackString(this.val$callbackId));
37: }
38: }
39: });
40: thread.start();
41: return "";
42: }
43:
44: cr = plugin.execute(action, args, callbackId);
45:
46: if ((cr.getStatus() == PluginResult.Status.NO_RESULT.ordinal()) && (cr.getKeepCallback()))
47: return "";
48: }
49: }
50: }
上面的代码简单介绍如下,当你轻轻在界面点击一个按钮,触发js事件时,PhoneGap脚本调用prompt函数,这会促发DroidGap的onJsPrompt,此函数最主要的作用是调用PluginManager的exec函数,这样将具体的js函数通过插件Manager来指定具体的Plugin来执行,这在PluginManager中通过Hash字典通过service参数获取具体的Plugin插件,专业的人做专业的事情嘛,这时候Plugin就通过execute来调用系统API,达到js与系统级别之间的通信。
另外,还有一个返回值问题的考虑,如果是同步,那很自然的通过返回的PluginResult来返回,而如果是异步方式,这就需要注意代码中Thread的应用,这时候PhoneGap创建了一个线程,来专门用于获取返回值,这个线程不停的Run,直到获取到返回的结果,然后以sendJavascript的方式回调给js,从而完成java通信js的过程。
另外,这个sendJavascript是干什么用的吗,呵呵,一看就是发送给Javascript的消息,这个一看就知道了,这里只是突然想到了MFC下面的经典面试题:PostMessage和SendMessage有什么不同,呵呵,SendMessage只是抛消息而不负责事件的返回就直接走人,而post则需要等待一个结果才负责人的闪人,说不定写sendJavascript接口这个人当时应该也是这么想的吧,可以通过是否有postJavascript这个接口来验证:)
这样,PhoneGap整个的过程都已经结束,其实总结下来其技术看起来确实很有新颖,但是本质看来并无什么惊叹之处,都是一些很实用直接的方法,当然这帮哥们应该是对Java客户端技术和HTML很熟悉的,但就代码而言还是很专业的,无论是质量还是设计。当然,其最难的部分确实是异步机制的处理,通过创建Thread的方式来实现,因为我对Java线程run运行机制不是很了解,直观来看这种方式在CS开发上没有问题,但是这样实现有点简单零散,感觉如果有观察者模式的方式来进行管理,代码上会更优雅一些,但本身返回值就是一个JSONArray,这样做是否有点小题大做,这也说不清楚,毕竟PhoneGap要考虑这么多移动平台的Native Code而非Android一个平台。
总结
通过如上的整个过程,个人感觉应该能对PhoneGap的内部实现机制有一个大概的了解,学以致用,如果从结果的角度来考虑,我们能对是否采用PhoneGap开发有一个简单的判断吧。
首先要承认,PhoneGap这样的实现,自己封装了一大堆,确实在效率上是有损失的,这点没办法,如果直接调用WebView,小打小闹还没问题,如果真要有规模,没有设计是不行的,这让我想到了OpenGL,有了OpenGL为啥还有OGRE和OSG以及Unity3D,直接用OpenGL属于玩死自己的一种做法,呵呵。
其次,你要评估一下,如果你自己做封装,你是否有足够的能力做到差不多的水平,坦白说,PhoneGap这套东西还是有积极成分的,特别随着移动平台和浏览器技术的发展,这种让用户专注页面开发的方式还是有一些人性化的
然后,PhoneGap这种Plugin的方式确实具有通用性,无论什么系统函数,在这种应用下都可以封装,即使是C++的接口,你都可以通过JNI封装成Java后,在通过这种技术来进行展现,比如大数据,渲染,三维等高性能的核心,技术上都可以走通,但也有接口封装繁杂,对开发人员要求较高,调试等问题,另外,还需要将C++面向对象的封装成JNI面向过程的,然后在封装成Java面向过程,然后在封装成JS面向过程,然后再通过原型封装成面向对象的,呵呵,这下终于圆满了,这种体系产品不是随便一个公司能搞定的。
最后,就是开发人员的积累,如果是BS开发丰富的,这种方式还是有很多积极成分的,专注Web技术,将产品做好,底层的效率,大数据这些问题如果可以接受,这种损失也是可行的,如果是CS开发,这种技术就有点过于先进了,毕竟本身这种技术归类于Web比较合适。要保证开发者用的顺手吧。
当然还有产品本身的定位,技术是死的,人是活的,产品是给人用的,结合目前的现状,发挥自己的有点,从产品设计上避免自己的缺点,这就需要综合考虑。
个人总结,PhoneGap这种方式很适合作为Web产品的App移植通用解决方案,除非你有足够的能力来搞定Native Code,除非你的产品对Native Code要求足够的简单,除非你不做App应用,否则在技术可行性上就没有更好的解决方案。
另外感慨技术发展的太快了,这种快,有时候让人觉得兴奋,可以很轻松的做很多以前看似不可能的事情,有时候也让人觉得失落,因为有些发展或多或少有点肤浅,有种过眼云烟的味道。
上周研究了一下PhoneGap这个技术,主要是对它的API和插件机制的学习,总体来看这种技术还是有一定的市场,特别是对BS为主的开发来讲确实有很多吸引,当然,这种技术也有严重的短板,比如效率和调试。当然这些都是一个概括性的认识,今天打算在仔细的研究一下,把PhoneGap的jar包反编译了一下,在自己的疑问下,借助互联网的帮助,简单的分析了一下PhoneGap的内部实现和依靠的技术以及架构,再次总结一下。本人对Java和Javascript不太熟悉,里面不对的地方欢迎指正和交流。
主要阐述内容:
WebView&JS通信
PhoneGap技术可行性的前提基础
PhoneGap架构
应用层和实现层两方面的架构简述,架构的实现核心以及其扩展性的稳定和成熟度
PhoneGap通信过程
简述PhoneGap通信的过程,调用网页,执行JS,JS调用OS功能,同步异步回调系统返回值的完整过程
其他
结合地图应用的个人简单想法整理
主要使用的资料和工具
phonegap-1.0.0.jar
jd-gui
参考资料
Android-----WebView使用
phonegap源码分析(一)------ android
phoneGap API介绍
eclipse Android开发环境
备注:在学习源码中有一些不太懂和疑惑的地方在博客中得到了很好的解释。文章中有部分文字图片粘贴自上面两个博客,这里为了文章的完整和连贯性,做了整理。
WebView&JS通信
在PhoneGap之前,先简单阐述一下Android下PhoneGap可行性的前提技术:WebView
不同于PC平台下浏览器的多样化,在Mobile平台下,WebKit已经成为主流,在iOS和Android两大主要移动平台都已经比较成熟。WebKit包含一个网页引擎WebCore和一个脚本引擎JavaScriptCore,它们分别对应的是KDE的KHTML和KJS。不过, 随着JavaScript引擎的独立性越来越强,现在WebKit和WebCore已经基本上混用不分。在Android手机中内置了一款高性能webkit内核浏览器,在SDK中封装为一个叫做WebView组件。这样通过WebView组件和WebKit浏览器通信成为了可行。
直接看代码,该代码实现了1WebView控件和网页的绑定2JS调用Java函数,实现JS调用系统函数的能力3Java调用JS,实现Java调用脚本的能力
Java代码
1: package com.webviewtest;
2:
3: // 引用Android SDK,Handler、WebSetting、WebView
4: import android.app.Activity;
5: import android.os.Bundle;
6: import android.os.Handler;
7: import android.webkit.WebSettings;
8: import android.webkit.WebView;
9:
10: public class WebViewTestActivity extends Activity {
11: private WebView mWebView;
12: private Handler mHandler = new Handler();
13: /** Called when the activity is first created. */
14: @Override
15: public void onCreate(Bundle savedInstanceState) {
16: super.onCreate(savedInstanceState);
17: setContentView(R.layout.main);
18: mWebView = (WebView) findViewById(R.id.webview);
19: WebSettings webSettings = mWebView.getSettings();
20: webSettings.setSavePassword(false);
21: webSettings.setSaveFormData(false);
22: // 保证WebView支持调用javascript脚本
23: webSettings.setJavaScriptEnabled(true);
24: webSettings.setSupportZoom(true);
25: // 添加可供Javascript调用的接口mydata,该函数在此模拟调用系统函数,返回数值10,验证js调用Java的方法
26: mWebView.addJavascriptInterface(new Object() {
27: public int mydata(){
28: return 10;
29: }
30: }, "demo");
31: /*
32: // 该接口验证Java调用js脚本的方法,调用demo页面下的js接口wave()
33: mWebView.addJavascriptInterface(new Object() {
34: public void clickOnAndroid() {
35: mHandler.post(new Runnable() {
36: public void run() {
37: mWebView.loadUrl("javascript:wave()");
38: }
39: });
40: }
41: }, "demo");
42: */
43:
44: // WebView控件绑定demo页面,实现Native和Web结合
45: mWebView.loadUrl("file:///android_asset/demo.html");
46: }
47:
48:
49: }
简单解释一下,首先初始化要注意setJavaScriptEnabled函数,设置为True保证WebView支持调用JS脚本
WebView::loadUrl接口:调用网页或js接口,实现Android Java(Native Code)调用Web
WebView::addJavascriptInterface:增加javascript识别接口,实现js调用Android Java
好了,通过简单的示例java实现思路大致如上,下面看一下demo页面的实现,则更为简单
HTML代码
1: <html>
2: <body>
3: <input type="text" id="a" value="test"></input>
4: <a onClick=va()>
5: Click me!
6: </a>
7: <a onClick=window.demo.clickOnAndroid() >
8: <img id="droid" src="01.png" /><br>
9: Click me!
10: </a>
11: </body>
12: </html>
JS代码
1: <script>
2: function wave() {
3: document.getElementById("droid").src="02.png";
4: }
5:
6: function va()
7: {
8: document.getElementById("a").value=window.demo.mydata();
9: }
10: </script>
HTML页面简单解释:第一个click事件:va调用Java接口demo.mydata(),返回10,修改text内容,验证js调用java;第二个事件:调用Javademo.clickOnAndroid,该函数调用javascript:wave(),从而验证Java调用js
不用任何第三方jar包,在Android下粘贴此代码,可以很好的在真机验证,还有,demo.html页面直接粘贴在assets文件夹下即可
PhoneGap架构简述
接下来,我们来看一下PhoneGap的架构组成。
PhoneGap在应用层上很简单清晰,将Native Code封装成13个类,通过上面的技术转化为可以调用的phonegap.js脚本,来供Web开发者在网页开发中应用。Phonegap提供的整体方案,下图阐述的非常好,盗用一下,一图胜千言啊,呵呵。
而在内部实现的架构,其实也并不复杂,分析jar包,里面的class如下:
分析一下PhoneGap类库架构,结合PhoneGap示例,首先我们最感兴趣的,就是DroidGap这个class,源码截取部分如下 :
1: package com.phonegap;
2:
3: public class DroidGap extends PhonegapActivity
4: {
5: protected WebView appView;
6: protected WebViewClient webViewClient;
7: protected PluginManager pluginManager;
8: }
通过类结构结合WebView的了解,相信就如梦初醒般的释疑了吧,其实这厮也是在WebView的基础上进行了一把封装,就起神奇性来说,还是远远无法和这个国度来媲美滴。虽然截取不多,我们也能得到PhoneGap结构第一个结论:PhoneGap整体技术思路建立在WebView的基础上,是结合WebView、Native Code和HTML技术的中间层封装。
接下来,我们打开Plugin和一个具体的Plugin类,截取代码如下,有兴趣自己写一个的可以参考如下链接,可以更好的理解:
1: public abstract class Plugin
2: implements IPlugin
3: {
4: public String id;
5: public abstract PluginResult execute(String paramString1, JSONArray paramJSONArray, String paramString2);
6: }
7:
8: public class DirectoryListPlugin extends Plugin {
9: /** List Action */
10: public static final String ACTION = "list";
11:
12: @Override
13: public PluginResult execute(String action, JSONArray data, String callbackId) {
14: return result;
15: }
16: }
通过如上的Plugin抽象类和DirectoryListPlugin实体类,我们可以看到,PhoneGap采用插件化的方式来管理各个模块,这个你可以通过分析class可以验证,所有的模块封装,都是继承自Plugin,而纯虚函数execute则定义各个模块函数功能的规范,让具体的实体类来负责各自的实现。于是,我们得到了PhoneGap结构的第二个结论:PhoneGap是通过插件机制来管理自己的架构,从而将整个框架支撑在一起。当然,这也是类库设计的一种经典方法了,及可以自用实现功能,也能扩展插件让用户自己开发,其实我们的类库设计还是很不错的,就是质量败给了数量。
下面,我们再看一下插件管理机制,主要是Plugin、PluginManager、PluginResult。,截取代码如下:
1: public final class PluginManager
2: {
3: private HashMap<String, IPlugin> plugins = new HashMap();
4: private HashMap<String, String> services = new HashMap();
5:
6: public PluginManager(WebView app, PhonegapActivity ctx)
7: {
8: loadPlugins();
9: }
10:
11: public void loadPlugins()
12: {
13: int id = this.ctx.getResources().getIdentifier("plugins", "xml", this.ctx.getPackageName());
14: if (id == 0) pluginConfigurationMissing();
15: XmlResourceParser xml = this.ctx.getResources().getXml(id);
16: int eventType = -1;
17: while (eventType != 1) {
18: if (eventType == 2) {
19: String strNode = xml.getName();
20: if (strNode.equals("plugin")) {
21: String name = xml.getAttributeValue(null, "name");
22: String value = xml.getAttributeValue(null, "value");
23:
24: addService(name, value);
25: }
26: }
27: try {
28: eventType = xml.next();
29: } catch (XmlPullParserException e) {
30: e.printStackTrace();
31: } catch (IOException e) {
32: e.printStackTrace();
33: }
34: }
35: }
36:
37: public String exec(String service, String action, String callbackId, String jsonArgs, boolean async)
38: {
39: }
40: }
1: public class DroidGap extends PhonegapActivity
2: {
3: public void init()
4: {
5: bindBrowser(this.appView);
6: }
7:
8: private void bindBrowser(WebView appView)
9: {
10: this.pluginManager = new PluginManager(appView, this);
11: }
12: }
13:
首先DroidGap类在初始化的时候会创建PluginManager类,可以简单DroidGap和PluginManager是一一对应的关系,全局变量(这种理解绝对上是不对的)。而PluginManager通过loadPlugins解析plugin.xml,将引用的插件加载进来,而在调用时则通过exec函数来具体到具体的Plugin插件来实现。于是,得到了PhoneGap架构的第三个结论:PhoneGap插件管理采用标准的工厂模式,通过xml进行解析和扩展,从而完成整个工作流的架构,对了,忘记提PluginResult了,这个就是结果返回类的标准。于是,我们提取简单的PhoneGap简单实现架构如下:
PhoneGap通信过程
好了,理解完上面的WebView思路和PhoneGap架构,我们就有一个好的基础来研究PhoneGap具体实现,比如一个完整的通信过程是如何实现,这就需要细化到具体的函数内部实现。。
当我们写的第一个PhoneGap程序时,我想你的第一个代码肯定是这样的吧:super.loadUrl(file:///android_asset/www/index.html);于是,在PhoneGap的帮助下,一个Hello World就展现在我们的眼前,这时候你就会眼前一亮,loadUrl,这个函数在WebView控件中也有,我们就来看一下PhoneGap的实现代码如下:
1: public class DroidGap extends PhonegapActivity
2: {
3: protected WebView appView;
4: public void init()
5: {
6: this.appView = new WebView(this);
7:
8:
9: if (Build.VERSION.RELEASE.startsWith("1.")) {
10: this.appView.setWebChromeClient(new GapClient(this));
11: }
12: else {
13: this.appView.setWebChromeClient(new EclairClient(this));
14: }
15:
16: settings.setJavaScriptEnabled(true);
17:
18: String url = getStringProperty("url", null);
19: if (url != null) {
20: System.out.println("Loading initial URL=" + url);
21: loadUrl(url);
22: }
23: }
24: public void loadUrl(String url)
25: {
26: this.val$me.appView.loadUrl(this.val$url);
27: }
28: }
如上可见,DroidGap在初始化时调用WebView支持JS脚本解析,在loadUrl中,也是经过一些判断,最终也是调用WebView的loadUrl来实现最终功能。
下面就该具体的通信过程了,以DirectoryListPlugin插件的应用为例子,这个事PhoneGap自己提供的自定义插件开发的示例,用户在页面开发中代码如下:
1: <script type="text/javascript" src="phonegap.js"></script>
2: <script type="text/javascript" src="directorylisting.js"></script>
3: <script type="text/javascript" >
4: document.addEventListener('deviceready', function () {
5: var btn = document.getElementById("list-sdcard");
6: btn.onclick = function () {
7: DirectoryListing.list("/sdcard",
8: function (r) { printResult(r) },
9: function (e) { alert("error") }
10: );
11: }
12: btn.disabled = false;
13: }, true);
14: </script>
1: // directorylisting.js
2: var DirectoryListing = {
3: list: function(directory,successCallback, failureCallback) {
4: return PhoneGap.exec(successCallback, //Success callback from the plugin
5: failureCallback, //Error callback from the plugin
6: 'DirectoryListPlugin', //Tell PhoneGap to run "DirectoryListingPlugin" Plugin
7: 'list', //Tell plugin, which action we want to perform
8: [directory]); //Passing list of args to the plugin
9: }
10: };
如上当脚本调用DirectoryListing.list,最终是调用的PhoneGap.exec函数来进行的具体实现,接下来要出场的是PhoneGap.js这个文件了,看实现:
1: PhoneGap.exec = function(success, fail, service, action, args) {
2: try {
3: var callbackId = service + PhoneGap.callbackId++;
4: if (success || fail) {
5: PhoneGap.callbacks[callbackId] = {success:success, fail:fail};
6: }
7:
8: var r = prompt(PhoneGap.stringify(args), "gap:"+PhoneGap.stringify([service, action, callbackId, true]));
9: } catch (e2) {
10: console.log("Error: "+e2);
11: }
12: };
这个脚本其实我也不太理解,哎,没文化真可怕啊,只看见五个参数,两个callback回调,一个类指针,一个类函数指针(允许我用指针来说吧,顺口一点),一个用户参数,然后里面的核心函数就是皮肉,prompt()这时候网上给的资料让我理解了:WebChromeClient提供了一个onJsPrompt方法,这个方法是当web端调用prompt方法时就会调到。于是乎,它就把这个方法给改了,改成Android向Web端暴露的接口,当Web要调用任何Android(Java)端的方法时,就调prompt,onJsPrompt被调后,它再去解析参数来代理后续的行为。这时,它就主要是调用Plugin,通过Plugin来满足Web端的需求。时序图如下图所示:
换句话说,WebKit的这个函数,接下来会调用onJsPrompt()函数,onJsPrompt()调用exec函数,那让我们来看一下他们又做了什么事情,依次代码如下:
1: public class DroidGap extends PhonegapActivity
2: {
3: public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result)
4: {
5: String r = DroidGap.this.pluginManager.exec(service, action, callbackId, message, async);
6: }
7: }
8:
9: public final class PluginManager
10: {
11: public String exec(String service, String action, String callbackId, String jsonArgs, boolean async)
12: {
13: String clazz = (String)this.services.get(service);
14: if (isPhoneGapPlugin(c)) {
15: IPlugin plugin = addPlugin(clazz, c);
16: if (runAsync)
17: {
18: Thread thread = new Thread(new Runnable(plugin, action, args, callbackId, ctx)
19: {
20: public void run() {
21: try {
22: PluginResult cr = this.val$plugin.execute(this.val$action, this.val$args, this.val$callbackId);
23: int status = cr.getStatus();
24:
25: if ((status != PluginResult.Status.NO_RESULT.ordinal()) || (!(cr.getKeepCallback())))
26: {
27: if ((status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal())) {
28: this.val$ctx.sendJavascript(cr.toSuccessCallbackString(this.val$callbackId));
29: }
30: else
31: {
32: this.val$ctx.sendJavascript(cr.toErrorCallbackString(this.val$callbackId)); }
33: }
34: } catch (Exception e) {
35: PluginResult cr = new PluginResult(PluginResult.Status.ERROR, e.getMessage());
36: this.val$ctx.sendJavascript(cr.toErrorCallbackString(this.val$callbackId));
37: }
38: }
39: });
40: thread.start();
41: return "";
42: }
43:
44: cr = plugin.execute(action, args, callbackId);
45:
46: if ((cr.getStatus() == PluginResult.Status.NO_RESULT.ordinal()) && (cr.getKeepCallback()))
47: return "";
48: }
49: }
50: }
上面的代码简单介绍如下,当你轻轻在界面点击一个按钮,触发js事件时,PhoneGap脚本调用prompt函数,这会促发DroidGap的onJsPrompt,此函数最主要的作用是调用PluginManager的exec函数,这样将具体的js函数通过插件Manager来指定具体的Plugin来执行,这在PluginManager中通过Hash字典通过service参数获取具体的Plugin插件,专业的人做专业的事情嘛,这时候Plugin就通过execute来调用系统API,达到js与系统级别之间的通信。
另外,还有一个返回值问题的考虑,如果是同步,那很自然的通过返回的PluginResult来返回,而如果是异步方式,这就需要注意代码中Thread的应用,这时候PhoneGap创建了一个线程,来专门用于获取返回值,这个线程不停的Run,直到获取到返回的结果,然后以sendJavascript的方式回调给js,从而完成java通信js的过程。
另外,这个sendJavascript是干什么用的吗,呵呵,一看就是发送给Javascript的消息,这个一看就知道了,这里只是突然想到了MFC下面的经典面试题:PostMessage和SendMessage有什么不同,呵呵,SendMessage只是抛消息而不负责事件的返回就直接走人,而post则需要等待一个结果才负责人的闪人,说不定写sendJavascript接口这个人当时应该也是这么想的吧,可以通过是否有postJavascript这个接口来验证:)
这样,PhoneGap整个的过程都已经结束,其实总结下来其技术看起来确实很有新颖,但是本质看来并无什么惊叹之处,都是一些很实用直接的方法,当然这帮哥们应该是对Java客户端技术和HTML很熟悉的,但就代码而言还是很专业的,无论是质量还是设计。当然,其最难的部分确实是异步机制的处理,通过创建Thread的方式来实现,因为我对Java线程run运行机制不是很了解,直观来看这种方式在CS开发上没有问题,但是这样实现有点简单零散,感觉如果有观察者模式的方式来进行管理,代码上会更优雅一些,但本身返回值就是一个JSONArray,这样做是否有点小题大做,这也说不清楚,毕竟PhoneGap要考虑这么多移动平台的Native Code而非Android一个平台。
总结
通过如上的整个过程,个人感觉应该能对PhoneGap的内部实现机制有一个大概的了解,学以致用,如果从结果的角度来考虑,我们能对是否采用PhoneGap开发有一个简单的判断吧。
首先要承认,PhoneGap这样的实现,自己封装了一大堆,确实在效率上是有损失的,这点没办法,如果直接调用WebView,小打小闹还没问题,如果真要有规模,没有设计是不行的,这让我想到了OpenGL,有了OpenGL为啥还有OGRE和OSG以及Unity3D,直接用OpenGL属于玩死自己的一种做法,呵呵。
其次,你要评估一下,如果你自己做封装,你是否有足够的能力做到差不多的水平,坦白说,PhoneGap这套东西还是有积极成分的,特别随着移动平台和浏览器技术的发展,这种让用户专注页面开发的方式还是有一些人性化的
然后,PhoneGap这种Plugin的方式确实具有通用性,无论什么系统函数,在这种应用下都可以封装,即使是C++的接口,你都可以通过JNI封装成Java后,在通过这种技术来进行展现,比如大数据,渲染,三维等高性能的核心,技术上都可以走通,但也有接口封装繁杂,对开发人员要求较高,调试等问题,另外,还需要将C++面向对象的封装成JNI面向过程的,然后在封装成Java面向过程,然后在封装成JS面向过程,然后再通过原型封装成面向对象的,呵呵,这下终于圆满了,这种体系产品不是随便一个公司能搞定的。
最后,就是开发人员的积累,如果是BS开发丰富的,这种方式还是有很多积极成分的,专注Web技术,将产品做好,底层的效率,大数据这些问题如果可以接受,这种损失也是可行的,如果是CS开发,这种技术就有点过于先进了,毕竟本身这种技术归类于Web比较合适。要保证开发者用的顺手吧。
当然还有产品本身的定位,技术是死的,人是活的,产品是给人用的,结合目前的现状,发挥自己的有点,从产品设计上避免自己的缺点,这就需要综合考虑。
个人总结,PhoneGap这种方式很适合作为Web产品的App移植通用解决方案,除非你有足够的能力来搞定Native Code,除非你的产品对Native Code要求足够的简单,除非你不做App应用,否则在技术可行性上就没有更好的解决方案。
另外感慨技术发展的太快了,这种快,有时候让人觉得兴奋,可以很轻松的做很多以前看似不可能的事情,有时候也让人觉得失落,因为有些发展或多或少有点肤浅,有种过眼云烟的味道。
发表评论
-
Android APK 签名比对
2014-04-10 14:11 666发布过Android应用的朋友 ... -
Android小知识点
2014-04-10 09:45 7101、 最近翻看以前的项目时候,想更改下布局文件,谁知道就改了 ... -
Android 获取基站信息
2013-10-18 10:39 1058Android 基站分CdmaCellLocation和Gsm ... -
Android 打开PDF,PPT,WORD,EXCEL,CHM,HTML,TEXT,AUDIO,VIDEO 格式文件代码
2013-03-15 16:42 1803import android.app.Activity; im ... -
Android平板上开发App的准则
2013-03-14 11:38 11101、保证符合App的通用开发准则 在谈Android平板A ... -
android程序发布时的常用工具
2012-07-12 14:38 10581.为应用程序设定版本,在应用程序清单文件中设置。 ... -
java CA证书相关操作,Android,java
2012-06-11 16:16 3473一:需要包含的包 import java.securi ... -
android播放多媒体的两种方式
2012-05-19 21:42 1038转载,原文地址:http://blog.csdn.net/xi ... -
android通过NFC读写数据
2012-05-17 15:56 4141/* * Writes an NdefMessage to a ... -
NFC相关研究
2012-05-15 14:07 1166NFC概述 NFC是短距离的无线通信,通常距 ... -
Android 面试题
2012-05-15 14:05 993Android 面试题 经典 1、 Android dvm的进 ... -
生成android使用的BKS证书
2012-05-10 12:21 2899生成android使用的BKS证书 android 系统中 ... -
windows+eclipse+cygwin+cdt+ndk
2012-03-07 10:34 1012一:什么是NDK? NDK 提 ... -
在android2.1如何实现对ssl的无证书访问?(转)
2012-03-01 17:25 2631在网上看了,httpchlent的 ... -
Android系统目录结构详解(转)
2012-03-01 10:34 1138Android系统基于linux内核、JAVA应用,算是一 ... -
Android开发规范(转)
2012-03-01 10:20 827Android开发规范 一、Andr ... -
如何让Android程序支持安装到SD卡(APP2SD)
2012-02-29 15:19 1005Android系统在2.1版本之前,应用程序是只能安装到机身内 ... -
生成android的bks证书
2011-07-14 15:40 3329生成android的bks证书 pushd %CD% cd / ... -
系统文件夹功能详谈
2011-07-13 17:46 958【文件夹功能简介】 \system\app 这个里面主要存放 ... -
在Android上实现SSL握手
2011-07-08 09:05 1088Android的私钥和信任证书的格式必须是BKS格式的,通过配 ...
相关推荐
【标题】:“Android + PhoneGap + jQuery Mobile” 这个项目标题揭示了一个使用三种技术栈构建的移动应用程序:Android、PhoneGap和jQuery Mobile。Android是Google主导的开源操作系统,主要用于智能手机和平板...
在本篇《Android PhoneGap源码详解(二)》中,我们将深入探讨PhoneGap这一混合移动应用开发框架的内部机制,以及如何利用它来构建原生功能丰富的Android应用。PhoneGap是一个基于Apache Cordova的开源框架,允许...
在本文中,我们将探讨Android PhoneGap开发的关键知识点,以及在实际应用中可能遇到的问题和解决策略。PhoneGap是一个开放源代码框架,它允许开发者使用HTML、CSS和JavaScript来构建跨平台的移动应用程序,包括...
phonegap的API提供 震动 响铃 调用原生弹窗功能. 此篇文章以cordova 3.3版本编写 据我总结核心步骤: 创建工程 ; 下载插件 ;编译工程; 调用插件; 按照如下步骤就能生产出代码: 打开cmd 控制台 1 使用...
这个特定的“android PhoneGap 自定义插件”是关于在Android平台上扩展PhoneGap功能的一个实例,通过编写自定义插件,开发者可以调用Android的本地API,实现更丰富的功能,比如访问硬件设备、系统服务等。...
本篇将详细介绍如何在Android的PhoneGap项目中集成WebSocket插件。 首先,我们需要理解WebSocket的基本概念。WebSocket协议是HTTP的升级版,它在建立连接后保持持久化连接,使得数据可以在服务器和客户端之间双向...
Android PhoneGap实例是这个技术在Android平台上的具体应用,结合了Jquery Mobile这一强大的前端框架,为开发人员提供了一种高效、便捷的方式来创建原生感的移动应用。 PhoneGap的核心理念是“Write Once, Run ...
PhoneGap的核心理念是通过Web技术实现与移动设备硬件的交互,让开发者能够利用熟悉的Web开发技能开发出能在iOS、Android、Windows Phone等多平台运行的应用。 在"android之PhoneGap入门实例"中,我们首先需要了解...
android phonegap
在Android中使用PhoneGap去访问Html,方便开发
■ 用PhoneGap和它的NFC插件写Android上的NFC应用程序用例 ■ 深入了解NDEF结构:测试已有的写标签程序,然后写自己的标签程序 ■ 用PhoneGap事件监听器来监听和过滤NDEF消息 ■ 写一个完整的Android应用程序来控制...
总之,搭建Android+PhoneGap开发环境涉及安装JDK、Android Studio、Node.js和PhoneGap CLI,配置Android SDK,以及创建和运行第一个PhoneGap项目。掌握这个过程后,开发者就可以利用Web技术开发出能在Android平台上...
PhoneGap Android is an Android application library that allows for PhoneGap-based projects to be built for the Android Platform. PhoneGap-based applications are, at the core, an application written in...
在"android PhoneGap JQuery Mobile Demo"项目中,我们可以看到如何将这两个技术结合起来创建Android应用。以下是一些关于这个项目的知识点: 1. **PhoneGap的安装与配置**: 在开始之前,你需要安装Node.js环境,...
在移动应用开发领域,PhoneGap和Android是两种广泛使用的平台。PhoneGap是一种基于HTML5、CSS3和JavaScript的跨平台框架,它允许开发者使用Web技术创建原生移动应用。而Android则是Google主导的开源操作系统,提供了...
Android Phonegap+HTML5+CSS3+jQuer简单界面模板示例及源码.zip源码资源下载Android Phonegap+HTML5+CSS3+jQuer简单界面模板示例及源码.zip源码资源下载
在"android phoneGap 静态页面中简单的数据传递"这个主题中,我们将探讨如何在PhoneGap的Web视图中处理数据传递,这通常涉及到JavaScript对象、URL参数和本地存储等技术。 首先,PhoneGap应用中的静态页面本质上是...
- 使用Chrome开发者工具的Remote Debugging功能,可以在浏览器中实时查看和调试运行在Android设备上的PhoneGap应用。 - 使用PhoneGap Build服务,可以远程编译和分发应用到不同平台,而无需在每个平台上单独构建。...
自己从官网下载的源码,编译打包成功的android cordova 3.4 版本。具体方法可以参考本人博客: http://blog.csdn.net/likunwen_001/article/details/23292197