Android开发中java与javascript交互:PhoneGap插件vs addJavascriptInterface

1.        前言

在《用PhoneGap+jQueryMobile开发Android应用实例》中,我们讲到PhoneGap(以下称Cordova)开发环境的搭建,以及如何整合出一个基本的Android应用框架(并给出了范例代码)。于是乎,我们便开始日夜兼程,披星戴月的炮制我们的第一个手机应用了。

但实际上,除了常见的API调用规范(有且仅有自查手册一途)引起的问题之外,我们仍然会遇到其他形形色色的各种问题。那么在这篇文章中,我们谈谈java与js之间的交互问题(哦,目前仅关注Android,所以只能谈java了)。当然,二者之间的交互目的,原因会有种种不同,但应该还是以发挥语言各自的优势,提供接口给对方调用的意图居多。

我们知道,在Android平台下,Cordova是通过内置WebKit内核的方式来实现界面容器的(事实上,在其他平台也是如此)。我们也同样知道,Cordova是一个桥接框架,其目的就是为原生API和js建立桥接,互通有无的。为了便于我们扩展自己的应用,Cordova还提供了插件(PlugIn)机制(当然,我们还可以直接修改Cordova开源代码)。只要遵循一定的规则(恩恩,事实上这个规则很简单),就可以扩展出丰富的功能特效来。

2.        Cordova插件与 WebView.addJavascriptInterface

恩恩,本文的主题是java与js的交互(差点跑了)。刚提到的,Cordova有插件机制,可以通过插件的形式,实现java与js交互,为什么还要提到addJavascriptInterface?

Cordova插件确实可以实现二者的交互,而且是异步的,非常方便。但基于一些特殊的原因,例如:一个回调需要被多次调用(啊哈,或许是我太菜?使用PlugIn注册的回调都只能被调用一次)。又或者不想写插件,想直接点。

总之,插件也并不是时时处处都符合我们的需求(我们的欲望无穷大啊),总是要找办法解决,寻点不同的路出来(个人不是特别认同Cordova的Hack方式,遑论其插件;而且Cordova目前的状态有点怪异,版本更新是很快,但文档更新不同步)。要真正成熟,还是有一段路要走的。

addJavascriptInterface则是WebKit的原生API,属于WebView对象的公共方法,用于暴露一个java对象给js,使得js可以直接调用java方法。当然,我们要实现java与js的双向交互,还需要另一个方法loadUrl(同属于WebView对象,Cordova也是采用的这个方法调用js的)的配合。

当然,这两种方式互有优劣(只有实践时,才会明白啊)。Cordova插件的不足刚才已经提过;而addJavascriptInterface也有些问题,一是Android平台封装WebKit内核时,不同的版本中有些许不一致;其次,直接使用loadUrl加载js实在是让人头疼。

其实应该有更好的方法,比如扩展js引擎(我更喜欢这种方式),但这种方式相对而言,涉及的内容繁杂,暂时不纳入这次的话题。

3.        Cordova插件的实现

Cordova插件分为两个部分(额,Cordova本身也是分为两个部分的,别扭不?),一部分由java实现,另一部分由js实现。

1)        java部分

Cordova插件的java部分很简单,继承Cordova.Plugin,实现execute方法就可以了:

public classNotificationClient extends Plugin {

         private static final String TAG ="NotificationClient";

         private String callbackId ="";

         public PluginResult execute(Stringaction, JSONArray args, String callbackId) {

                   PluginResult.Status status =PluginResult.Status.OK;

                   if(action.equals("register")){

                            try {

                                     register(args.getString(0),args.getString(1));

                            } catch(JSONException e) {

                                     status =PluginResult.Status.JSON_EXCEPTION;

                            }

                   } elseif(action.equals("watch")) {

                            this.callbackId =callbackId;

                            PluginResult r = newPluginResult(PluginResult.Status.NO_RESULT);

                            r.setKeepCallback(true);

                            return r;

                   } else {

                            status =PluginResult.Status.INVALID_ACTION;

                   }

                   return newPluginResult(status);

         }

         public Object onMessage(String id,Object data) {

                   Log.d(TAG,"onMessage(" + id + ").");

                   if(id.equals("onClientNotification")){

                            if(!callbackId.equals("")){

                                     this.success("true",callbackId);

                            }

                   }

                   return data;

         }

         private void register(String username,String phone) {

                   Log.d(TAG,"register(" + username + ", " + phone + ").");

         }

}

嗯,就这样,作为一个Cordova插件java部分的范例,他已经完成了使命(原谅我为了节省篇幅,删掉了注释和空行;不必太多介怀,参考资源里有很多范例工程可以学习)。

不得不说,Cordova还是做了很多工作的,为了减轻插件开发的工作量,对js的调用进行了很多的包装(回头看看loadUrl是多么的贫瘠的时候,才会有如此感慨吧)。

2)        js部分

唉,让我浑身别扭的部分来了。说到js部分,我接触过的版本里(当然,我也仅仅接触过3个版本而已:1.0、2.0、2.1)已经有两种写法。嗯,从执行效果上来说,2.0是兼容1.0的写法的(哦哦,前提是我做了一些改动,虽然改动很小);美中不足的是,跟踪脚本时还是会报错,虽然不影响脚本的继续加载。

先来看看第一种写法吧(1.0的写法):

functionNotificationClient() { }

NotificationClient.prototype.register= function(userName, phone) {

         PhoneGap.exec(null, null,"NotificationClient", "register", [ userName, phone ]);

};

