Cordova—Android源码分析一:Cordova插件的初始化流程
在CordovaActivity中的onCreate(Bundle savedInstanceState)中,会调用loadConfig()方法,loadConfig方法代码如下:
protected void loadConfig() {
ConfigXmlParser parser = new ConfigXmlParser();
parser.parse(this);
preferences = parser.getPreferences();
preferences.setPreferencesBundle(getIntent().getExtras());
// launchUrl = parser.getLaunchUrl();
mUrl = parser.getLaunchUrl();
pluginEntries = parser.getPluginEntries();
Config.parser = parser;
}
使用ConfigXmlParser类来解析/res/xml/config.xml文件( parser.parse(this) )。在ConfigXmlParser类中,分别解析出插件的名称、全路径类名、是否立即加载等属性,通过:
public void handleEndTag(XmlPullParser xml) {
String strNode = xml.getName();
if (strNode.equals("feature")) {
pluginEntries.add(new PluginEntry(service, pluginClass, onload));
service = "";
pluginClass = "";
insideFeature = false;
onload = false;
}
}
pluginEntries.add将插件信息添加到一个ArrayList中。ConfigXmlParser中有对应pluginEntries的get方法:
public ArrayList<PluginEntry> getPluginEntries() {
return pluginEntries;
}
此方法在CordovaActiivty的loadConfig中,在解析完config.xml之后被调用。在调用loadUrl时,会首先判断CordovaWebView是否为空,当为null时,会调用init()方法进行初始化:
public void loadUrl(String url) {
if (appView == null) {
init();
}
// If keepRunning
this.keepRunning = preferences.getBoolean("KeepRunning", true);
appView.loadUrlIntoView(url, true);
}
init()方法如下:
protected void init() {
appView = makeWebView();
createViews();
if (!appView.isInitialized()) {
appView.init(cordovaInterface, pluginEntries, preferences);
}
cordovaInterface.onCordovaInit(appView.getPluginManager());
// Wire the hardware volume controls to control media if desired.
String volumePref = preferences.getString("DefaultVolumeStream", "");
if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
}
在init()方法中创建CordovaWebView,同时,调用CordovaWebView的init方法,将插件信息传入。CordovaWebView是一个接口,在CordovaWebView的实现类CordovaWebViewImpl的init(CordovaInterface cordova, List pluginEntries, CordovaPreferences preferences)方法中,将pluginEntries作为参数实例化PluginManager变量:
//....................................................................
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
//....................................................................
PluginManager的构造方法如下:
public PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, Collection<PluginEntry> pluginEntries) {
this.ctx = cordova;
this.app = cordovaWebView;
setPluginEntries(pluginEntries);
}
调用setPluginEntries方法:
public void setPluginEntries(Collection<PluginEntry> pluginEntries) {
if (isInitialized) {
this.onPause(false);
this.onDestroy();
pluginMap.clear();
entryMap.clear();
}
for (PluginEntry entry : pluginEntries) {
addService(entry);
}
if (isInitialized) {
startupPlugins();
}
}
开始时,isInitialized变量为false,此时,只会执行for循环方法,其中的addService方法如下:
/**
* Add a plugin class that implements a service to the service entry table.
* This does not create the plugin object instance.
*
* @param entry The plugin entry
*/
public void addService(PluginEntry entry) {
this.entryMap.put(entry.service, entry);
if (entry.plugin != null) {
entry.plugin.privateInitialize(entry.service, ctx, app, app.getPreferences());
pluginMap.put(entry.service, entry.plugin);
}
}
其中entry.service即为config.xml中配置的feature节点的name属性,如下:
<feature name="NFCPlugin">
<param name="android-package" value="com.cmbc.firefly.nfc.NFCPlugin" />
</feature>
在addService方法中,首先会以Plugin的名称,即上面xml中的name作为key,存储插件信息,初始化时,entry.plugin为null,因此,不会进入下面的方法。在将插件信息添加到HashMap中后,在CordovaWebViewImpl的init方法的最后,会调用PluginManger的init()方法,如下:
@Override
public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences) {
if (this.cordova != null) {
throw new IllegalStateException();
}
this.cordova = cordova;
this.preferences = preferences;
mActivity = cordova.getActivity();
mFragment = (BaseCordovaFragment) cordova.getFragment();
//实例化PluginManager
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
resourceApi = new CordovaResourceApi(engine.getView().getContext(), pluginManager);
nativeToJsMessageQueue = new NativeToJsMessageQueue();
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.NoOpBridgeMode());
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.LoadUrlBridgeMode(engine, cordova));
if (preferences.getBoolean("DisallowOverscroll", false)) {
engine.getView().setOverScrollMode(View.OVER_SCROLL_NEVER);
}
engine.init(this, cordova, engineClient, resourceApi, pluginManager, nativeToJsMessageQueue);
// This isn't enforced by the compiler, so assert here.
assert engine.getView() instanceof CordovaWebViewEngine.EngineView;
pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid");
pluginManager.init();
}
CordovaPlugin插件分为两种,一种是在被调用时初始化实例,一种是在网页加载时就被初始化实例,对于第二种,在init()方法中,调用startupPlugins()方法,完成创建xml配置onLoad属性为true的插件。
/**
* Create plugins objects that have onload set.
*/
private void startupPlugins() {
for (PluginEntry entry : entryMap.values()) {
// Add a null entry to for each non-startup plugin to avoid
// ConcurrentModificationException
// When iterating plugins.
if (entry.onload) {
getPlugin(entry.service);
} else {
pluginMap.put(entry.service, null);
}
}
}
其中的getPlugin方法如下:
/**
* Get the plugin object that implements the service.
* If the plugin object does not already exist, then create it.
* If the service doesn't exist, then return null.
*
* @param service The name of the service.
* @return CordovaPlugin or null
*/
public CordovaPlugin getPlugin(String service) {
CordovaPlugin ret = pluginMap.get(service);
if (ret == null) {
PluginEntry pe = entryMap.get(service);
if (pe == null) {
return null;
}
if (pe.plugin != null) {
ret = pe.plugin;
} else {
ret = instantiatePlugin(pe.pluginClass);
}
ret.privateInitialize(service, ctx, app, app.getPreferences());
pluginMap.put(service, ret);
}
return ret;
}
流程如下:
(1).首先在以插件名称为key,以插件实例为value的LinkedHashMap 中查找插件是否已经被实例化,如果被实例化则直接返回;
(2).若map中没有service对应的CordovaPlugin示实例,则在以插件名称为key,以插件的相关信息为value的LinkedHashMap 中查找插件信息,如果对应的此信息没有查找到,则说明config.xml中没有配置此插件信息,则返回null。
(3).如果从LinkedHashMap 查找到的PluginEntry不为null,则判断下PluginEntry中的CordovaPlugin是否为null,如果为null,则调用instantiatePlugin方法得到CordovaPlugin的实例,此方法根据插件的全路径类名反射得到插件实例。
(4).在实例化CordovaPlugin后,调用CordovaPlugin的privateInitialize进行初始化操作。
(5).将实例化后的CordovaPlugin添加到LinkedHashMap 中,并返回CordovaPluign实例。
CordovaPlugin的privateInitialize方法如下:
/**
* Call this after constructing to initialize the plugin.
* Final because we want to be able to change args without breaking plugins.
*/
public final void privateInitialize(String serviceName, CordovaInterface cordova, CordovaWebView webView, CordovaPreferences preferences) {
assert this.cordova == null;
this.serviceName = serviceName;
this.cordova = cordova;
this.webView = webView;
this.preferences = preferences;
initialize(cordova, webView);
pluginInitialize();
}
在privateInitialize的方法最后,会调用initialize方法和pluginInitialize方法,用户自定义CordovaPlugin时,可以复写这两个方法完成一些插件的初始化操作。 此时,完成了整个插件的初始化过程。