В фокусе
Читать
12.07.2017

Создание системных сервисов в Android

Игорь Марков, Игорь Починок, НИВЦ МГУ, Auriga Inc.

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

Допустим, мы хотим создать системный сервис, управляющий десятью светодиодами, подключёнными к основной плате устройства. Аппаратная конфигурация состоит из дополнительного микроконтроллера, который управляет светодиодами. Управление самим микроконтроллером осуществляется по шине SPI по заданному протоколу.

Оставим за рамками статьи аппаратную реализацию устройства, управляющего яркостью светодиодов. Достаточно сказать, что оно ожидает 11 байт по шине SPI, первый из которых – фиксированное значение 0xF0, остальные же – значения яркости 10 светодиодов в диапазоне 0 (не горит) до 100 (максимальная яркость).

 Блок-схема программного управления светодиодами в ОС Android

Запрос на установку яркости проходит следующий путь (см. Рисунок):

1. Приложение вызывает метод setLights Java-объекта LedLightsManager.

2. Код этого метода вызывает «связанный» механизмом Binder метод setLights системного сервиса LedLightsService. Этот сервис находится в контексте процесса SystemServer, а не процесса приложения.

3. Код этого метода вызывает метод nativeSetLights, написанный на языке C++.

4. Этот метод, в свою очередь, вызывает функцию liblights_write, находящуюся в модуле HAL (динамически подключаемой библиотеке).

5. В свою очередь, эта функция совершает операцию ioctl над файлом "/dev/spidev0.0".

Эта операция выполняется в коде драйвера spidev, который отправляет данные по шине SPI в микроконтроллер, управляющий светодиодами.

Итак, пошаговая схема действий:

1. Создаём модуль HAL.

Модуль HAL (Hardware Abstraction Layer) – это «драйвер» устройства, работающий в пространстве пользователя (в отличие от драйверов, работающих в пространстве ядра Linux, к которым, как правило, обращается модуль HAL для работы с аппаратной частью).

Код модуля должен располагаться в каталоге device/<производитель>/<устройство>/ledlights. Здесь «производитель» и «устройство» заменяем на конкретные названия производителя и устройства, для которого мы создаём модуль HAL. Например, если бы мы создавали модуль для устройства Nexus 5X, то путь выглядел бы как device/lge/bullhead/ledlights.

В рассматриваемом случае весь код модуля HAL находится в одном файле ledlights.c и выглядит следующим образом:

static int spi_fd;

 

#define LIGHTS_NUM 10

 

static int liblights_write(char *buffer)

{

        char cmd[LIGHTS_NUM + 1];

        cmd[0] = 0xF0;

        memcpy(cmd + 1, buffer, LIGHTS_NUM);

       

        return ioctl(spi_fd, SPI_IOC_MESSAGE(1), &cmd);

}

 

static int ledlights_open(const hw_module_t* module, const char* id __unused, hw_device_t** device __unused)

{

 

    ledlights_device_t *ledlights_dev = (ledlights_device_t*)malloc(sizeof(ledlights_device_t));

    __android_log_print(ANDROID_LOG_DEBUG, "LEDLIGHTS", "HAL module opened");

 

    ledlights_dev->common.tag = HARDWARE_DEVICE_TAG;

    ledlights_dev->common.module = (hw_module_t *) module;

    ledlights_dev->common.version = LEDLIGHTS_API_VERSION;

    ledlights_dev->write = ledlights_write;

  

    *device = (hw_device_t *) ledlights_dev;

 

      spi_fd = open("/dev/spidev0.0", O_RDWR);

 

    return spi_fd >= 0;

}

 

 

static struct hw_module_methods_t ledlights_module_methods = {

    .open = ledlights_open

};

 

struct hw_module_t HAL_MODULE_INFO_SYM = {

    .tag = HARDWARE_MODULE_TAG,

    .module_api_version = LEDLIGHTS_API_VERSION,

