Spec-Zone.ru › Cordova 7

Руководство по разработке плагинов для Android

В этом разделе приводятся подробные инструкции по реализации кода нативного плагина на платформе Android. Прежде чем приступить к чтению, ознакомьтесь с Руководством по разработке плагинов для общего обзора структуры плагина и его стандартного JavaScript-интерфейса. В этом разделе продолжается демонстрация образца плагина echo, который осуществляет взаимодействие между Cordova webview и нативной платформой. Для другого примера см. также комментарии в CordovaPlugin.java.

Плагины для Android основаны на Cordova-Android, который построен на базе Android WebView с нативным мостом. Нативная часть плагина Android состоит как минимум из одного Java-класса, который расширяет класс CordovaPlugin и переопределяет один из его методов execute.

Сопоставление классов плагина

Интерфейс плагина JavaScript использует метод cordova.exec следующим образом:

exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);

Это обеспечивает передачу запроса из WebView на нативную сторону Android, эффективно вызывая метод action в классе service, с дополнительными аргументами, переданными в массиве args.

Независимо от того, распространяете ли вы плагин в виде Java-файла или в виде собственного файла jar, плагин должен быть указан в файле res/xml/config.xml вашего приложения Cordova-Android. См. Раздел "Плагины приложения" для получения дополнительной информации о том, как использовать файл plugin.xml для внедрения этого элемента feature.

<feature name="<service_name>">
    <param name="android-package" value="<full_name_including_namespace>" />
</feature>

Имя сервиса совпадает с именем, используемым в вызове JavaScript exec. Значение представляет собой полное квалифицированное имя пространства имён Java-класса. В противном случае, плагин может быть скомпилирован, но по-прежнему недоступен для Cordova.

Инициализация и жизненный цикл плагина

Один экземпляр объекта плагина создаётся на всё время существования WebView. Плагины не создаются до тех пор, пока они не будут впервые упомянуты в вызове JavaScript, если только <param> с атрибутом onload name не установлен в значение "true" в файле config.xml. Например,

<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 и могут обрабатывать их, расширяя один из предоставленных методов (onResume, onDestroy, и т.д.). Плагины с долго выполняющимися запросами, фоновой деятельностью, такой как воспроизведение медиа, обработчиками событий или внутренним состоянием, должны реализовать метод onReset(). Он выполняется, когда WebView переходит на новую страницу или обновляется, что перезагружает JavaScript.

Написание Java-плагина для Android

Вызов JavaScript запускает запрос плагина на нативной стороне, и соответствующий Java-плагин правильно сопоставляется в файле config.xml, но как выглядит окончательный Java-класс плагина Android? Всё, что отправляется в плагин с помощью функции 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.
}

Параметр exec функции JavaScript action соответствует методу класса для обработки вызова с необязательными параметрами.

При перехвате исключений и возвращении ошибок важно для ясности, чтобы ошибки, возвращаемые в JavaScript, соответствовали именам исключений Java как можно точнее.

Потоки

JavaScript-код плагина не выполняется в главном потоке интерфейса WebView, а вместо этого выполняется в потоке WebCore, как и метод execute. Если вам необходимо взаимодействовать с пользовательским интерфейсом, вы должны использовать метод Activity's runOnUiThread следующим образом:

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

Если вам не нужно работать в потоке пользовательского интерфейса, но вы также не хотите блокировать поток WebCore, вы должны выполнить свой код, используя Cordova ExecutorService, полученный с помощью cordova.getThreadPool() следующим образом:

@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 /> (подробнее см. в Спецификации плагина). Указание библиотек таким образом позволяет их разрешать через логику управления зависимостями Gradle Dependency Management logic. Это позволяет использовать такие распространённые библиотеки, как gson, android-support-v4 и google-play-services несколькими плагинами без конфликтов.

Второй вариант - использование тега <lib-file /> для указания расположения файла jar (подробнее см. в Спецификации плагина). Этот подход следует использовать только в том случае, если вы уверены, что ни один другой плагин не будет зависеть от используемой вами библиотеки (например, если библиотека специфична для вашего плагина). В противном случае вы рискуете вызвать ошибки сборки для пользователей вашего плагина, если другой плагин добавит ту же библиотеку. Стоит отметить, что разработчики Cordova-приложений не обязательно являются разработчиками нативных приложений, поэтому ошибки сборки нативных платформ могут быть особенно раздражающими.

Пример плагина Android Echo

Чтобы соответствовать функции echo интерфейса JavaScript, описанной в разделе "Плагины приложения", используйте 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.

Далее, метод извлекает строку echo, используя метод args объекта getString, указав первый параметр, переданный в метод. После передачи значения в частный метод echo, проверяется параметр, чтобы убедиться, что он не равен null или пустой строке, в этом случае callbackContext.error() вызывает обратный вызов ошибки JavaScript. Если различные проверки пройдены, callbackContext.success() передаёт исходную строку message обратно в обратный вызов успеха JavaScript в качестве параметра.

Интеграция с Android

Android имеет систему Intent, которая позволяет процессам взаимодействовать друг с другом. Плагины имеют доступ к объекту CordovaInterface, который может получить доступ к Android Activity, выполняющему приложение. Это контекст CordovaInterface, необходимый для запуска нового Android Intent.

