phoneGap简单分析

来源:互联网 发布:linux内核poll流程 编辑:程序博客网 时间:2024/06/06 08:35

想了解phonegap 开发大概流程,应该知道如下几点。

1  js 通过html prompt弹窗接口往anroid native 发送消息。

2  android 本地利用WebChromeClient 对象的 onJsPrompt函数截获html 弹窗的消息。

3  android本地截获到消息以后,通过Pluginmanager 把消息分发到目的插件,同时通过jsMessageQueue收集需要返回给js的数据。(异步处理)

4  如何实现把jsMessageQueue的数据返回到js

5  pluginmanager负责加载和管理所有plugin.

 

 

Js通过prompt函数 调用native代码

 

module.exports = {

    exec: function(service, action, callbackId,argsJson) {

        return prompt(argsJson,'gap:'+JSON.stringify([service, action, callbackId]));

    },

};

 

CordovaChromeClient继承 WebChromeClient 实现了 onJsPrompt函数

这个函数用来截获 js 发过来弹窗

请求中主要有以下 5种方式 消息类型:

1: gap(这个消息头 是调用android本地插件接口)

gap: 开头标志得知是调用本地插件请求,然后向 PluginManager 转发该请求。PluginManager 将会根据参数来查找并执行具体插件方法。

2: gap_bridge_mode:

//后面单独分析

3: gap_poll  //

Gap_poll   之前版本留下来的,目前视乎native没有做什么处理。

 

4: gap_init

5: 显示android Dialog

 

Js调用到 android native的流程如下:

