[toc]
# [Android Plugin Development Guide](http://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html)
首先你要去看看[插件开发指南](插件开发指南.md),以便对插件开发有个总体的结构认识。本节继续演示示例echo插件,该插件从Cordova webview通信到本机平台并返回。有关另一个示例,另请参阅 [CordovaPlugin.java](https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/CordovaPlugin.java) 中的注释。
Android插件基于 Cordova-Android ,它是使用带有原生桥接(*native bridge*)的 Android WebView构建的。
Android插件的原生部分包含至少一个继承(extends) `CordovaPlugin` 类并覆盖其中的一个 `execute` 方法。
# 插件类映射
该插件的JavaScript接口使用 `cordova.exec` 方法,如下所示:
~~~
exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);
~~~
这将从WebView 向Android 本地端封送一个请求,有效地调用 `service`类上的 `action`方法,并在 `args`数组中传递附加参数。
无论是将插件作为Java 文件还是作为自己的 jar 文件分发,都必须在Cordova-Android 应用程序的 `res/xml/config.xml` 文件中指定插件。有关如何使用 `plugin.xml` 文件注入此 `feature`元素的详细信息,请参阅应用程序插件:
~~~
<feature name="<service_name>">
<param name="android-package" value="<full_name_including_namespace>" />
</feature>
~~~
`service_name` 与 `JavaScript exec` 调用中使用的名称匹配。该值是Java类的完全限定名称空间标识符。否则,插件可能会编译但仍然无法访问Cordova。
# 插件初始化和生命周期
在每个 `WebView` 的生命周期中创建一个插件对象实例。除非首先通过JavaScript调用引用插件,否则不会实例化插件,除非在 `config.xml` 中将带有`onload` `name`属性的设置为“true”。例如:
~~~
<feature name="Echo">
<param name="android-package" value="<full_name_including_namespace>" />
<param name="onload" value="true" />
</feature>
~~~
插件应该使用 `initialize` 方法初始化它们的启动逻辑。
~~~
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
// your init code here
}
~~~
插件还可以访问Android生命周期事件,并可以通过j集成其中一个提供的方法(`onResume`,`onDestroy`等)来处理它们。具有长时间运行请求,后台活动(如媒体播放,侦听器或内部状态)的插件应实现`onReset()`方法。它在`WebView` 导航到新页面或刷新时执行,这会重新加载JavaScript。
# 编写一个Android Java插件
JavaScript调用会触发对本机端的插件请求,相应的Java插件会在config.xml文件中正确映射,但最终的Android Java插件类是什么样的?
使用JavaScript的`exec`函数调度到插件的任何内容都会传递到插件类的 `execute`方法中。大多数`execute`实现看起来像这样:
~~~
@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.
}
~~~
JavaScript `exec`函数的`action` 参数对应于一个私有类方法,可以用可选参数进行分派。
当捕获异常并返回错误时,为了清晰起见,返回到JavaScript的错误尽可能地匹配Java的异常名是非常重要的。
# 线程
插件的JavaScript不会在 `WebView` 接口的主线程中运行;相反,它和 `execute`方法一样运行在 `WebCore` 线程上。如果您需要与用户界面交互,您应该使用 [Activity's runOnUiThread](http://developer.android.com/reference/android/app/Activity.html#runOnUiThread(java.lang.Runnable)) 方法,如下所示:
~~~
@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;
}
~~~
如果您不需要在UI线程上运行,但也不希望阻止WebCore线程,您应该使用从 `Cordova.getthreadpool()` 获得的Cordova `ExecutorService`执行代码,如下所示:
~~~
@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;
}
~~~
# 添加依赖库
如果你的Android插件有额外的依赖项,它们必须在插件的`plugin.xml` 中以两种方式中的一种列出。
首选方法是使用 `<framework />` 标记(有关详细信息,请参阅[插件规范](http://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#framework))。以这种方式指定库允许通过Gradle的[依赖关系管理](https://docs.gradle.org/current/userguide/dependency_management.html)逻辑来解析它们。这允许多个插件使用诸如*gson*,*android-support-v4* 和 *google-play-services* 之类的常用库而不会发生冲突。
第二个选项是使用 `<lib-file />` 标记指定 *jar* 文件的位置(有关详细信息,请参阅[插件规范](http://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#framework))。只有当您确信没有其他插件依赖于您正在引用的库时(例如,如果库是特定于您的插件的话),才应该使用这种方法。
例如:
~~~
<lib-file src="src/android/PaySDK/libs/alipaySdk-20170725.jar" />
~~~
该jar包会被自动复制到 `platforms>android>libs` 下面!
否则,如果另一个插件添加了相同的库,则可能会导致插件用户出现构建错误。值得注意的是,Cordova应用程序开发人员不一定是原生开发人员,因此原生构建错误尤其令人沮丧。
# Echo Android插件的例子
要匹配Application Plugins中描述的JavaScript接口的echo功能,请使用 `plugin.xml` 将 `feature`规范注入原生平台的`config.xml`文件:
~~~
<platform name="android">
<config-file target="config.xml" parent="/*">
<feature name="Echo">
<param name="android-package" value="org.apache.cordova.plugin.Echo"/>
</feature>
</config-file>
<source-file src="src/android/Echo.java" target-dir="src/org/apache/cordova/plugin" />
</platform>
~~~
然后将以下内容添加到`src/android/Echo.java` 文件中:
~~~
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.");
}
}
}
~~~
文件顶部的必要导入扩展了`CordovaPlugin` 的类,它的 `execute()` 方法覆盖了 `exec()` 接收消息。`execute()` 方法首先测试`action`的值,在这种情况下,只有一个有效的`echo`值。任何其他操作返回 `false` 并导致`INVALID_ACTION` 错误,这会转换为在JavaScript端调用的错误回调。
接下来,该方法使用 `args`对象的 `getString` 方法检索echo字符串,指定传递给方法的第一个参数。将值传递给私有echo方法后,将对其进行参数检查,以确保它不是`null`或空字符串,在这种情况下,`callbackContext.error()` 调用JavaScript的错误回调。如果各种检查通过,`callbackContext.success()` 将原始 `message` 字符串传递回JavaScript成功回调作为参数。
# Android集成
Android具有 [Intent](http://developer.android.com/reference/android/content/Intent.html) 系统,允许进程相互通信。插件可以访问 `CordovaInterface` 对象,该对象可以访问运行应用程序的[Android Activity](http://developer.android.com/reference/android/app/Activity.html)。这是启动新[Android Intent](http://developer.android.com/reference/android/content/Intent.html)所需的[Context](http://developer.android.com/reference/android/content/Context.html)*上下文*。 CordovaInterface允许插件为结果启动[Activity](http://developer.android.com/reference/android/app/Activity.html),并为 [Intent](http://developer.android.com/reference/android/content/Intent.html) 返回应用程序时设置回调插件。
从Cordova 2.0开始,插件就不能直接访问[Context](http://developer.android.com/reference/android/content/Context.html),并且不赞成使用遗留的`ctx` 成员。所有`ctx` 方法都存在于[Context](http://developer.android.com/reference/android/content/Context.html)中,因此 `getContext()`和`getActivity()` 都可以返回所需的对象。
# Android权限
直到最近,Android权限一直是在安装时而不是运行时处理的。需要在使用这些权限的应用程序上声明这些权限,并且需要将这些权限添加到 Android Manifest 中。这可以通过 `config.xml` 来实现 将这些权限注入到 `AndroidManifest.xml` 文件中。下面的示例使用 联系人权限。
~~~
<config-file target="AndroidManifest.xml" parent="/*">
<uses-permission android:name="android.permission.READ_CONTACTS" />
</config-file>
~~~
# 运行时权限(Cordova-Android 5.0.0+)
Android 6.0 "Marshmallow" 引入了一种新的权限模型,用户可以根据需要打开和关闭权限。这意味着应用程序必须处理这些权限更改,以确保不变更,这是Cordova-Android 5.0.0版本的重点。
需要在运行时处理的权限可以在此处的[Android Developer文档](http://developer.android.com/guide/topics/security/permissions.html#perm-groups)中找到。
就插件而言,可以通过调用权限方法来请求权限;签名如下:
~~~
cordova.requestPermission(CordovaPlugin plugin, int requestCode, String permission);
~~~
为了减少冗长,标准的做法是把它赋给一个局部静态变量:
~~~
public static final String READ = Manifest.permission.READ_CONTACTS;
~~~
下面这样定义 `requestCode` 也是标准做法:
~~~
public static final int SEARCH_REQ_CODE = 0;
~~~
然后,在 `exec`方法中,应检查权限:
~~~
if(cordova.hasPermission(READ))
{
search(executeArgs);
}
else
{
getReadPermission(SEARCH_REQ_CODE);
}
~~~
在这种情况下,我们只调用 `requestPermission`:
~~~
protected void getReadPermission(int requestCode)
{
cordova.requestPermission(this, requestCode, READ);
}
~~~
这将调用活动并导致出现提示,要求获得权限。一旦用户拥有权限,就必须使用 `onRequestPermissionResult` 方法处理结果,每个插件都应覆盖该方法。下面是一个例子:
```java
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;
}
}
```
上面的`switch`语句将从提示符返回,并且根据传入的`requestCode`,将调用相应的方法。应该注意的是,如果未正确处理执行,则可以堆叠权限提示,并且应该避免这种情况。
除了要求获得单个权限的权限之外,还可以通过定义权限数组来请求整个组的权限,就像使用 Geolocation 插件所做的那样:
~~~
String [] permissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION };
~~~
然后在请求权限时,需要做的就是:
~~~
cordova.requestPermissions(this, 0, permissions);
~~~
这会请求数组中指定的权限。提供可公开访问的权限数组是个好主意,因为这可以被使用你的插件作为依赖项的插件使用,尽管这不是必需的。
# 调试Android插件
虽然推荐使用Android studio,但可以使用Eclipse 或Android Studio 进行Android调试。由于Cordova-Android 目前用作库项目,并且支持插件作为源代码,因此可以像在本机Android应用程序中一样调试Cordova应用程序中的Java代码。
# 启动其他 Activities
如果您的插件启动将Cordova活动推送到后台的活动,则需要特别注意。如果设备内存不足,Android OS将在后台销毁活动。在这种情况下,`CordovaPlugin`实例也将被销毁。如果您的插件正在等待它所启动的Activity的结果,那么当Cordova [Activity](http://developer.android.com/reference/android/app/Activity.html) 返回到前台并获得结果时,将创建插件的新实例。但是,插件的状态不会自动保存或恢复,插件的 `CallbackContext` 将丢失。 `CordovaPlugin` 可以通过两种方法来处理这种情况:
```java
/**
* 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) {}
```
重要的是要注意,只有在插件为结果启动Activity 时才应使用上述方法,并且只应恢复处理该Activity 结果所需的状态。插件的状态将不会被恢复,除非在使用 `CordovaInterface`的`startActivityForResult()` 方法获得插件请求的Activity结果并且在后台 操作系统销毁Cordova活动时。
作为 `onRestoreStateForActivityResult()` 的一部分,您的插件将被传递一个替换 CallbackContext。重要的是要意识到这个CallbackContext 与使用 Activity销毁的CallbackContext 不同。原始回调丢失,并且不会在javascript应用程序中触发。相反,此替换CallbackContext 将返回结果作为应用程序恢复时触发的[`resume`](http://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume)事件的一部分。[`resume`](http://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume)事件的有效负载遵循以下结构:
~~~
{
action: "resume",
pendingResult: {
pluginServiceName: string,
pluginStatus: string,
00000000000000000000 result: any
}
}
~~~
`pluginServiceName` 将匹配 `plugin.xml` 中的[name元素](http://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#name)。
`pluginStatus` 是一个String,描述传递给CallbackContext的 PluginResult 的状态。有关与插件状态对应的String值,请参阅`PluginResult.java`
`result` 是插件传递给`CallbackContext`的任何结果(例如 String,number,JSON object等)
此 [`resume`](http://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume) 有效负载将传递给javascript应用程序为 `resume`事件注册的任何回调中。这意味着结果*直接*进入Cordova应用程序;您的插件将无法在应用程序收到之前使用javascript处理结果。因此,您应该努力使本机代码返回的结果尽可能完整,并且在 launching activities(启动活动)时不依赖于任何javascript回调。
请务必告知Cordova应用程序如何解释他们在 [`resume`](http://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume) 事件中收到的结果。 Cordova应用程序需要维护自己的状态,并记住他们提出的请求以及必要时提供的参数。但是,您仍应清楚地传达 `pluginStatus` 值的含义以及作为插件API的一部分在`resume` 字段中返回的数据类型。
启动活动的完整事件序列如下:
1. Cordova应用程序调用您的插件
2. 您的插件会为结果启动一个Activity
3. Android操作系统会破坏Cordova Activity和您的插件实例
`onSaveInstanceState()`被调用
4. 用户与您的活动进行交互,活动完成
5. 重新创建Cordova活动并接收活动结果
`onRestoreStateForActivityResult()`被调用
6. 调用 `onActivityResult()` 并且您的插件将结果传递给新的CallbackContext
7. Cordova应用程序触发并接收 `resume` 事件
Android 提供了一个开发人员设置,用于在低内存上调试 活动销毁(Activity destruction)。在您的设备或模拟器上的Developer Options菜单中启用“Don't keep activities”设置,以模拟低内存场景。如果您的插件启动了外部活动,您应该始终启用该设置进行一些测试,以确保正确地处理低内存场景。
- PWA 概念
- Immutable
- Angular 基础概念
- 入门参考
- Angular 更新总结
- Angular 生态系统
- Rx.js
- Ngrx
- CQRS/ES 模式
- Angular 5 详解
- 测试
- 定义共享模块
- 懒路由加载
- angular组件
- 双向绑定及变化检测
- 样式
- ionic 3详解
- ionic3
- ionic 插件
- Ionic 添加动画
- Ghost-Loading
- 打包发布
- Android上架国内应用市场流程
- 总结
- 文章
- 问题合集
- Cordova
- 插件开发指南
- Android插件开发指南-官网
- IOS插件开发指南-官网
- Hooks 编写
- 桥接技术
- ===cordova插件收集===
- 相关主题-官网
- 实战-自定义插件流程
- UI 及 相关资源