[Из песочницы] Пишем фоновый процесс на Apache Cordova

Два года назад я увлекся мобильной разработкой под Android. Тогда я писал простенькие приложения для парсинга веб-сайтов. Программный код писался на Java. Это очень мощный язык, но для написания простых легковесных приложений, не выполняющих сложных задач, его объектно-ориентированная парадигма показалась мне не слишком кстати. В то время я только начинал знакомиться с JavaScript. Изначально он привлек меня своей простотой, затем я стал открывать в нем все большие и большие возможности. Я был знаком с HTML5 и CSS3, удовольствия ради создавал симпатичные веб-страницы.

Однажды я узнал об Apache Cordova — фреймворке, который позволяет писать мобильные приложения на HTML, CSS и JavaScript. Я сразу же установил его.

Работать с Cordova оказалась по-настоящему удобно. Тем не менее, судя по количеству тематических статей в интернете, фреймворк пока не получил достаточно широкого распространения. Хотя на нем реализовано, например, мобильное приложение Википедии.

Я считаю, что это связано с недостаточным функционалом стандартного Cordova API. Для работы с Bluetooth, распознаванием и синтезом речи, камерой и т.д. требуется использовать плагины. Плагины могут писаться для одной или нескольких платформ на их нативном языке.

Для нужд разработчиков были написаны тысячи плагинов, которые находятся в открытом доступе. Я был доволен до того момента, когда мне потребовалось создать приложение, которое бы даже после своего закрытия отправляло пользователю push-уведомления каждый раз через определенный промежуток времени. На Android такой фукнционал реализуется через фоновый процесс. Плагина для создания чего-либо подобного не нашлось. Плагин https://github.com/katzer/cordova-plugin-background-mode позволял запускать фоновый процесс, который останавливался сразу же, как закрывалось приложение.

На форумах утверждали, что создать полноценный фоновый процесс на Cordova невозможно. Но кто мешает написать свой плагин для этих нужд? После нескольких дней работы мне удалось написать плагин и приложение, которое при запуске начинало отправлять пользователю каждые 5 секунд уведомления.

О пути создания приложения и плагина я хочу рассказать в этом посте.


Установка Cordova и запуск приложения

Перед установкой Cordova обязательно установите Android SDK, Apache Ant и Java.

Затем добавьте их в системный путь:

На Windows: введите в поиск «Панель управления». Нажмите на Дополнительные параметры системы. Нажмите Переменные среды. В разделе Переменные среды выберите переменную среды PATH. Нажмите Изменить.
Добавьте в конец строки: ;C:\Development\adt-bundle\sdk\platform-tools;C:\Development\adt-bundle\sdk\tools;%JAVA_HOME%\bin;%ANT_HOME%\bin.

На Mac/Linux: откройте .bash_profile командой open ~/.bash_profile. Добавьте туда системные переменные: export PATH=${PATH}:/Development/adt-bundle/sdk/platform-tools:/Development/adt-bundle/sdk/tools. Добавьте пути для Java и Apache Ant, если их нет.

Обязательно укажите СВОИ пути к папкам.


  1. С сайта https://nodejs.org/en/download/ устанавливаем Node.js, позволяющий транслировать JavaScript в нативные коды. Платформа содержит пакетный менеджер npm, с помощью которого мы установим Cordova.


  2. Открываем командную строку или терминал и устанавливаем Cordova
    npm install -g cordova


  3. Создаем новый проект:

cordova create MyApp com.app.myapp MyApp

Первый аргумент — название папки, в которой будут храниться коды и файлы приложения. В данном случае это MyPluginApp.
Второй аргумент — название пакета приложения, его уникальный идентификатор.
Третий аргумент — название приложения, которое будет видно пользователю. Как правило, совпадает с названием папки.

Не забудьте перейти в папку приложения:

cd MyApp.


  1. Добавим платформы, на которых будет запускаться приложение. В моем случае это Android.

cordova platform add android


  1. Теперь запустим свое первое Cordova-приложение%

cordova run android

image


Пишем плагин

Теперь создадим плагин, который будет проводить API к классу Service, отвечающему за фоновый процесс.

Для написания плагина требуется создать обычную папку. Создадим ее в той же директории, в которой находится папка приложения. Назовем ее MyPlugin.

Любой плагин содержит файл plugin.xml. Создадим и добавим его в папку плагина.

plugin.xml:




  MyPlugin

  
    
  

  

  
     
  

  

    
      
         
      
    

    
       
    

     
    
    
  


В этом файле хранится информация о плагине, необходимых для его функционирования файлах и библиотеках, а также особенности API. Как вы можете заметить, мы ссылаемся на один JavaScript- и два Java-файла. Так создадим же их!

