android studio java plugins_Android

Android Plugin Development Guide

This section provides details for how to implement native plugin code

on the Android platform. Before reading this, see the Plugin Development Guide

for an overview of the plugin's structure and its common JavaScript

interface. This section continues to demonstrate the sample echo

plugin that communicates from the Cordova webview to the native

platform and back. For another sample, see also the comments in

CordovaPlugin.java.

Android plugins are based on Cordova-Android, which is built from an

Android WebView with a native bridge. The native portion of an Android plugin

consists of at least one Java class that extends the CordovaPlugin class and

overrides one of its execute methods.

Plugin Class Mapping

The plugin's JavaScript interface uses the cordova.exec method as

follows:

exec(, , , , []);

This marshals a request from the WebView to the Android native side,

effectively calling the action method on the service class, with

additional arguments passed in the args array.

Whether you distribute a plugin as Java file or as a jar file of its

own, the plugin must be specified in your Cordova-Android

application's res/xml/config.xml file. See Application Plugins for

more information on how to use the plugin.xml file to inject this

feature element:

The service name matches the one used in the JavaScript exec call.

The value is the Java class's fully qualified namespace identifier.

Otherwise, the plugin may compile but still be unavailable to Cordova.

Plugin Initialization and Lifetime

One instance of a plugin object is created for the life of each

WebView. Plugins are not instantiated until they are first

referenced by a call from JavaScript, unless with an onload

name attribute is set to "true" in config.xml. For example,

Plugins should use the initialize method for their start-up logic.

@Override

public void initialize(CordovaInterface cordova, CordovaWebView webView) {

super.initialize(cordova, webView);

// your init code here

}

Plugins also have access to Android lifecycle events and can handle them

by extending one of the provided methods (onResume, onDestroy, etc).

Plugins with long-running requests, background activity such as media playback,

listeners, or internal state should implement the onReset() method. It

executes when the WebView navigates to a new page or refreshes, which reloads

the JavaScript.

Writing an Android Java Plugin

A JavaScript call fires off a plugin request to the native side, and

the corresponding Java plugin is mapped properly in the config.xml

file, but what does the final Android Java Plugin class look like?

Whatever is dispatched to the plugin with JavaScript's exec function

is 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.

}

The JavaScript exec function's action parameter corresponds to a

private class method to dispatch with optional parameters.

When catching exceptions and returning errors, it's important for the

sake of clarity that errors returned to JavaScript match Java's

exception names as much as possible.

Threading

The plugin's JavaScript does not run in the main thread of the

WebView interface; instead, it runs on the WebCore thread, as

does the execute method. If you need to interact with the user

interface, you should use the Activity's runOnUiThread

method like so:

@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 wish to block the

WebCore thread either, you should execute your code using the Cordova

ExecutorService obtained with cordova.getThreadPool() like

so:

@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;

}

Adding Dependency Libraries

If your Android plugin has extra dependencies, they must be listed in the

plugin.xml in one of two ways.

The preferred way is to use the tag (see the

Plugin Specification for more details).

Specifying libraries in this manner allows them to be resolved via Gradle's

Dependency Management logic. This allows commonly used

libraries such as gson, android-support-v4, and google-play-services to be

used by multiple plugins without conflict.

The second option is to use the tag to specify the location of

a jar file (see the Plugin Specification for

more details). This approach should only be used if you are sure that no other

plugin will be depending on the library you are referencing (e.g. if the library

is specific to your plugin). Otherwise, you risk causing build errors for users

of your plugin if another plugin adds the same library. It is worth noting that

Cordova app developers are not necessarily native developers, so native platform

build errors can be especially frustrating.

Echo Android Plugin Example

To match the JavaScript interface's echo feature described in

Application Plugins, use the plugin.xml to inject a feature

specification to the local platform's config.xml file:

Then add the following to the src/android/Echo.java file:

package org.apache.cordova.plugin;

import org.apache.cordova.CordovaPlugin;