Начиная с Cordova 2.0, плагины больше не могут напрямую получить доступ к Context, и устаревший член ctx устарел. Все методы ctx существуют в объекте Context, поэтому как getContext(), так и getActivity() могут возвращать требуемый объект.

Разрешения Android

Разрешения Android до недавнего времени обрабатывались во время установки, а не во время выполнения. Эти разрешения должны быть объявлены в приложении, использующем разрешения, и эти разрешения должны быть добавлены в Android Manifest. Это можно сделать, используя config.xml для внедрения этих разрешений в файл AndroidManifest.xml. В примере ниже используется разрешение Contacts.

<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 здесь.

Что касается плагина, то разрешение можно запросить, вызвав метод разрешения, сигнатура которого следующая:

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, который должен переопределяться каждым плагином. Пример этого представлен ниже:

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

Это запрашивает разрешения, указанные в массиве. Хорошо иметь доступный массив разрешений, так как это можно использовать плагинам, которые используют ваш плагин в качестве зависимости, хотя это не обязательно.

END_OF_DOCUMENT_MARKER

Отладка плагинов Android

Отладка приложений Android может быть выполнена с помощью Eclipse или Android Studio, хотя рекомендуется использовать Android Studio. Поскольку Cordova-Android в настоящее время используется как проект библиотеки, а плагины поддерживаются в виде исходного кода, можно отлаживать код Java внутри приложения Cordova так же, как и в обычном приложении Android.

Запуск других активностей

Необходимо учитывать некоторые особенности, если ваш плагин запускает активность, которая переводит активность Cordova в фоновый режим. ОС Android уничтожает активности в фоновом режиме, если на устройстве мало памяти. В этом случае экземпляр CordovaPlugin также будет уничтожен. Если ваш плагин ожидает результат от запущенной активности, новый экземпляр вашего плагина будет создан, когда активность Cordova будет возвращена в фоновый режим, и результат будет получен. Однако состояние плагина не будет автоматически сохранено или восстановлено, и CallbackContext для плагина будет потеряно. Существует два метода, которые ваш плагин CordovaPlugin может реализовать для обработки этой ситуации:

/**
 * 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 для получения результата и должен восстанавливать только состояние, необходимое для обработки результата этой активности. Состояние плагина НЕ БУДЕТ восстановлено, за исключением случая, когда получен результат активности, запрошенный плагином с помощью метода CordovaInterface`s startActivityForResult(), а активность Cordova была уничтожена ОС во время работы в фоновом режиме.

В качестве части onRestoreStateForActivityResult(), вашему плагину будет передан заменяющий CallbackContext. Важно понимать, что этот CallbackContext НЕ ЯВЛЯЕТСЯ тем же, что был уничтожен вместе с активностью. Исходный коллбэк потерян и не будет вызван в приложении JavaScript. Вместо этого, этот заменяющий CallbackContext вернет результат в рамках события resume, которое срабатывает при возобновлении приложения. Данные события resume имеют следующую структуру:

{
    action: "resume",
    pendingResult: {
        pluginServiceName: string,
        pluginStatus: string,
        result: any
    }
}
  • pluginServiceName будет соответствовать элементу имя из вашего файла plugin.xml.
  • pluginStatus будет строкой, описывающей статус PluginResult, переданного CallbackContext. См. PluginResult.java для значений строк, соответствующих статусам плагина.
  • result будет тем результатом, который плагин передает в CallbackContext (например, строка, число, объект JSON и т. д.).

Этот payload события resume будет передан любым коллбэкам, зарегистрированным приложением JavaScript для события resume. Это означает, что результат передается непосредственно в приложение Cordova; ваш плагин не сможет обработать результат с помощью JavaScript перед получением результата приложением. Следовательно, вы должны стремиться сделать возвращаемый результатом код максимально полным и не полагаться на JavaScript-коллбэки при запуске активностей.

Убедитесь, что вы указали, как приложение Cordova должно интерпретировать полученный результат в событии resume. Приложение Cordova должно самостоятельно поддерживать свое состояние и запоминать сделанные запросы и предоставленные аргументы, если это необходимо. Однако вы должны четко указать значение pluginStatus и какой тип данных возвращается в поле resume в рамках API вашего плагина.

Полная последовательность событий для запуска активности следующая

  1. Приложение Cordova вызывает ваш плагин
  2. Ваш плагин запускает активность для получения результата
  3. ОС Android уничтожает активность Cordova и экземпляр вашего плагина
    • onSaveInstanceState() вызывается
  4. Пользователь взаимодействует с вашей активностью, и активность завершается
  5. Активность Cordova воссоздается, и результат активности получен
    • onRestoreStateForActivityResult() вызывается
  6. onActivityResult() вызывается, и ваш плагин передает результат в новый CallbackContext
  7. Срабатывает событие resume, которое получает приложение Cordova

Android предоставляет разработчикам настройку для отладки уничтожения активностей при малом объеме памяти. Включите настройку "Не сохранять активности" в меню "Параметры разработчика" на вашем устройстве или эмуляторе, чтобы смоделировать сценарии с малым объемом памяти. Если ваш плагин запускает внешние активности, вы всегда должны провести тестирование с этой настройкой, чтобы убедиться, что вы правильно обрабатываете сценарии с малым объемом памяти.

© 2012, 2013, 2015 The Apache Software Foundation
Licensed under the Apache License 2.0.
https://cordova.apache.org/docs/en/7.x/guide/platforms/android/plugin.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API