欢迎各位兄弟 发布技术文章
这里的技术是共享的
在phonegap中,通过android和iphone的webview实现对html网页的展示。本文的目的采用android中webview实现对html调用以及js调用java方法的。其实质也是phonegap插件的原理。
PhoneGap如何工作?
PhoneGap架构拥有强大的跨平台访问设备能力,但是其工作原理并不神秘,下面以iPhone和Android平台为例进行分析。
iPhone和Android平台共同点是都有内置的WebView组件,其具备两个特性:
1、WebView组件实质是移动设备的内置浏览器
WebView这个内置浏览器特性是Web能被打包成本地客户端的基础,可方便的用HTML5、CSS3页面布局,这是移动Web技术的优势相对于原生开发。
2、WebView提供Web和设备本地API双向通信的能力
PhoneGap针对不同平台的WebView做了扩展和封装,使WebView这个组件变成可访问设备本地API的强大浏览器,所以开发人员在PhoneGap框架下可通过JavaScript访问设备本地API。
明白以上两个特性,参照下面PhoneGap与设备本地API通信图,一个成熟的PhoneGap技术客户端运行状况如下:
应用运行在WebView组件上 —》 通过PhoneGap在各平台的扩展 —》 最终访问设备本地资源
WebView可以显示Web页面,并且,可以像普通的View那样应用到任何布局中。
WebView提供了两个方法用于显示网页的内容:loadUrl()用于加载一个指定的URL,而loadData()则用于直接向WebView写入指定的HTML文本。此外,loadDataWithBaseURL()和loadData()类似,但是,如果写入的HTML文本包含图片、CSS等外部资源的相对链接时,WebView就会根据指定的baseURL来正确地加载这些资源,比如:"file:///android_asset/", "<html><body background="lenovo.png">"这个方法正好符合我们显示Item的需求。
WebView的属性:Android中专门通过WebSettings来设置WebView的一些属性、状态等。可以通过WebView.getSettings()得到这个设置。
WebViewClient:专门辅助WebView处理各种通知、请求等事件的类,可以通过WebView的一个
setWebViewCilent()方法来指定一个WebViewClient对象。
如:setWebViewClient(new WebViewClient(){
@Override public boolean shouldOverrideUrlLoading(此方法:控制新的连接在当前WebView中打开)});
如果你在HTML中添加了一些JavaScript,你会发现WebView并不会执行JavaScript,这是因为WebView默认关闭了JavaScript功能。为了能执行JavaScript,需要在加载任何URL或写入HTML文本前开启JavaScript,代码如下:
WebView web = (WebView) this.findViewById(R.id.webview);
web.getSettings().setJavaScriptEnabled(true);
WebChromeClient:辅助WebView处理JavaScript的对话框、网站图标、网站Title、加载进度等
我们还可以向WebView添加更多的代码,甚至可以监听和控制WebView执行JavaScript
为了让Webview从apk载文件中加载assets,Android SDK提供了一个schema,前缀为file:///android_asset/WebView遇到这样的schema,就去当前包中的assets目录中找内容。
如:file:///android_asset/demo.html
一个简单的列子(不完整,主要说明使用WebViewClent控制新连接在当前WebView中打开):
WebView browser=(WebView)findViewById(R.id.webkit);
browser.setWebViewClient(genWebViewClientForURL());
WebSettings s = browser.getSettings();
s.setSaveFormData(false);
s.setSavePassword(false);
s.setUseWideViewPort(true);
s.setJavaScriptEnabled(true);
s.setLightTouchEnabled(true);
private WebViewClient genWebViewClientForURL() {
return new CiHaiWebViewClient();
}
private class CiHaiWebViewClient extends WebViewClient
{
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
String strURL = "";
try {
strURL = URLDecoder.decode(url.replaceAll("fake://wistron/", ""), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (strURL.equals(STR_PRE))
{ …… } else if (strURL.equals(STR_NEXT)) { …… } else { …… }
}
有代码:browser.loadDataWithBaseURL("fake://wistron/", htmlText, "text/html", "utf-8", null);
这里的baseUrl:fake://wistron/故这里需要WebViewClient 的shouldOverrideUrlLoading()方法,且要实现URLDecoder.decode(url.replaceAll("fake://wistron/", ""), "utf-8");
目录结构如下:
android的类如下:
html代码如下:
转载自IBM中国开发网站:
http://www.ibm.com/developerworks/cn/opensource/os-cn-android-phonegap/#major1
PhoneGap 是利用 HTML 来开发移动应用的一个开放源代码的框架,利用它就可以仅仅使用 HTML 和 Javascript 实现一次编写,多个移动平台运行的应用。目前已经可以支持 Android、iOS、BlackBerry 等 6 个平台。在完成这篇短文的过程中,PhoneGap 正式发布了 1.0.0 版本。因而短文中的版本使用的是 1.0.0rc2 版本的代码。
PhoneGap 在 Android 平台上的实现的框架是利用了在本地代码和浏览器间建立中间层来实现对 Java 代码的隔离。利用浏览器的接口和网络 socket 接口实现数据的通讯。因此在对 PhoneGap 框架的理解上,需要从 Java 端和浏览器端两部分来分别学习。
关于 Android 开发环境和 PhoneGap 开发环境的搭建,请参考参考资料中的开发者网站实施。
Java 端作为后台调用 Android 本地 SDK 的接口,主要实现了如下的功能:
而这三部分的功能对应到代码中则是如下的几个重要的 Java 类:
因此我们需要依次来了解这几个重要的 Java 类的具体实现,这样才可以对 PhoneGap 在 Android 上的体系有一个很好的了解。
当我们完成一个基本的 PhoneGap 的示例后,我们就会发现,在使用 PhoneGap 进行开发的手机应用中,第一步就是将继承关系 extends Activity 修改为 extends DroidGap。因此,DroidGap 是整个应用开始的地点,首先需要了解 DroidGap 的内容。
在源码中可以看到 DroidGap 继承自 PhonegapActivity,而 PhonegapActivity 是一个抽象类,继承自 Activity,但是具体的实现都是集中在 DroidGap 类中。
因此,我们继续回到 DroidGap 类中,当我们知道 DroidGap 类也是一个 Acitvity 后,就会明白,DroidGap 在 onCreate 方法中实现了整个类最初的一些操作,代码的实现则集中在 onCreate() 和 init() 这两个方法中,具体的流程如下:
设置 WebView 的代码都集中在 init() 方法中,其中一个值得注意的工作是设置 WebChromClient。代码中提供了一个继承自 WebChromClient 的类,重写了其中的 onJsAlert,onJsConfirm,onJsPrompt 等方法。而在 onJsPrompt() 方法中,实现了 PhoneGap 中 Java 端和浏览器端通讯的关键一步。因此,值得我们专门的关注其代码,具体的功能实现代码如下,我们已经过滤了一些错误处理、安全验证等等可能会妨碍我们对重要的功能实现理解的代码:
if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) { JSONArray array; try { array = new JSONArray(defaultValue.substring(4)); String service = array.getString(0); String action = array.getString(1); String callbackId = array.getString(2); boolean async = array.getBoolean(3); String r = pluginManager.exec(service, action, callbackId, message, async); result.confirm(r); } catch (JSONException e) { e.printStackTrace(); } } // Polling for JavaScript messages else if ...... |
在此,我们就可以明白,实现 JavaScript 与 Java 端通讯的原理是 JavaScript 利用 prompt 来传递调用信息的数据,在 onJsPrompt 中,重写的方法截获了这些数据,在完成了对数据格式等等分析后,按照要求进行具体的调用。而具体的调用利用是的 PluginManager 类,这将在随后说明。
在完成 WebView 的设置后,再将 PhoneGap 和 js 绑定,实现 js 与 PhoneGap 的通讯。在这一步中主要的内容就是实例化了 CallbackServer 和 PluginManager 两个类。因此,更多的内容会在随后对这两个类的介绍中再说明。
LoadUrl 方法也是实现一个 PhoneGap 示例后很熟悉的方法,在 loadUrl 中,完成基本的字符串处理后,主要利用 runOnUiThread 开始处理,其中第一步检测是否需要提供载入的显示,之后使用 WebView 的 loadUrl 载入内容,并且设置超时时间。
至此,Java 端的准备工作也就完成,实现了 UI 的载入。可以看到,在 DroidGap 类中最重要的一个部分就是截获 JavaScript 的 prompt 数据。这是实现浏览器端和 Java 端通讯的基础。
Plugin 是一个抽象类,实现了 IPlugin 接口,PhoneGap 中利用 Android SDK 实现逻辑代码的途径就是通过继承 Plugin 来实现。应此 Plugin 本身非常简单,最主要的部分是提供一个 execute 方法,如下:
public abstract PluginResult execute(String action, JSONArray args, String callbackId); |
Plugin 的实现中的重要逻辑在其中来实现,它返回的是一个 PluginResult 对象,这个对象主要负责传递数据信息,包括的成员主要如下:
private final int status; |
用于返回状态,status 与一个 enum 类型相关,用于标注插件的执行结果如何。
private final String message; |
用于返回数据信息。
PluginManager 事实上是 DroidGap 类和具体的继承自 Plugin 的插件的联系纽带,由它来寻找和载入插件,并且调用。
首先,PluginManager 在构造函数中,调用了 loadPlugins 方法,该方法负责解析 xml 配置文件,对应每个 plugin,调用一次 addService 用于注册插件,具体的注册地点,是通过维护一个 HashMap<String, String> 来实现,分别保存 serviceType, className。在成员中,有另外一个 HashMap<String, IPlugin> 用于实现 className 和 IPlugin 类的绑定。这样就实现了 serviceType 和 IPlugin 的对应。
在 PluginManager 中,执行插件的方法 exec 是主要功能,我们将会详细说明一下这个方法的过程:
执行结果的转换则是利用 PluginResult 中的一些 to***String 方法。这些方法返回了一个 String 类型,其中包括了具体的 JSON 对象和 callbackId,具体的形式则是一个 js 代码,用于前端的调用。
CallbackServer 实现了 Runnable 接口,具体的功能就是维护一个数据的队列,并且建立一个服务器,用于 XHR 的数据传递,对数据的队列的维护利用的是 LinkedList<String>。
由于实现的是 Runnable 接口,在 CallbackServer 中,最主要的方法就是 run() 方法,run() 方法的具体内容简介如下:
之后就是对队列维护的方法,这时理解之前的 sendJavaScript 则很简单,该方法与 getJavaScript 相反,一个是从 LinkedList 中取出 js 代码,一个则是加入。
综上,CallbackServer 实现的是两个功能,一个是 XHR 的 SocketServer,一个是对队列的维护。而完成 CallbackServer 类的说明后,Java 端的主要功能也都说明完毕。随后会说明浏览器端的说明,在完成浏览器端说明后,我们就会明白整个 PhoneGap 框架运行的原理。
浏览器端的 JavaScript 代码较多,但是实现其中核心功能的代码都在 PhoneGap 类中,其他的都是 PhoneGap 框架中自带的一些 Plugin 的 JavaScript 代码。
在浏览器端,其中一个最重要的函数就是 PhoneGap.exec 函数,因为大部分的函数调用,最终都会在这里实现与本地 Java 端的数据通讯,转而去调用 Java 端的 Plugin 来实现具体的执行。主要的内容如下,我们已经去除一些非功能的错误处理代码段,集中于具体的功能实现:
var callbackId = service + PhoneGap.callbackId++; if (success || fail) { PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; } var r = prompt(PhoneGap.stringify(args), "gap:"+ PhoneGap.stringify([service, action, callbackId, true])); // If a result was returned if (r.length > 0) { eval("var v="+r+";"); // If status is OK, then return value back to caller if (v.status === PhoneGap.callbackStatus.OK) { ...... }else if ...... } |
在以上标红加粗的代码段上,我们可以看到,调用的是 prompt 方法 , 我们在之前特别提到了,PhoneGap 在 DroidGap 类中,继承了一个 WebChromeClient
,其中重写了方法 onJsPrompt,这样在 Java 端,就截获了浏览器端的调用。现在可以回到之前的 Java 端的说明,查看之前列出的 onJsPrompt 代码,联系之前的 Java 端的介绍,就会觉得豁然开朗了,剩下了流程也就顺理成章了。通过 r 的返回值,就可以轻松的返回数据给浏览器端了。
这时我们会有一个好奇,那就是 CallbackServer 的 SocketServer 的作用在哪里,因为至此我们已经看到了整个数据在浏览器端和 Java 端的通讯,那么这样的一个 SocketServer 又是提供的什么数据通讯呢?
显然,在 PluginManager 中的 exec
中,我们会返回执行获得的数据,但是,在使用异步调用的时候,立即返回的则是一个空字符串,也即没有数据返回,之后插件会在一个新的线程中执行,等到异步的插件执行的结果返回数据后,会放入到队列中,等待 XHR 的获得。而 XHR 的调用是在 JavaScript 代码初始化中开始调用的,其中由于 JsCallBack 函数有使用轮询保证自己循环执行。这样就可以不断的获得异步数据。
掌握了上文中对 PhoneGap 框架的分析,我们就可以通过自己实现相关的插件来完成自己希望实现的功能,这样就可以不断丰富自己应用的功能。我们假设开发者已经熟悉了 PhoneGap 和 Android 的基本开发知识,Android 和 PhoneGap 开发环境搭建已经完成。
在具体的 Plugin 实现中,要分两个部分来实现,Java 代码和 JavaScript 代码,就此流程,我们来通过一个简单的例子来看一下如何具体的完成 Plugin 的实现。
在 Java 端,我们要继承一个 Plugin 类,实现其中的 execute 方法,在这里,我们省略具体的功能逻辑。
在 JavaScript 端,我们要利用现有 PhoneGap.exec 来调用具体的 Java 端,而且需要注意的是要在 PhoneGap 中注册这一个插件,PhoneGap.addConstructor
就在 JavaScript 端调用 Java 端代码,完成添加,当然直接修改 XML 文件来实现也可以。
首先,完成 Java 端的代码实现。
我们实现一个简单的字符串回射程序,来模拟具体的逻辑,代码如下:
@Override public PluginResult execute(String action, JSONArray args, String callbackId) { PluginResult.Status status = PluginResult.Status.OK; PluginResult r=null; if (action.equals("sayHello")){ r = new PluginResult(status,"Hello"); }else if (action.equals("saySth")){ r = new PluginResult(status,args); } return r; } |
实现的功能如下:
之后,完成 JavaScript 代码
请注意 JavaScipt 代码是需要 HTML 代码包含后才能使用的:
var Hello=function(){ } Hello.prototype.saySth = function(msg, callback, fail) { return PhoneGap.exec(function(args) { callback(args); }, function(args) { if(typeof fail == 'function') { fail(args); } }, 'Hello', 'saySth', [msg]); } Hello.prototype.sayHello = function(callback, fail) { return PhoneGap.exec(function(args) { callback(args); }, function(args) { if(typeof fail == 'function') { fail(args); } }, 'Hello', 'sayHello',['sayHello']); } PhoneGap.addConstructor(function() { PhoneGap.addPlugin('hello', new Hello()); //PluginManager.addService("Hello","qj.Hello"); }) |
在这里我们需要有一个特别的说明:
在上述 JavaScript 代码中,我们注释了 addConstructor 函数中的一行,因为我们在开发过程中发现这条代码的执行在 Javascript 端没有被实现。
因此这段代码具体的执行则需要我们在 Java 端实现代码,那么为了实现这段 JavaScript 代码所做的补偿性的 Java 代码要在自己实现的 Java 端的 onCreate 函数中实现,内容是很简单的一句(当然,也可以通过修改 xml 文件来实现这一功能,上文提到过,PluginManager 类会扫描 xml 文件来依次以对应 xml 文件中的插件名作为参数执行这一调用):
this.pluginManager.addService("Hello", "qj.Hello"); |
注意第二个参数是实现的插件的代码的位置,Hello 插件的位置是“src.qj.Hello.java”,对应的位置信息就是去除 .java 后缀名后的位置信息,整个项目的组织结构如图。
至此,Hello 插件已经实现,如果需要调用,则 JavaScript 代码如下:
window.plugins.hello.sayHello(function(arg){alert(arg);}, function(arg){alert(arg);}); window.plugins.hello.saySth("Bingo",function(arg){alert(arg);}, function(arg){alert(arg);}); |
我们简单的将返回的数据通过 alert() 展示出来,插件的执行结果如下:
点击 SayHello,
点击 SayBingo
至此插件的开发已经完成,其中很大的篇幅都是通过对 PhoneGap 的框架的说明,让我们可以对插件的运行过程有一个更加底层的认识,我想这是比实现一个简单的插件更加重要的东西。如果读者需要了解更多的信息,请浏览参考资料中的开发者网站,获得更加实时的信息。
<!-- CMA ID: 812102 --><!-- Site ID: 10 --><!-- XSLT stylesheet used to transform this file: dw-document-html-6.0.xsl -->针对Phonegap开发中常用的DroidGap类继承自PhonegapActivity,PhonegapActivity继承自Activity。源代码如下:
在使用这个类启动Webapp移动项目时候可以配置许多东西,如下:
针对Phonegap开发中常用的DroidGap类继承自PhonegapActivity,PhonegapActivity继承自Activity。源代码如下:
DroidGap中可以配置的属性如下:
Properties: The application can be configured using the following properties:
//加载时候加载对话框的信息
// Display a native loading dialog when loading app. Format for value = "Title,Message".
// (String - default=null)
super.setStringProperty("loadingDialog", "Wait,Loading Demo...");
//加载对话对话框
// Display a native loading dialog when loading sub-pages. Format for value = "Title,Message".
// (String - default=null)
super.setStringProperty("loadingPageDialog", "Loading page...");
// Cause all links on web page to be loaded into existing web view,
// instead of being loaded into new browser. (Boolean - default=false)
super.setBooleanProperty("loadInWebView", true);
//加载相关的动画信息
// Load a splash screen image from the resource drawable directory.
// (Integer - default=0)
super.setIntegerProperty("splashscreen", R.drawable.splash);
//设置默认的背景色
// Set the background color.
// (Integer - default=0 or BLACK)
super.setIntegerProperty("backgroundColor", Color.WHITE);
//设置超时时间
// Time in msec to wait before triggering a timeout error when loading
// with super.loadUrl(). (Integer - default=20000)
super.setIntegerProperty("loadUrlTimeoutValue", 60000);
//设置请求错误时候的提示页面
// URL to load if there's an error loading specified URL with loadUrl().
// Should be a local URL starting with file://. (String - default=null)
super.setStringProperty("errorUrl", "file:///android_asset/www/error.html");
//是否在后台运行的功能
// Enable app to keep running in background. (Boolean - default=true)
super.setBooleanProperty("keepRunning", false);
Phonegap.xml的配置如下:
PhoneGap uses a configuration file at res/xml/phonegap.xml to specify the following settings.
//允许phonegap访问的路径和域
Approved list of URLs that can be loaded into DroidGap
<access origin="http://server regexp" subdomains="true" / >
//日志的模式
Log level: ERROR, WARN, INFO, DEBUG, VERBOSE (default=ERROR)
<log level="DEBUG" />
Phonegap plugins:
PhoneGap uses a file at res/xml/plugins.xml to list all plugins that are installed.
Before using a new plugin, a new element must be added to the file.
name attribute is the service name passed to PhoneGap.exec() in JavaScript
value attribute is the Java class name to call.
<plugins>
<plugin name="App" value="com.phonegap.App"/>
...
</plugins>
评论
谢谢,分享,沟通交流
我想准确的判断页面加载是否完成,不知是否可有方法,不想延迟6秒
页面加载与网络有关,目前暂时没有相关的方法。
谢谢,分享,沟通交流
我想准确的判断页面加载是否完成,不知是否可有方法,不想延迟6秒
谢谢,分享,沟通交流
来自 http://topmanopensource.iteye.com/blog/1559276
phonegap底层原理学习和研究(四)
在phonegap的android开发中主要通过WebView显示相关的本地页面。针对WebView设置的代码如下:
//创建相关的Web容器
this.appView = new WebView(DroidGap.this);
this.appView.setId(100);
//设置WebView的布局
this.appView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT,
1.0F));
//获取WebView的WebSetting的几个方法以便于后面通过反射注入相关的存储
WebViewReflect.checkCompatibility();
this.appView.setWebChromeClient(new GapClient(DroidGap.this));
this.setWebViewClient(this.appView, new GapViewClient(this));
this.appView.setInitialScale(100);
this.appView.setVerticalScrollBarEnabled(false);
this.appView.requestFocusFromTouch();
// Enable JavaScript
WebSettings settings = this.appView.getSettings();
//启用js脚本的运行
settings.setJavaScriptEnabled(true);
//js是否可以打开窗体
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
// Enable database
settings.setDatabaseEnabled(true);
String databasePath = this.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setDatabasePath(databasePath);
// Enable DOM storage
WebViewReflect.setDomStorage(settings);
// Enable built-in geolocation
WebViewReflect.setGeolocationEnabled(settings, true);
// Create callback server and plugin manager
this.callbackServer = new CallbackServer();
this.pluginManager = new PluginManager(this.appView, this);
// Add web view but make it invisible while loading URL
this.appView.setVisibility(View.INVISIBLE);
root.addView(this.appView);
评论
来自 http://topmanopensource.iteye.com/blog/1559312