import org.apache.cordova.CallbackContext;

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.");

}

}

}

The necessary imports at the top of the file extends the class from

CordovaPlugin, whose execute() method it overrides to receive

messages from exec(). The execute() method first tests the value

of action, for which in this case there is only one valid echo

value. Any other action returns false and results in an

INVALID_ACTION error, which translates to an error callback invoked

on the JavaScript side.

Next, the method retrieves the echo string using the args object's

getString method, specifying the first parameter passed to the

method. After the value is passed to a private echo method, it is

parameter-checked to make sure it is not null or an empty string, in

which case callbackContext.error() invokes JavaScript's error

callback. If the various checks pass, the callbackContext.success()

passes the original message string back to JavaScript's success

callback as a parameter.

Android Integration

Android features an Intent system that allows processes to

communicate with each other. Plugins have access to a

CordovaInterface object, which can access the Android Activity

that runs 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 returns to the application.

As of Cordova 2.0, Plugins can no longer directly access the

Context, and the legacy ctx member is deprecated. All ctx

methods exist on the Context, so both getContext() and

getActivity() can return the required object.

Android Permissions

Android permissions until recently have been handled at install-time instead

of runtime. These permissions are required to be declared on an application that uses

the permissions, and these permissions need to be added to the Android Manifest. This can be

accomplished by using the config.xml to inject these permissions in the AndroidManifest.xml file.

The example below uses the Contacts permission.

Runtime Permissions (Cordova-Android 5.0.0+)

Android 6.0 "Marshmallow" introduced a new permissions model where

the user can turn on and off permissions as necessary. This means that

applications must handle these permission changes to be future-proof, which

was the focus of the Cordova-Android 5.0.0 release.

The permissions that need to be handled at runtime can be found in the Android Developer

documentation here.

As far as a plugin is concerned, the permission can be requested by calling the permission method, which signature is as follows:

cordova.requestPermission(CordovaPlugin plugin, int requestCode, String permission);

To cut down on verbosity, it's standard practice to assign this to a local static variable:

public static final String READ = Manifest.permission.READ_CONTACTS;

It is also standard practice to define the requestCode as follows:

public static final int SEARCH_REQ_CODE = 0;

Then, in the exec method, the permission should be checked:

if(cordova.hasPermission(READ))

{

search(executeArgs);

}

else

{

getReadPermission(SEARCH_REQ_CODE);

}

In this case, we just call requestPermission:

protected void getReadPermission(int requestCode)

{

cordova.requestPermission(this, requestCode, READ);

}

This will call the activity and cause a prompt to appear asking for the permission. Once the user has the permission, the result must be handled with the onRequestPermissionResult method, which

every plugin should override. An example of this can be found below:

public void onRequestPermissionResult(int requestCode, String[] permissions,

int[] grantResults) throws JSONException

{

for(int r:grantResults)

{

if(r == PackageManager.PERMISSION_DENIED)

{

this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR));

return;

}

}

switch(requestCode)

{

case SEARCH_REQ_CODE:

search(executeArgs);

break;

case SAVE_REQ_CODE:

save(executeArgs);

break;

case REMOVE_REQ_CODE:

remove(executeArgs);

break;

}

}

The switch statement above would return from the prompt and depending on the requestCode that was passed in, it would call the method. It should be noted that permission prompts may stack if the execution is not handled correctly, and that this should be avoided.

In addition to asking for permission for a single permission, it is also possible to request permissions for an entire group by defining the permissions array, as what is done with the Geolocation plugin:

String [] permissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION };

Then when requesting the permission, all that needs to be done is the following:

cordova.requestPermissions(this, 0, permissions);

This requests the permissions specified in the array. It's a good idea to provide a publicly accessible permissions array since this can be used by plugins that use your plugin as a

dependency, although this is not required.

Debugging Android Plugins

Android debugging can be done with either Eclipse or Android Studio, although Android

studio is recommended. Since Cordova-Android is currently used as a library project,

and plugins are supported as source code, it is possible to debug the Java code inside