    .hal_api_version = HARDWARE_HAL_API_VERSION,

    .id = LEDLIGHTS_HARDWARE_MODULE_ID,

    .name = "LED Lights HAL",

    .author = "Your Name Here",

    .methods = &ledlights_module_methods

};

 

Основной рабочей функцией является ledligts_write, которая отправляет команду из 11 байт по шине SPI на устройство управления яркостью светодиодов.

Функция ledlighs_open инициализирует модуль. Она открывает символьное устройство "/dev/spidev0.0", которое представляет собой интерфейс к драйверу SPI "spidev", работающему на уровне ядра. Также эта функция инициализирует структуру ledlights_dev, заполняя, в частности, указатели на операции, которые предоставляет данный модуль. В нашем случае это одна функция ledlights_write.

Хотим обратить внимание на структуру HAL_MODULE_INFO_SYM. Она определяет идентификатор модуля HAL и указатель на ledlights_module_methods, которая, в свою очередь, содержит указатель на функцию ledlights_open. Эта структура описывает модуль HAL. Она используется при поиске модуля и для его инициализации.

Файл ledlights.c компилируется в динамически подключаемую библиотеку, модуль HAL.

Для этого необходимо добавить файл сборки Android.mk в device/<производитель>/<устройство>/ledlights:

 

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

 

LOCAL_MODULE := ledlights.$(TARGET_BOARD_PLATFORM)

LOCAL_MODULE_RELATIVE_PATH := hw

LOCAL_SRC_FILES := ledlights.c

LOCAL_C_INCLUDES := hardware/libhardware

LOCAL_MODULE_TAGS := optional

LOCAL_SHARED_LIBRARIES := liblog

 

include $(BUILD_SHARED_LIBRARY)

 

Описание же структуры ledlights_device_t и констант LEDLIGHTS_HARDWARE_MODULE_ID и LEDLIGHTS_API_VERSION определяются в файле "hardware/libhardware/include/hardware/ledlights.h":

 

#define LEDLIGHTS_HARDWARE_MODULE_ID "ledlights"

#define LEDLIGHTS_API_VERSION HARDWARE_MODULE_API_VERSION(1,0)

 

struct ledlights_device {

  struct hw_device_t common;

  int (*write)(char *buffer); // buffer is 10 bytes

}

 

typedef struct ledlights_device ledlights_device_t;

 

Данный заголовочный файл используется также и в JNI-части системного сервиса, позволяющим управлять светодиодами.

2. Создаём системный сервис.

Этот сервис состоит из двух частей: части JNI(Java Native Interface), которая расположена в файле "com_android_server_LedLightsService.cpp" в каталоге "frameworks/base/services/core/jni", и части на языке Java.

Основные функции JNI-части:

 

static ledlights_device_t * device;

 

static jint initHAL(JNIEnv *env, object clazz)

{

        hw_module *module;

        int res = hw_get_module(LEDLIGHTS_HARDWARE_MODULE_ID, (const hw_module_t**)&module);

        if (res == 0)

             res = module->methods->open(module, LEDLIGHTS_HARDWARE_MODULE_ID, (hw_device_t**)&device);

        return res;

}

 

static jint nativeSetLeds(JNIEnv *env, jobject jobj, jbyteArray jarr)

{

        int res = -1;

        jbyte *buf = env->GetByteArrayElements(jarr, NULL);

        if (buf) {

             res = device->write((char *)buf);

             env->ReleaseByteArrayElements(jarr, buf, JNI_ABORT);

        }

        return res;

          

}

Эти функции будут использованы из кода сервиса, написанного на языке Java.

Код сервиса frameworks/base/services/core/com/android/LedLights.java:

 

public class LedLightsService extends ILedLightsService.Stub {

 

        private native int nativeSetLeds(byte[] leds);

 

        public boolean setLights(byte[] leds) {

             if (leds.length != 10)

                  return false;

             return nativeSetLeds(leds) >= 0;

        }

  }

 

