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

来源:互联网 发布:linux 物理网卡 编辑:程序博客网 时间:2024/06/05 04:15

                        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. */@Deprecatedpublic 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();    }
       具体里面是什么意思,下篇继续分解。


   


0 0