PhoneGap插件调用Java流程源码分析(一)

原创 2015年11月18日 14:44:55

                        PhoneGap插件调用Java流程源码分析

PhoneGap 简介

   PhoneGap是一个能够让Web开发者快速进行移动app开发的开源框架。

    PhoneGap主要涉及的技术包括HTML,CSS,JavaScript。

    PhoneGap在 Android 平台上的实现的框架是利用了在本地代码和浏览器间建立中间层来实现对 Java 代码的隔离。

Java 端介绍

   Java 端作为后台调用 Android 本地 SDK 的接口,主要实现了如下的功能:

        1.  建立通讯机制,提供接口给浏览器端,方便 JavaScript 进行调用。

        2.  数据队列的维护,以保证浏览器端的调用后产生的数据可以回送。

        3.  插件体系的建立,提供整个框架的可扩展性。

   而这三部分的功能对应到代码中则是如下的几个重要的 Java 类:

        1.CordovaActivityNativeToJspMessageQueue

        2.NativeToJspMessageQueue

        3.CordovaPlugin、PluginManager

    因此我们需要依次来了解这几个重要的 Java 类的具体实现,这样才可以对 PhoneGap 在 Android 上的体系有一个很好的了解。

    我的代码版本是 3.6.4.


CordovaActivity分析

   当我们完成一个基本的 PhoneGap 的示例后,我们就会发现,在使用 PhoneGap 进行开发的手机应用中,官方建议我们第一步就是将继承关系 extends Activity 修改为 extends DroidGap。因此,DroidGap 是整个应用开始的地点,首先需要了解 DroidGap 的内容。

   在源码中可以看到 DroidGap 继承自 CordovaActivity,而 CordovaActivity是一个抽象类,继承自 Activity,具体的实现都是集中在 CordovaActivity类中。

   DroidGap 中说:应用开发者应该继承这个类,不过也可以继承CordovaActivity,DroidGap在未来会被removed掉。
package org.apache.cordova;
/**
 * This used to be the class that should be extended by application
 * developers, but everything has been moved to CordovaActivity. So
 * you should extend CordovaActivity instead of DroidGap. This class
 * will be removed at a future time.
 */
@Deprecated
public class DroidGap extends CordovaActivity {

}
   再来看看CordovaActivity:

public class CordovaActivity extends Activity implements CordovaInterface {
    public static String TAG = "CordovaActivity";
    .........
 }
   CordovaActivity 继承于Activity,并实现了CordovaInterface接口。

   作为Activity,onCreate方法是我们第一个要关心的

@Override
    public void onCreate(Bundle savedInstanceState) {
        LOG.i(TAG, "Apache Cordova native platform version " + CordovaWebView.CORDOVA_VERSION + " is starting");
        LOG.d(TAG, "CordovaActivity.onCreate()");

        // need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
        loadConfig();
        if(!preferences.getBoolean("ShowTitle", false))
        {
            getWindow().requestFeature(Window.FEATURE_NO_TITLE);
        }
        
        if(preferences.getBoolean("SetFullscreen", false))
        {
            Log.d(TAG, "The SetFullscreen configuration is deprecated in favor of Fullscreen, and will be removed in a future version.");
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
        } else if (preferences.getBoolean("Fullscreen", false)) {
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
        } else {
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
        }

        super.onCreate(savedInstanceState);

        if(savedInstanceState != null)
        {
            initCallbackClass = savedInstanceState.getString("callbackClass");
        }
    }

    首先,调用loadConfig()加载配置文件。调用完loadConfig之后,控制页面显示,是否显示title,是否设置全屏等等

    loadConfig:主要是读取res/xml/config.xml文件,上代码:

    protected void loadConfig() {
        ConfigXmlParser parser = new ConfigXmlParser();
        parser.parse(this);
        preferences = parser.getPreferences();
        preferences.setPreferencesBundle(getIntent().getExtras());
        preferences.copyIntoIntentExtras(this);
        internalWhitelist = parser.getInternalWhitelist();
        externalWhitelist = parser.getExternalWhitelist();
        launchUrl = parser.getLaunchUrl();
        pluginEntries = parser.getPluginEntries();
        Config.parser = parser;
    }
    在loadConfig中获取一个xmlParser的解析器,调用parse()进行解析.
    public void parse(Activity action) {
        // First checking the class namespace for config.xml
        int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName());
        if (id == 0) {
            // If we couldn't find config.xml there, we'll look in the namespace from AndroidManifest.xml
            id = action.getResources().getIdentifier("config", "xml", action.getPackageName());
            if (id == 0) {
                LOG.e(TAG, "res/xml/config.xml is missing!");
                return;
            }
        }
        parse(action.getResources().getXml(id));
    }
       在这里就获取到一个res/xml/config.xml路径,开始解析:

