DroidScript
DroidScript
разработка мобильных приложений

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

DroidScript  
22.12.2016

На этой странице мы начнём разрабатывать универсальный плагин расширения ExtUI для DroidScript, который позволит ощутимо расширить его нативные возможности. Кроме того, разработка и использование плагина поможет в изучении основ практического программирования на Java и API Android, что не будет лишним.

Данный плагин не планируется выпускать в общий доступ, поэтому выбираем командный способ доступа к функциям плагина посредством двух интерфейсных функций: создания объектов и выполнения методов. Такой подход позволит сосредоточиться на реализации внутреннего механизма, а интерфейсную часть можно будет без труда реализовать позже.

В отличие от программирования на JavaScript работа с Java требует точного указания типа используемых данных. Если методу, принимающему в качестве параметра целое число передать вещественное число, то возникнет исключение. Значит, при обработке входных данных в Java-функции необходимо преобразовывать параметры к нужному типу. На этом мы ещё остановимсяа пока займёмся первой функцией для создания объектов.

Мы будем создавать виджеты динамически при помощи конструктора вида plg.CreateExtObject( ИмяКласса ), например:

plg.CreateExtObject( 'Button' );

Виджеты будут генерироваться с использованием рефлексии динамически по названию класса, поэтому ИмяКласса чувствительно к регистру и должно в точности соответствовать его названию в пакете android.widget. Для этого на странице разработчика Android ищем строку android.widget и смотрим в нём название интересующего класса. Общее правило такое: название класса начинается с заглавной буквы и все входящие в названия слова также начинаются с заглавной буквы, например, DatePicker или MultiAutoCompleteTextView. Некоторые объекты API DroidScript совпадают с названиями классов API Android, но не все.

Виджеты имеют конструкторы с параметрами, поэтому метод newInstance() не подходит. Создание экземпляров объектов будет осуществляться посредством их самых простых конструкторов с одним параметром. Вначале создаётся объект общего типа Object, который затем приводится к объекту-родителю - View. Данное приведение необходимо, так как объекты типа Object являются неотображаемыми и не имеют необходимого для отображения объекта метода setLayoutParams, с помощью которого задаются параметры компоновки его содержимого.

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

В DroidScript компоновка используется, большей частью, для организации виджетов внути компоновщика, но, в общем случае, компоновка используется для организации содержимого любого объекта. Можно сказать, что виджет является компоновщиком для своего контента. Такой механизм позволяет более гибко управлять отображением различных данных, по сравнению с подходом, при котором каждому элементу доступна функциональность, обусловленная его предназначением и определённая в рамках оформления операционной системы. Например, если раньше в распоряжении разработчика была серая кнопка, у которой текст всегда расположен по центру, и для изменения её оформления нужно было либо выбрать другой компонент, либо создать свой собственный, то сейчас многое можно легко получить путём изменения соответствующих свойств, например, для задания нужной компоновки, выравнивания надписи, добавления изображения и др.

В конструкторах DroidScript app.Create можно задавать параметры, что позволяет быстро настроить виджет, но мы используем другой подход, при котором объект создаётся с параметрами по умолчанию, а его свойства будут изменяться при помощи методов.

Динамическое создание методов сложнее и практически вся работа здесь ложится на конструктор методов.

Для поиска нужного метода в Java-плагин нужно передать не только название метода, но и типы параметров, которые ему соответствуют. В противном случае при нахождении нескольких методов с одинаковым названием, например, setText, в результат попадёт первый найденный метод, а не тот, который нам был действительно нужен. Например, если мы хотим передать методу setText строку, то нужно вызвать метод setText(CharSequence text), а не setText(int resid), что приведёт к исключению.

Передачу типов параметров можно реализовать по-разному: передавать их в параметрах метода или определять их непосредственно в конструкторе методов.

Выбираем первый подход - передача типов в параметре методов, который обладает большей гибкостью. При определении типа параметров в конструкторе методов пришлось бы учитывать разнообразие типов данных в Java. Например, строковые данные могут иметь тип String, CharSequence или char[], числовые - long, float, int и т.д. К тому же, разработка кода на стороне JavaScript осуществляется проще, чем на стороне Java и имеет смысл сместить её в сторону перевого.