a Cordova application just like a native Android application.

Launching Other Activities

There are special considerations to be made if your plugin launches an Activity

that pushes the Cordova Activity to the background. The Android OS will destroy

Activities in the background if the device is running low on memory. In that

case, the CordovaPlugin instance will be destroyed as well. If your plugin is

waiting on a result from the Activity it launched, a new instance of your plugin

will be created when the Cordova Activity is brought back to the foreground and

the result is obtained. However, state for the plugin will not be automatically

saved or restored and the CallbackContext for the plugin will be lost. There are

two methods that your CordovaPlugin may implement to handle this situation:

/**

* Called when the Activity is being destroyed (e.g. if a plugin calls out to an

* external Activity and the OS kills the CordovaActivity in the background).

* The plugin should save its state in this method only if it is awaiting the

* result of an external Activity and needs to preserve some information so as

* to handle that result; onRestoreStateForActivityResult() will only be called

* if the plugin is the recipient of an Activity result

*

* @return Bundle containing the state of the plugin or null if state does not

* need to be saved

*/

public Bundle onSaveInstanceState() {}

/**

* Called when a plugin is the recipient of an Activity result after the

* CordovaActivity has been destroyed. The Bundle will be the same as the one

* the plugin returned in onSaveInstanceState()

*

* @param state Bundle containing the state of the plugin

* @param callbackContext Replacement Context to return the plugin result to

*/

public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {}

It is important to note that the above methods should only be used if your

plugin launches an Activity for a result and should only restore the state

necessary to handle that Activity result. The state of the plugin will NOT be

restored except in the case where an Activity result is obtained that your

plugin requested using the CordovaInterface's startActivityForResult() method

and the Cordova Activity was destroyed by the OS while in the background.

As part of onRestoreStateForActivityResult(), your plugin will be passed a

replacement CallbackContext. It is important to realize that this

CallbackContext IS NOT the same one that was destroyed with the Activity. The

original callback is lost, and will not be fired in the javascript application.

Instead, this replacement CallbackContext will return the result as part of the

resume event that is fired when the application resumes. The

payload of the resume event follows this structure:

{

action: "resume",

pendingResult: {

pluginServiceName: string,

pluginStatus: string,

result: any

}

}

pluginServiceName will match the name element from your plugin.xml.

pluginStatus will be a String describing the status of the PluginResult

passed to the CallbackContext. See PluginResult.java for the String values

that correspond to plugin statuses

result will be whatever result the plugin passes to the CallbackContext

(e.g. a String, a number, a JSON object, etc.)

This resume payload will be passed to any callbacks that the javascript

application has registered for the resume event. This means that the result is

going directly to the Cordova application; your plugin will not have a chance

to process the result with javascript before the application receives it.

Consequently, you should strive to make the result returned by the native code

as complete as possible and not rely on any javascript callbacks when launching

activities.

Be sure to communicate how the Cordova application should interpret the result

they receive in the resume event. It is up to the Cordova application to

maintain their own state and remember what requests they made and what arguments

they provided if necessary. However, you should still clearly communicate the

meaning of pluginStatus values and what sort of data is being returned in the

resume field as part of your plugin's API.

The complete sequence of events for launching an Activity is as follows

The Cordova application makes a call to your plugin

Your plugin launches an Activity for a result

The Android OS destroys the Cordova Activity and your plugin instance

onSaveInstanceState() is called

The user interacts with your Activity and the Activity finishes

The Cordova Activity is recreated and the Activity result is received

onRestoreStateForActivityResult() is called

onActivityResult() is called and your plugin passes a result to the new

CallbackContext

The resume event is fired and received by the Cordova application

Android provides a developer setting for debugging Activity destruction on low

memory. Enable the "Don't keep activities" setting in the Developer Options menu

on your device or emulator to simulate low memory scenarios. If your plugin

launches external activities, you should always do some testing with this

setting enabled to ensure that you are properly handling low memory scenarios.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值