首先看看config.xml长什么样子?

<?xml version='1.0' encoding='utf-8'?>
<widget id="com.example.hello" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <preference name="loglevel" value="DEBUG" />
    <feature name="Device">
        <param name="android-package" value="org.apache.cordova.device.Device" />
    </feature>
    <feature name="Notification">
        <param name="android-package" value="org.apache.cordova.dialogs.Notification" />
    </feature>
    <feature name="Report">
        <param name="android-package" value="net.xtion.crm.cordova.service.ReportService" />
    </feature>
    <feature name="HtmlPagePlugin">
        <param name="android-package" value="net.xtion.crm.cordova.service.HtmlPageService" />
    </feature>
    <name>HelloWorld</name>
    <description>
        A sample Apache Cordova application that responds to the deviceready event.
    </description>
    <author email="dev@cordova.apache.org" href="http://cordova.io">
        Apache Cordova Team
    </author>
    <content src="index.html" />
    <access origin="*" />
</widget>

      解析:

    public void parse(XmlResourceParser xml) {
        int eventType = -1;
        String service = "", pluginClass = "", paramType = "";
        boolean onload = false;
        boolean insideFeature = false;
        ArrayList<String> urlMap = null;

        // Add implicitly allowed URLs
        internalWhitelist.addWhiteListEntry("file:///*", false);
        internalWhitelist.addWhiteListEntry("content:///*", false);
        internalWhitelist.addWhiteListEntry("data:*", false);

        while (eventType != XmlResourceParser.END_DOCUMENT) {
            if (eventType == XmlResourceParser.START_TAG) {
                String strNode = xml.getName();
                if (strNode.equals("url-filter")) {
                    Log.w(TAG, "Plugin " + service + " is using deprecated tag <url-filter>");
                    if (urlMap == null) {
                        urlMap = new ArrayList<String>(2);
                    }
                    urlMap.add(xml.getAttributeValue(null, "value"));
                } else if (strNode.equals("feature")) {
                    //插件
                    //Check for supported feature sets  aka. plugins (Accelerometer, Geolocation, etc)
                    //Set the bit for reading params
                    insideFeature = true;
                    service = xml.getAttributeValue(null, "name");
                }
                else if (insideFeature && strNode.equals("param")) {
                    paramType = xml.getAttributeValue(null, "name");
                    if (paramType.equals("service")) // check if it is using the older service param
                        service = xml.getAttributeValue(null, "value");
                    else if (paramType.equals("package") || paramType.equals("android-package"))
                        pluginClass = xml.getAttributeValue(null,"value");
                    else if (paramType.equals("onload"))
                        onload = "true".equals(xml.getAttributeValue(null, "value"));
                }
                else if (strNode.equals("access")) {
                    String origin = xml.getAttributeValue(null, "origin");
                    String subdomains = xml.getAttributeValue(null, "subdomains");
                    boolean external = (xml.getAttributeValue(null, "launch-external") != null);
                    if (origin != null) {
                        if (external) {
                            externalWhitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
                        } else {
                            if ("*".equals(origin)) {
                                // Special-case * origin to mean http and https when used for internal
                                // whitelist. This prevents external urls like sms: and geo: from being
                                // handled internally.
                                internalWhitelist.addWhiteListEntry("http://*/*", false);
                                internalWhitelist.addWhiteListEntry("https://*/*", false);
                            } else {
                                internalWhitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
                            }
                        }
                    }
                }
                else if (strNode.equals("preference")) {
                    //配置
                    String name = xml.getAttributeValue(null, "name").toLowerCase(Locale.ENGLISH);
                    String value = xml.getAttributeValue(null, "value");
                    prefs.set(name, value);
                }
                else if (strNode.equals("content")) {
                    //开始的Url
                    String src = xml.getAttributeValue(null, "src");
                    if (src != null) {
                        setStartUrl(src);
                    }
                }
            }
            else if (eventType == XmlResourceParser.END_TAG)
            {
                String strNode = xml.getName();
                if (strNode.equals("feature")) {
                    pluginEntries.add(new PluginEntry(service, pluginClass, onload, urlMap));

                    service = "";
                    pluginClass = "";
                    insideFeature = false;
                    onload = false;
                    urlMap = null;
                }
            }
            try {
                eventType = xml.next();
            } catch (XmlPullParserException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

       回到onCreate,当解析完就会根据解析处理的配置进行设置。


     在PhoneGap中,我们通过调用一个CordovaActivity.loadUrl()来加载一个html页面.

           /**
	     * Load the url into the webview.
	     */
	    public void loadUrl(String url) {
	        if (appView == null) {
	            init();
	        }

	        // If keepRunning
	        this.keepRunning = preferences.getBoolean("KeepRunning", true);
	        if(this.splashscreen != 0)
	        {
	            this.appView.loadUrl(url, this.splashscreenTime);
	        }
	        else
	        {
	            this.appView.loadUrl(url);
	        }
	    }
     loadUrl的时候,先判断view是否为空,为空,就调用init方法。如果不为空,就设置一些参数,最后,调用appView.loadUrl来加载url,就显示网页了。
     进入init()看看:

public void init(CordovaWebView webView, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient) {
        LOG.d(TAG, "CordovaActivity.init()");

        appView = webView != null ? webView : makeWebView();
        if (appView.pluginManager == null) {
            appView.init(this, webViewClient != null ? webViewClient : makeWebViewClient(appView),
                    webChromeClient != null ? webChromeClient : makeChromeClient(appView),
                    pluginEntries, internalWhitelist, externalWhitelist, preferences);
        }

        // TODO: Have the views set this themselves.
        if (preferences.getBoolean("DisallowOverscroll", false)) {
            appView.setOverScrollMode(View.OVER_SCROLL_NEVER);
        }
        createViews();

        // TODO: Make this a preference (CB-6153)
        // Setup the hardware volume controls to handle volume control
        setVolumeControlStream(AudioManager.STREAM_MUSIC);
    }

   在init() 这个方法中,总共创建了三个实例:

    1.CordovaWebView的对象。

       继承于webview,webview专心干好自己的解析以及渲染工作

    2.CordovaWebViewClient对象。

       继承于WebViewClient:帮助WebView处理各种通知、请求事件

    3.CordovaChromeClient对象。

       继承于WebChromeClient:辅助WebView处理Javascript的对话框,网站图标,网站title,加载进度等


   当pluginManager等于null时候,需要调用webview的初始化方法init():

public void init(CordovaInterface cordova, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient,
            List<PluginEntry> pluginEntries, Whitelist internalWhitelist, Whitelist externalWhitelist,
            CordovaPreferences preferences) {
        if (this.cordova != null) {
            throw new IllegalStateException();
        }
        this.cordova = cordova;
        this.viewClient = webViewClient;
        this.chromeClient = webChromeClient;
        this.internalWhitelist = internalWhitelist;
        this.externalWhitelist = externalWhitelist;
        this.preferences = preferences;
        super.setWebChromeClient(webChromeClient);
        super.setWebViewClient(webViewClient);

        pluginManager = new PluginManager(this, this.cordova, pluginEntries);
        bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova));
        resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);

        pluginManager.addService("App", "org.apache.cordova.App");
        initWebViewSettings();
        exposeJsInterface();
    }
       具体里面是什么意思,下篇继续分解。


   