Выбранный нами способ требует при задании методов дополнительной работы - поиск и запись типов для метода. Например, для обновления даты в виджете календаря нужно будет задать список типов "int,int,int", так как метод updateDate принимает три целочисленных аргумента. Но гораздо удобнее работать с методами без у казания типов параметров! Позже нужно будет создать класс-адаптер, который будет будет принимать безтиповые параметры, а отправлять Java-плагину соответствующие им параметры с указанием типов параметров.

Итак, конструктору метода нужно передать следующие данные:

  • Методам установки свойств (сеттеры):
    • Сылка на объект - объект
    • Команда (название метода) - строка
    • Типы параметров - список
    • Параметры - значения
  • Методам возвращающим значения свойств (геттеры):
    • Сылка на объект - объект
    • Команда (название метода) - строка
    • Типы параметров - пустая строка

Первые три параметра обязательные, четвёртый - необязательный. Не все методы являются параметризированными.

Возникает вопрос, а как конструктор методов узнает, какого класса объект ему передаётся из DroidScript? Мы же не указываем его тип! Исходно объекты API DroidScript - это Java-объекты, взаимодействие с которыми в DroidScript происходит посредством JavaScript-интерфейса, ограничивающего их функциональность. Передача объекта плагину осуществляется из JavaScript окружения, но передаётся Java-объект, класс которого можно получить при помощи метода getClass().

Для облегчения процесса отладки и выполнения методов они будут возвращать строковое значение:

  • Сеттеры - пустую строку или строку с ошибкой вида "error: описание ошибки"
  • Геттеры - строку со значением свойства, пустую строку или строку с ошибкой вида "error: описание ошибки"

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

Вызов конструктора методов осуществляется так:

  • Из приложения DroidScript вызывается интерфейсный метод плагина
  • Интерфейсный метод плагина направляет плагину команду и параметры для вызова нужной функции
  • Обработчик комманд в плагине вызывает нужную функцию для приёма данных
  • Функция приёма данных вызывает конструктор методов

Можно было бы обойтись и без промежуточной функции приёма данных и сразу вызывать конструктор методов, но такой вариант снижает гибкость модели и противоречит рекомендации "одна функция - одно действие".

Вызов интерфейсной функции плагина осуществляется так:

plg.runMethodNot( myBtn, 'setRotation', 'int', 45);

, где

  • myBtn - ссылка на объект
  • setRotation - название метода
  • 'int' - тип параметра
  • 45 - значение параметра

В этом методе используются строковые параметры, в которых можно задавать любые строки, например, "setrotation" или "INT", но мы будем использовать точные названия методов и типов параметров из API Android, которые можно посмотреть в справке по нему.

Передача команды из интерфейсного метода в плагин происходит так:

Функция приёма данных для конструктора выглядит следующим образом:

При помощи методов плагина DroidScript в Java-плагин можно передать до 8 параметров простых типов, которые будут доступны через объект Bundle под именами с "p1" по "p8". Если внутренней части плагина необходимо передать несколько объектов, то для этого необходимо использовать соответствующее количество методов SendObj.

Для работы с переменным числом параметров разных типов воспользуемся объявлением конструктора методовc переменным числом параметров объектного типа:

private Sting runMethodNot( String p_objType, Object p_obj, String p_paramsTypes, Object... args )

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

  1. Определить класс полученного объекта по извлечённому из входящих данных названия его класса
  2. Выбрать все методы найденного класса
  3. Произвести поиск нужного метода класса по извлечённому из входящих данных его названия
  4. Если найдено несколько методов с одинаковым названием, то уточнить метод путём сопоставления количества и типов его формальных параметров с количеством и типами фактически полученных параметров
  5. Выполнить метод и вернуть результат

С учетом сказанного выше код конструктора с вспомогательной функцией показан в примере ниже.

Обратите внимание на то, что объекты и методы создаются динамически по их названию из строки. В JavaScript данный подход является типовым, но в Android чаще можно встретить динамическое создание объектов при помощи оператора new, что для нашего случая не подходит из-за недостаточной гибкости.

DroidScript  
© 2016-2022  Александр Страшко