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


   


使用Eclipse新建phonegap程序,并打包

1.新建一个Android工程 2.解压Phonegap包,找到里面的找到Android目录,会发现有如下文件 phonegap-1.0.0.jar   phonegap-1.0.0.js   x...
  • l863784757
  • l863784757
  • 2014年07月17日 17:30
  • 2673

PhoneGap2.9.0本地将html打包成Android应用

参考: 1.开发环境设置 http://www.cnblogs.com/zoupeiyang/p/4034517.html http://www.cnblogs.com/Random/archi...
  • u014345282
  • u014345282
  • 2016年03月28日 14:20
  • 1642

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

JS调用Java端 分析 想了解phonegap 开发大概流程,应该知道如下几点。    1.js 通过html prompt弹窗接口往anroid native 发送消息。    2.andr...
  • wubo_fly
  • wubo_fly
  • 2015年11月19日 14:43
  • 733

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

0
  • wubo_fly
  • wubo_fly
  • 2015年11月19日 13:55
  • 1342

PhoneGap build生成apk,打开后报Application Error等等

一:android app 启动错误,即出现:Application Error - The connection to the server was unsuccessful. (file:///a...
  • zhanqizi
  • zhanqizi
  • 2014年11月21日 15:52
  • 1078

开发phonegap应用问题汇总

事前并未进行可行性分析,用phonegap框架开发仅是用于尝试,我的感觉是公司的这个项目可有可无,呵呵,感觉而已... 开始之初没有做太多的规划,以至于后来想重构代码的打算,由于各种原因吧,应用可以正...
  • xiangjai
  • xiangjai
  • 2013年07月23日 14:04
  • 5502

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

PhoneGap插件调用Java流程源码分析    回顾一下上一篇  PhoneGap插件调用Java流程源码分析(一)最后的话题,当调用webview.loadUrl(...)会需要判断是否初...
  • wubo_fly
  • wubo_fly
  • 2015年11月19日 11:52
  • 1166

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

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

PhoneGap2.9.0本地将html打包成Android应用

PhoneGap的在线打包有大小限制,超过30M的包无法在线打包。当然,可以把包里面的图片、声音文件去掉,然后打包。下载以后,解包,重新打包并签名。蛮麻烦的。 本地打包的简单方法如下: ...
  • zrbvxvxl
  • zrbvxvxl
  • 2014年09月03日 21:41
  • 213

PhoneGap入门

phonegap入门
  • liangyun929
  • liangyun929
  • 2015年09月02日 16:32
  • 234
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:PhoneGap插件调用Java流程源码分析(一)
举报原因:
原因补充:

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