相关文章推荐

js通过cordova调用本地代码

Cordova 3.x 基础(11) -- JS是如何调用本地API的? 博客分类: Cordova CordovaPhoneGap  Cordova应用基于Webview,所以后...

PhoneGap插件开发 js与Java之间的交互例子 详解

PhoneGap中js与Java之间相互调用有两种方式,分别是同步和异步。 1、同步方式:js调用Java类的方法,然后Java类的方法直接返回一个值给js端,这跟我们Java方法间的调用是差不多一个...

phonegap 通过 js调用java方法

本文以android为例 phonegap 的js调用java 需要通过

PhoneGap插件调用Java流程源码分析(三)

0

sqoop 抽取源码流程分析(一) 主流程分析以及各种插件

sqoop 源码 继承关系

Phonegap插件编写 Java面向对象方法

上一篇博文中我转载了别人写的phonegap的编写方法,简单回顾一下: cordova中有Plugin这个类,编写的插件继承于这个类,其中有exec这个方法,根据第二个参数的命令进行判断,来决定该执...
  • Yelbosh
  • Yelbosh
  • 2012年07月02日 21:50
  • 1575

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

1.        前言 在《用PhoneGap+jQueryMobile开发Android应用实例》中,我们讲到PhoneGap(以下称Cordova)开发环境的搭建,以及如何整合出一个基本的An...

java流程分析插件(SOPA)_V1.0.0.zip

  • 2014年02月15日 11:25
  • 2.27MB
  • 下载

phonegap扫描二维码插件

  • 2014年09月09日 09:02
  • 1.88MB
  • 下载

[Phonegap+Sencha Touch] 移动开发76 让cordova app访问远端网站也能调用cordova插件功能

我相信,应该会有一些cordova开发者想过实现下面这种app: 使用cordova制作一个外壳app,用于浏览服务端部署的网站,这样当服务器上网站升级后,这个cordova app可以立即访问最新版...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:PhoneGap插件调用Java流程源码分析(一)
举报原因:
原因补充:

(最多只允许输入30个字)