В папку MyPlugin добавим папку www и там создадим файл MyPlugin.js. Там мы опишем функции, с помощью которых приложение сможет взаимодействовать с нативным кодом. То есть этот файл будет служить своеобразным проводником между JavaScript-кодом разработчика приложения и Java-кодом разработчика плагина.

MyPlugin.js:

module.exports = {
    runBackground: function (successCallback, errorCallback) {
        cordova.exec(successCallback, errorCallback, "MyPlugin", "runBackground", [])
    }
}

Плагин имеет одну единственную функцию runBackground, которая запускает фоновый процесс, отправляющий уведомления. В качестве аргументов мы передаем ей функцию, которая вызывается в случае успешного выполнения команды, и функцию, которая сообщает об ошибке. Далее идет имя класса нативного кода, которому будет отправлена команда и название самой команды. В конце следует массив дополнительных аргументов, который трогать не будем.

И наконец приступим к работе с нативным кодом. Создаем папку src/android внутри MyPlugin. Здесь будет находиться два Java-класса: MyPlugin.java и MyService.java.

MyPlugin.java:

package com.example.plugin;

import org.apache.cordova.*;
import org.json.JSONArray;
import org.json.JSONException;
import android.widget.Toast;
import android.content.Context;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;

public class MyPlugin extends CordovaPlugin {

    @Override
    public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
        if (action.equals("runBackground")) {
            Context context = cordova.getActivity().getApplicationContext();
            Intent service = new Intent(context, MyService.class);
            context.startService(service);
            return true;
        } else {
            return false;
        }
    }

}

Сначала импортируются библиотеки, в том числе org.apache.cordova.*, которая позволяет взаимодействовать с приложением на базе Cordova. Далее мы можем встретить Notification и NotificationManager — библиотеки для работы с уведомлениями. Ради библиотеки NotificationCompat мы и добавили в plugin.xml строку .

Как только приложение вызывает какую-либо функцию плагина, выполняется метод execute, которому передается строка action. В зависимости от значения этой строки выполняются различные наборы действий. В нашем плагине action может принимать лишь одно значение — runBackground. Создается и вызывается объект нашего фонового процесса.

MyService.java:

package com.example.plugin;

import org.apache.cordova.*;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.content.Context;
import android.os.Handler;
import android.support.v4.app.NotificationCompat;

public class MyService extends Service {

    Handler mHandler = new Handler();

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        mHandler.postDelayed(ToastRunnable, 5000);

        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    Runnable ToastRunnable = new Runnable() {
        public void run() {
            Context context = getApplicationContext();

            NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

            NotificationCompat.Builder mBuilder =
            new NotificationCompat.Builder(context)
                .setSmallIcon(context.getApplicationInfo().icon)
                .setWhen(System.currentTimeMillis())
                .setContentTitle("It works!")
                .setTicker("Ticker")
                .setContentText("Text")
                .setNumber(1)
                .setAutoCancel(true);

            mNotificationManager.notify("App Name", 228, mBuilder.build());

            mHandler.postDelayed( ToastRunnable, 5000);
        }
    };

}

Этот класс — обычный Service. При запуске сервиса его метод onStartCommand возвращает START_STICKY. Благодаря этому даже после закрытия приложения процесс продолжает жить. Чтобы отправлять push-уведомления каждые 5 секунд, был создан поток Runnable, который вызывается хэндлером каждые 5 секунд. В теле потока происходит создание и отправка уведомления.


Добавляем плагин в приложение

Переходим в директорию приложения и добавляем наш плагин:

cordova plugin add ../MyPlugin.

Затем переходим в www/js/index.js и переписываем метод onDeviceReady следующим образом:

onDeviceReady: function() {
       app.receivedEvent('deviceready');

        var failure = function() {
            alert("Error calling MyPlugin");
        }

        MyPlugin.runBackground(function() {}, failure);
}

Через объект MyPlugin вызывается функция runBackground, которой передаем функцию, которая вызывается в случае ошибки и пустую функцию, если все прошло успешно. Эта функция была оставлена пустой, так как подтверждением успешного вызова у нас уже является отправка push-уведомления.


Заключение

Теперь у нас есть работающее приложение, которое отправляет каждые 5 секунд push-уведомления даже после того, как мы его закроем.

image

image

Хочу сказать, что это — лишь каркас для создания полезных приложений. Так, к примеру, можно напоминать человеку о том, что пора пробежаться (не каждые 5 секунд, разумеется). В любом случае, этот пример показывает, что Cordova с использованием плагинов не уступает по функциональности нативным приложениям.

Исходные коды приложения и плагина вы можете загрузить по ссылкам: само приложение, плагин.

© Habrahabr.ru