Это код системного сервиса, выполняющегося в контексте процесса SystemServer. Данный процесс включает в себя десятки других системных сервисов, таких как сервис местоположения, ввода, управления WiFi, телефонией, питанием, и т. п.

Регистрация нашего нового сервиса осуществляется добавлением следующих строк в метод startOtherServices в файле "frameworks/base/services/core/com/android/server/SystemServer.java":

 

try {

        Slog.i(TAG, "LedLights Service");

        ServiceManager.addService(Context.LEDLIGHTS_SERVICE,

             new LedLightsService() );

} catch (Throwable t ) {

            reportWtf("starting LedLightsService", t);

}

 

 

Чтобы к сервису можно было обращаться из других процессов (приложений), создадим интерфейс к сервису в новом файле "frameworks/base/core/android/hardware/ILedLightsService.aidl":

 

package android.hardware;

/** @hide */

interface ILedLightsService

{

      oneway boolean setLights(in byte[] leds);

}

 

Этот интерфейс написан на языке Android Interface Definition Language и описывает единственный доступный метод сервиса, setLights.

Создадим также идентификатор для поиска системного сервиса. Для этого добавим в файл "frameworks/base/core/android/content/Context.java" строку:

 

public final String LED_SERVICE = "led_service";

3. Создаём вспомогательный класс управления сервисом.

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

Код класса:

 

public class LedLightsManager {

 

        private static LedLightsManager sInstance;

        private static ILedLightsService sServiceInstance;

 

        public static LedLightsManager getInstance()

        {

        if ( sInstance == null ) {

 

            sServiceInstance  = ILedLightsService.Stub.asInterface(ServiceManager.getService(Context.LEDLIGHTS_SERVICE));

             if (sServiceInstance != null)

             sInstance = new LedLightsManager();

        }

 

        return sInstance;

        }

 

        public boolean setLights(byte[] leds) {

             try {

             return sServiceInstance.setLights(leds);

             } catch (RemoteException e) {

            return false;

             }

        }

 

}

 

 

Класс расположен в файле "frameworks/base/core/android/app/LedLightsManager.java".

Стандартный способ в ОС Android для получения объекта для доступа к системному сервису из обычного приложения, это вызов Context.getSystemService(<идентификатор_сервиса>);

Для того чтобы обеспечить возможность получать наш объект класса LedLightsManager таким образом, добавим следующие строки в секцию static в файле "frameworks/base/core/android/app/SystemServiceRegistry.java":

 

registerService(Context.LEDLIGHTS_SERVICE, LedLightsManager.class,

                new CachedServiceFetcher<LedLightsManager>() {

                    @Override

                    public LedLightsManager createService(ContextImpl ctx) {

                        return LedLightsManager.getInstance();

                    }

                });

  

4. Собираем прошивку.

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

 

. build/envsetup.sh

lunch

make -j40 update-api

После этого, соберём проект AOSP командой "make -j40". Обновим прошивку устройства одним из двух методов:

1. Перегрузим устройство в режим fastboot командой "adb reboot bootloader". Запишем собранный образ system.img в раздел system: "fastboot flash system system.img; fastboot reboot".

2. Либо обновим содержимое раздела system командами "adb root; adb shell stop; adb remount; adb sync; adb shell start".

Так как появились изменения в системном интерфейсе (API), то соберём Android SDK командой "lunch sdk-eng; make -j40 sdk". При использовании собранного таким образом SDK будут доступны константа Context.LEDLIGHTS_SERVICE и класс LedLightsManager.

5. Создаём приложение.

После обновления прошивки, светодиодами можно управлять из любого приложения. Достаточно использовать следующий код в любом классе, производном от Activity или Service:

 

LedLightsManager manager = getSystemService(LEDLIGHTS_SERVICE);

manager.setLights(new byte[] {100, 0, 0, 0, 100, 100, 0, 0, 0, 100});

 

 

Версия для печати569 просмотров.
Оцените статью по: