Spec-Zone.ru › Cordova 6

Руководство по разработке плагинов для 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-файл, плагин должен быть указан в файле приложения Cordova-Android res/xml/config.xml. Дополнительную информацию о том, как использовать файл 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.

Создание плагина 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.
}

Параметр 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 . Это позволяет использовать широко распространенные библиотеки, такие как gson, android-support-v4 и google-play-services, несколькими плагинами без конфликтов.

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

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

Для соответствия функции 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 объекта, указав первый параметр, переданный в метод. После передачи значения в частный метод 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);
}

Это вызовет Activity и отобразит запрос разрешения. После того, как пользователь предоставит разрешение, результат необходимо обработать с помощью метода 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);

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

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

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

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

Следует учитывать особые моменты, если ваш плагин запускает Activity, которая переводит Cordova Activity на задний план. ОС Android уничтожает Activity на заднем плане, если устройству не хватает памяти. В этом случае экземпляр CordovaPlugin также будет уничтожен. Если ваш плагин ожидает результат от запущенной Activity, новый экземпляр вашего плагина будет создан, когда Cordova Activity вернётся на передний план и результат будет получен. Однако состояние плагина не будет автоматически сохранено или восстановлено, и состояние 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 для получения результата и должен восстановить только состояние, необходимое для обработки результата этой Activity. Состояние плагина НЕ будет восстановлено, за исключением случая, когда получен результат Activity, запрошенный вашим плагином с помощью метода CordovaInterface's startActivityForResult(), и Cordova Activity была уничтожена ОС при работе в фоновом режиме.

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

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

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

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

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

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

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

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

Spec-Zone.ru

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