license: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Writing a plugin requires an understanding of the architecture of Cordova-Android. Cordova-Android consists of an Android WebView with hooks attached to it. These plugins are represented as class mappings in the config.xml file.
A plugin will consist of at least a single Java class that extends the CordovaPlugin
class. A plugin must override one of the execute
methods from CordovaPlugin
. In addition to this, there is a best practice that the plugin should handle pause and resume events, and should handle message passing between plugins. Plugins with long-running requests, background activity (e.g. playing media), listeners or internal state should implement the onReset()
method as well. This method is run when the WebView
navigates to a new page or refreshes, which reloads the Javascript.
The JavaScript portion of a plugin always uses the cordova.exec
method as follows:
exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);
This will marshal a request from the WebView to the Android native side, more or less boiling down to calling the action
method on the service
class, with the arguments passed in the args
Array.
Whether you distribute your plugin as Java file or as a JAR of its own, the plugin must be added to the config.xml
file in your Cordova-Android application's res/xml/
folder.
<plugin name="<service_name>" value="<full_name_including_namespace>"/>
The service name should match what you use in the JavaScript exec
call, and the value will be the full name of the Java class including the namespace. Without this added, the plugin may compile but will not be reachable by Cordova.
We have JavaScript to fire off a plugin request to the native side. We have the Android Java plugin mapped properly via the config.xml
file. So what does the final Android Java Plugin class look like?
What gets dispatched to the plugin via JavaScript‘s exec
function gets passed into the Plugin class’s execute
method. Most execute
implementations look like this:
@Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { if ("beep".equals(action)) { this.beep(args.getLong(0)); callbackContext.success(); return true; } return false; // Returning false results in a "MethodNotFound" error. }
We compare the value of the action
parameter, and dispatch the request off to a (private) method in the class, optionally passing some of the parameters to the method.
When catching exceptions and returning errors, it's important that the error we return to JavaScript match the Java exception as much as possible, for clarity.
JavaScript in the WebView does not run on the UI thread. It runs on the WebCore thread. The execute
method also runs on the WebCore thread.
If you need to interact with the UI, you should use the following:
@Override public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { if ("beep".equals(action)) { final long duration = args.getLong(0); cordova.getActivity().runOnUiThread(new Runnable() { public void run() { ... callbackContext.success(); // Thread-safe. } }); return true; } return false; }
If you do not need to run on the UI thread, but do not want to block the WebCore thread:
@Override public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { if ("beep".equals(action)) { final long duration = args.getLong(0); cordova.getThreadPool().execute(new Runnable() { public void run() { ... callbackContext.success(); // Thread-safe. } }); return true; } return false; }
We would add the following to our config.xml:
<plugin name="Echo" value="org.apache.cordova.plugin.Echo" />
Then we would add the following file to src/org/apache/cordova/plugin/Echo.java
inside our Cordova-Android application:
package org.apache.cordova.plugin; import org.apache.cordova.api.CordovaPlugin; import org.apache.cordova.api.PluginResult; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; /** * This class echoes a string called from JavaScript. */ public class Echo extends CordovaPlugin { @Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { if (action.equals("echo")) { String message = args.getString(0); this.echo(message, callbackContext); return true; } return false; } private void echo(String message, CallbackContext callbackContext) { if (message != null && message.length() > 0) { callbackContext.success(message); } else { callbackContext.error("Expected one non-empty string argument."); } } }
Let's take a look at the code. At the top we have all of the necessary Cordova imports
. Our class extends from CordovaPlugin
. We override the execute() method in order to recieve messages from exec(). Our method first compares against action
: this plugin only supports one action, the echo
action. Any other action will return false, which results in an error of type INVALID_ACTION
- this will translate into an error callback invocation on the JavaScript side. Next, we grab the echo string using the getString
method on our args
, telling it we want to get the 0th parameter in the parameter array. We do a bit of parameter checking: make sure it is not null
, and make sure it is not a zero-length string. If it is, we call callbackContext.error() (which, by now, you should know will invoke the error callback). If all of those checks pass, then we call callbackContext.success(), and pass in the message
string we received as a parameter. This will finally translate into a success callback invocation on the JavaScript side. It will also pass the message
parameter as a parameter into the JavaScript success callback function.
Eclipse can be used to debug an Android project, and the plugins can be debugged if the Java source is included in the project. Only the latest version of the Android Dev Tools is known to allow source code attachment to JAR dependencies, this is not fully supported at this time.
CordovaInterface
object. This object has access to the Android Activity
that is running the application. This is the Context
required to launch a new Android Intent
. The CordovaInterface
allows plugins to start an Activity
for a result, and to set the callback plugin for when the Intent
comes back to the application. This is important, since the Intent
s system is how Android communicates between processes.Context
as they have in the past. The legacy ctx
member is deprecated, and will be removed six months after 2.0 is released. All the methods that ctx
has exist on the Context
, so both getContext()
and getActivity()
are capable of returning the proper object required.One of the best ways to prepare yourself to write your own plugin is to have a look at other plugins that already exist.
You should also read through the comments in CordovaPlugin.java.