NotificationClient.prototype.watch= function(fn) {

         PhoneGap.exec(fn, null,"NotificationClient", "watch", []);

};

PhoneGap.addConstructor(function(){

         if(typeof navigator.notificationClient== "undefined")

                   navigator.notificationClient= new NotificationClient();

});

网上的教程都是这么弄的,事实上运行时会报错:找不到PhoneGap对象;更严重的是navigator.notificationClient在运行时根本无法访问。

当然,如果你改成这样:

// PhoneGap.addConstructor(function(){

         if(typeof navigator.notificationClient== "undefined")

                   navigator.notificationClient= new NotificationClient();

// });

程序是可以正常运行的,虽然仍然会报错。

OK,再来看看第二种(2.0的写法):

cordova.define("cordova/plugin/notificationClient",function(require, exports, module){

         var exec = require('cordova/exec');

         var NotificationClient = function() {};

         NotificationClient.prototype.register =function(userName, phone) {

                   exec(null, null,"NotificationClient", "register", [ userName, phone ]);

         };

         NotificationClient.prototype.watch =function(fn) {

                   exec(fn, null,"NotificationClient", "watch", []);

         };

         var notificationClient = newNotificationClient();

         module.exports = notificationClient;

});

if(!window.plugins) {

         window.plugins = { };

}

if(!window.plugins.notificationClient) {

         window.plugins.notificationClient =cordova.require("cordova/plugin/notificationClient");

}

恩,这种写法没有任何错误了,而且能正常运行,开心。

3)        注册插件

把插件写完之后,还需要注册,插件才能在Cordova下使用。找到工程目录下的res\xml目录,1.0打开plugins.xml文件,2.0打开config.xml文件,在plugins节点下加入:

<pluginname="NotificationClient"value="cn.yofang.mobile.NotificationClient"/>

至此,NotificationClient插件就可以在js中调用了。

1.0的用法:

navigator.notificationClient.register("azhi","15810108888");

2.0的用法:

window.plugins.notificationClient.register("azhi","15810108888");

4.        WebView.addJavascriptInterface实现

啊,终于到addJavascriptInterface了,每次文档写到一半左右都手酸呐(看文档的人是不是也暗叹了一声:终于来了)。

addJavascriptInterface比起Cordova插件来更加的简单,首先我们来定义一个类:

public classNotificationClient {

         private static final String TAG ="NotificationClient";

         private Context context = null;

         private CordovaWebView view = null;

         private String callback = "";

         public NotificationClient(Contextcontext, CordovaWebView view) {

                   this.context = context;

                   this.view = view;

         }

         public void register(String user,String mobile, String callback) {

                   Log.d(TAG, "register(user: " + user + ", mobile: " + mobile + ", callback:" + callback + " )");

                   this.callback = callback;

                   checkMessage();

         }

         public void checkMessage() {

                   SharedPreferences sp =context.getSharedPreferences("NotificationClient", 0);

                   int message =Integer.valueOf(sp.getInt("Message", 0));

                   Log.d(TAG,"checkMessage(): " + message);

                   if(message > 0) {

                            Editor editor =sp.edit();

                            editor.putInt("Message",0);

                            editor.commit();

                            newHandler().post(new Runnable() {

                                     public voidrun() {

                                               view.sendJavascript(callback);

                                     }

                            });

                   }

         }

};

这个类的意图很简单(嗯,跟上面Cordova插件的NotificationClient插件很相似对不对?):提供一个register方法供js调用,传入相应的参数增加了一个callback,在合适的时机(通过SharedPreferences检查Message标志,大于0则认为是合适的时机了),从java端调用这个callback(当然,代码里使用了SharedPreferences、Handler等其他的Android原生对象,大家暂时忽略就是)。

嚯,我是不是没有用loadUrl,而是用的sendJavascript?sendJavascript是Cordova对WebView封装后提供的方法,其实把那一句改成:

view.loadUrl("javascript:"+ this.callback);

效果是一样的(当然,如果你不是用Cordova,而是自己写的Activity,那么你就必须得这么写了)。

好了,类写完了,下面就应该把这个类暴露给js了:

appView.getSettings().setJavaScriptEnabled(true);     // 暴露之前,先开启javascript

appView.addJavascriptInterface(newNotificationClient(this, appView), "notificationClient");

嗯,这里用到了appView(DroidGap的成员变量),我们使用的Cordova嘛,所以用这个没有罪过的。如果是直接实现的Activity,就要自己内嵌WebView了,把appView改成自己的WebView对象即可。

         再就是js里的用法了:

window.notificationClient.register("azhi","15810108888","OnMessage();");

大家看到了,在js中调用时,还是比较方便的,不需要预先建立js类对象,通过addJavascriptInterface添加的对象直接就附加在window对象上了。但弊端也是很明显,看看我们的callback,是以代码形式传入的(当然了,其实是可以改良的,但今天就不聊这个了)。

         呵呵,稍微来点结束语:就这样吧,希望大家都有所收获。

5.        参考资源

PhoneGap插件范例:

https://github.com/phonegap/phonegap-plugins/tree/master/Android

addJavaScriptInterface应用范例:

http://www.codeproject.com/Articles/392603/Android-addJavaScriptInterface

WebKit API:

https://developer.android.com/reference/android/webkit/WebView.html

原文地址:https://www.cnblogs.com/javawebsoa/p/3006069.html