1. cordova.exec( 

2.            successCallback, // 成功时回调js代码

3.             errorCallback, //失败时回调 js代码

4.            'CalendarPlugin', // android 本地插件java类名

5.            'addCalendarEntry', // with this action name 

6.            [参数

 

执行上面js 函数就会执行 

prompt(argsJson, 'gap:'+JSON.stringify([service, action,callbackId]));

 

执行这一句之后, native端CordovaChromeClient 类函数onJsPrompt 得到执行。

执行的消息头为 gap

 

执行android本地代码片段如下:

        if (reqOk && defaultValue !=null && defaultValue.length() > 3 &&defaultValue.substring(0, 4).equals("gap:")) {

            JSONArray array;

            try {

                array = newJSONArray(defaultValue.substring(4));

                String service =array.getString(0);

                String action = array.getString(1);

                String callbackId =array.getString(2);

                String r =this.appView.exposedJsApi.exec(service, action, callbackId, message);

                result.confirm(r == null ?"" : r);

            } catch (JSONException e) {

                e.printStackTrace();

                return false;

            }

        }

片段主要是解析jsonarray ,调用exposedJsApi.exec函数。

类 exposedJsApi  从名字上看就知道 暴露给js 的api(接口)

看看 appview (CordovaWebView)里面的如何初始化的。

 

exposedJsApi = newExposedJsApi(pluginManager, jsMessageQueue);

包含 pluginmanager 和 jsMessageQueue

 

Pluginmanager 是管理所有插件的, 也就是说js上调native 插件执行具体代码是有 它来分发的。

jsMessageQueue 从名字上看就知道, 是一个消息队列用来保存native端返回给js的消息的队列。

 

再考虑一个问题, 如果 ExposedJsApi 真的暴露给了js 是不是js 就可以通过这个类,来获得 jsMessageQueue里面的消息呢?

 

果然, cordovaWebView 有这么一个函数把ExposedJsApi 暴露给JS

    private void exposeJsInterface() {

        //这是一些判断

       this.addJavascriptInterface(exposedJsApi, "_cordovaNative");//暴露给js

    }

 

再看 ExposedJSApi.exec函数

这个函数主要是执行

pluginManager.exec(service,action, callbackId, arguments);

 

    public void exec(final String service,final String action, final String callbackId, final String rawArgs) {

        if (numPendingUiExecs.get() > 0) {//判断当前等待UI线程执行的插件个数 大于0就往主线程消息队列里面发送一个消息。

            numPendingUiExecs.getAndIncrement();//统计当前主线程消息队列插件的个数。增加一个

           this.ctx.getActivity().runOnUiThread(new Runnable() {

                public void run() {

                    execHelper(service, action,callbackId, rawArgs);

                    numPendingUiExecs.getAndDecrement();//统计当前主线程消息队列插件的个数。减少一个

 

                }

            });

        } else {//如果主线程队列里面没有消息,直接调用execHelper

            execHelper(service, action,callbackId, rawArgs);

        }

}

 

execHelper()函数如下:

private void execHelper(final String service, finalString action, final String callbackId, final String rawArgs) {

       CordovaPlugin plugin =getPlugin(service);//通过js传过来的名字找到对应的插件

        try {

           CallbackContext callbackContext = new CallbackContext(callbackId, app);

//这个对象一看就是callback 接口前面说了ExposedJSApi 把接口暴露给js 从jsmessagequeue队列里面取消息,谁往里面添加消息呢? 就是他了!

           boolean wasValidAction = plugin.execute(action,rawArgs, callbackContext);

这里就执行具体插件的 android本地代码了。

 

执行完成之后 客户端就会调用如下代码:

callbackContext.success(message);

 

sendPluginResult(newPluginResult(PluginResult.Status.OK, message));

    接下来调用WebView 接口

    public voidsendPluginResult(PluginResult result, String callbackId) {

       this.jsMessageQueue.addPluginResult(result, callbackId);

    }

webView调用jsMessageQueue.addPluginResult(result, callbackId);

  jsMessageQueue主要代码如下:

       JsMessage message = new JsMessage(result, callbackId);

//新建一个jsMessage 对象, 保存callbackID 和 native 回送的具体消息。

        if(FORCE_ENCODE_USING_EVAL) {

           StringBuilder sb = new StringBuilder(message.calculateEncodedLength() +50);

           message.encodeAsJsMessage(sb);

           message = new JsMessage(sb.toString());

        }

 

enqueueMessage(message);//加入到消息队列。

 

上面函数主要执行 queue.add(message);

private final LinkedList<JsMessage> queue = newLinkedList<JsMessage>();

这样往jsMessageQueue消息队列里面添加消息的过程就结束了!

 

Js怎么取jsMessageQueue消息队列里面的消息呢?

流程大概是这样, 前面说过exposedJSApi包含了jsMessageQueue同时又暴露给js, js就可以通过 调用 exposedJSApi.retrieveJsMessages来取消息。

所以你可以在 cordova.js中搜索 retrieveJsMessages 函数名,就能找到js相关的处理,

很遗憾js看不太明白。暂时不做分析,直接看android代码吧!

 

gap_bridge_mode:  作用是 js请求native调用js代码的一种方式,

好像很绕, 简单来说是:

Js 请求 native 处理 保存jsMessageQueue 消息队列里面的消息通过下面两种方式调用js 语句实现数据回传。

 

1           LoadUrlBridgeMode:(从名字看出端倪, loadUrl方式执行js语句)

 

WebView loadUrlNow 的接口实现调用js 把数据返回给js

代码如下:

String js = popAndEncodeAsJs();

webView.loadUrlNow("javascript:" + js);

 

2           PrivateApiBridgeMode:(从名字看出端倪, 私有接口方式执行js语句)看到这,我们能想到什么, OK, 就是利用反射机制

 

通过WebView 获得内部私有对象 webViewCore

通过 WebViewCore 获得 sendMessage()函数

然后:从jsMessageQueue 消息队列里面取出消息拼成JS 消息 调用 sendMessage 去执行 。

代码如下:

String js = popAndEncodeAsJs();

          Message execJsMessage = Message.obtain(null, EXECUTE_JS, js);

                     try {

                              sendMessageMethod.invoke(webViewCore, execJsMessage);

                     } catch (Throwable e) {

                            Log.e(LOG_TAG, "Reflection message bridge failed.",e);

                     }

第二种方式只适合在 Android 3.2.4 或以上,

 

具体执行流程:

前奏:

Js 端需要知道:

Cordova.js文件中有一个这样的函数:

    setNativeToJsBridgeMode: function(value) {

        prompt(value, 'gap_bridge_mode:');

},

 

Value 取值如下:

    nativeToJsModes = {

        POLLING: 0,

        LOAD_URL: 1,

        ONLINE_EVENT: 2,

        PRIVATE_API: 3

},

 

  对应 android端如下:

在NativeToJsMessageQueue类里面:有一个 数组

private final BridgeMode[] registeredListeners;

 

构造函数对数组的初始化如下:

        registeredListeners = newBridgeMode[4];

        registeredListeners[0] = null;  // Polling. Requires no logic.

        registeredListeners[1] = newLoadUrlBridgeMode();

        registeredListeners[2] = newOnlineEventsBridgeMode();

        registeredListeners[3] = newPrivateApiBridgeMode();

 

看到没js端value 的取值刚刚好和 android端对应不同的处理方式。

 

1: js 调用 setNativeToJsBridgeMode(value)指定一个处理类型 假设value = 1

  调用js弹窗函数 prompt(1, 'gap_bridge_mode:');

2:   CordovaChromeClient 在函数 onJsPrompt 拦截到这个窗口。

对应 gap_bridge_mode 调下面函数。

this.appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message));

 

3:  调用 exposedJsApi setNativeToJsBridgeMode(1) 前面说过 exposed包含 jsMessageQueue对象。 所以直接调用 这个对象的 setBridgeMode(1)

 

4: 接下来的代码就是从数组registeredListeners取出value对应的方式来处理 

Js消息。

 

           BridgeMode activeListener =registeredListeners[value];

           if (!paused &&!queue.isEmpty() && activeListener != null) {

                activeListener.onNativeToJsMessageAvailable();

           }

再往下就回到前面LoadUrlBridgeMode 和 PrivateApiBridgeMode 的具体处理了。

 

 

 

关于plugin (插件的加载过程)

前面说过,pluginManager 是管理所有 plugin的管理类。所有的plugin类定义在config.xml文件里面。其实加载plugin的过程就是 解析config.xml 的过程。

 

具体如下: 

1 在 CordovaWebView 构造函数中会调用初始化webView的函数setup()

2  在Setup 函数中会创建pluginManager = new PluginManager(this, this.cordova);

3  在用CordovaWebView 装载url 函数loadUrlIntoView中回到用 pluginManager.init()

4  pluginManagerinit函数中会调用loadplugin()函数,这个函数主要是解析config.xml文件把定义插件的信息 保存到pluginManager中

        PluginEntry entry = new PluginEntry(service, pluginClass, onload);

        this.addService(entry);

 

0 0