[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 及 相關資源