четверг, 10 июля 2014 г.

Как создавать обёртки для JAVA-кода

В этой статье я постараюсь объяснить, как создавать обёртки, самым доступным языком и с примером. В блоге я уже выкладывал собственную обёртку для работы с Wi-Fi, вот её мы и рассмотрим в качестве примера. Почти всё, что написано ниже, справедливо для любых JAVA-классов. В скором времени я покажу, как работать с собственными JAVA-классами в Delphi.

Update (11.07.14) - Продолжение тут "Как подключить и использовать свой JAVA-класс"



Начнём.

Рассмотрим задачу на примере обёртки для Android API. Допустим, вы решили написать обёртку для package android.net.wifi.

Что нужно знать/понимать:

1)  Необязательно писать обёртку для всех классов и методов, можно писать только для необходимых вам классов/методов

2)  Все классы и методы можно найти в трёх местах
  • На сайте Android API
  • В файле «api-versions.xml», данный файл хранится в папке «C:\Users\Public\Documents\Embarcadero\Studio\14.0\PlatformSDKs\adt-bundle-windows-x86-20131030\sdk\platform-tools\api», в этом файле вы также найдёте сигнатуры
  • В вашем собственном исходнике JAVA-класса (об этом поговорим в след. статье)
3)  Для связи используются сигнатуры, записываются вот так (пример)
  [JavaSignature('android/net/wifi/WifiManager$MulticastLock')]

4)  Соответствие типов JAVA в Delphi
Java Type
Delphi Type
Type Name
Signature
byte
ShortInt
JByte
B
char
WideChar
JChar
C
double
Double
JDouble
D
float
Single
JFloat
F
int
Integer
JInt
I
long
Int64
JLong
J
short
SmallInt
JShort
S
void
N/A
N/A
V
boolean
Boolean
JBoolean
Z

Типы, в которых содержатся квадратные скобки, записываются как массивы. 

Пример:
  Имеем тип «public String[]», значит, запишем его как «TJavaObjectArray<JString>».

5)  Необходимо рандомно генерировать GUID – это можно сделать при помощи комбинации клавиш "Ctrl+Shift+G"

Пример:
  ['{1D32FC9E-D6EB-4F1F-9760-1E90D971D602}']

6)  Любой класс, расширяет какой-то другой класс.

Пример:
Класс «ScanResult», расширяет основной класс «java.lang.Object», значит, в обёртке мы также должны расширять «JavaObject».

Обёртка для «ScanResult» будет выглядеть вот так:
  JScanResultClass = interface(JObjectClass)
  …
  JScanResult = interface(JObject)

7)  Поля (Fields):
  • Если тип поля указан как «public String», т.е. имеет только слово «public», то это поле мы пишем в «interface(JObject)». Данное поле мы можем получать и изменять.
  • Если тип поля указан как «public static final String[]», то это поле мы пишем в «interface(JObjectClass)». Данное поле мы можем получить и не можем изменить.


8)  Константы (Constants):
  • Все константы всегда пишутся в «interface(JObjectClass)». Константы можно получать и нельзя изменять.

9)  Методы (Public Methods):
  • Если тип метода содержит слово «static», то такой метод записывается в «interface(JObjectClass)».
  • Если тип метода не содержит слово «static», то такой метод записывается в «interface(JObject)».

10)  Для нормальной работы обёртки, необходимо подключать минимум два модуля:
  • Androidapi.JNIBridge,
  • Androidapi.JNI.JavaTypes;

11)  В конце обёртки, желательно зарегистрировать типы:
procedure RegisterTypes;
begin
  TRegTypes.RegisterType('Название вашего модуля', TypeInfo(‘Название вашего модуля’.’Название интерфейса’)); 
end;

initialization

  RegisterTypes;

end.

На этом всё, теперь рассмотрим примеры:

Примечание: Кучу различных обёрток вы можете найти в исходниках студии, они лежат примерно тут «C:\Program Files\Embarcadero\Studio\14.0\source\rtl\android».


Пишем обёртку для классов «ScanResult» и «WifiInfo».

Запускаем студию, создаём Unit, назовём его «Androidapi.JNI.Net.Wifi». 
Сразу выложу код:
unit Androidapi.JNI.Net.Wifi;

interface

uses
  Androidapi.JNIBridge,
  Androidapi.JNI.JavaTypes;

type
  {Class forward declarations}
  JScanResult = interface; //android.net.wifi.ScanResult
  JWifiInfo = interface; //android.net.wifi.WifiInfo

JScanResultClass = interface(JObjectClass)
['{5AEBB13C-C013-47CB-B120-F3D1FCFF9BE8}']
end;

[JavaSignature('android/net/wifi/ScanResult')]
JScanResult = interface(JObject)
['{1D32FC9E-D6EB-4F1F-9760-1E90D971D602}']
  {Property Methods}
  function _GetBSSID: JString;
  procedure _SetBSSID(Value: JString);
  function _GetSSID: JString;
  procedure _SetSSID(Value: JString);
  function _Getcapabilities: JString;
  procedure _Setcapabilities(Value: JString);
  function _Getfrequency: Integer;
  procedure _Setfrequency(Value: Integer);
  function _Getlevel: Integer;
  procedure _Setlevel(Value: Integer);
  {Methods}
  function toString: JString; cdecl;
  {Properties}
  property BSSID: JString read _GetBSSID write _SetBSSID;
  property SSID: JString read _GetSSID write _SetSSID;
  property capabilities: JString read _Getcapabilities write _Setcapabilities;
  property frequency: Integer read _Getfrequency write _Setfrequency;
  property level: Integer read _Getlevel write _Setlevel;
end;
TJScanResult = class(TJavaGenericImport<JScanResultClass, JScanResult>) end;

JWifiInfoClass = interface(JObjectClass)
['{E34882C5-CD5D-469C-9020-513FC1C4E48A}']
  {Property Methods}
  function _GetLINK_SPEED_UNITS: JString;
  {Methods}
  //getDetailedStateOf(SupplicantState suppState): NetworkInfo.DetailedState; cdecl;
  {Properties}
  property LINK_SPEED_UNITS: JString read _GetLINK_SPEED_UNITS;
end;

[JavaSignature('android/net/wifi/WifiInfo')]
JWifiInfo = interface(JObject)
['{6E31D165-FE5E-49EF-BE9D-61A93C7A8EAB}']
  {Methods}
  function getBSSID: JString; cdecl;
  function getHiddenSSID: Boolean; cdecl;
  function getIpAddress: Integer; cdecl;
  function getLinkSpeed: Integer; cdecl;
  function getMacAddress: JString; cdecl;
  function getNetworkId: Integer; cdecl;
  function getRssi: Integer; cdecl;
  function getSSID: JString; cdecl;
  function getSupplicantState: JSupplicantState; cdecl;
  function toString: JString; cdecl;
end;
TJWifiInfo = class(TJavaGenericImport<JWifiInfoClass, JWifiInfo>) end;

implementation

procedure RegisterTypes;
begin
  TRegTypes.RegisterType('Androidapi.JNI.Net.Wifi.JScanResult', TypeInfo(Androidapi.JNI.Net.Wifi.JScanResult));
  TRegTypes.RegisterType('Androidapi.JNI.Net.Wifi.JWifiInfo', TypeInfo(Androidapi.JNI.Net.Wifi.JWifiInfo));
end;

initialization
  RegisterTypes;
end.

Как видите, получилось всего 80 строчек кода, ни чего сложного в них нет. Я внезапно понял, что и описывать-то в коде нечего уже, т.к. я всё изложил выше в 11 пунктах. Единственное, что ещё раз повторю, необязательно описывать все классы или методы, можно описать только необходимое, даже лучше так делать, т.к. чем больше всего описано, тем больше весит ваше приложение.

Дополнение: Как использовать полученную обёртку. Здесь приведена только часть обёртки и чтобы не захламлять статью, пример использования можно посмотреть тут.


Если у вас появились вопросы, то задавайте. Если вы нашли неточность или хотите дополнить, то лучше пишите мне на почту infocean @ gmail . com (удалите пробелы)

Чуть не забыл сказать, обёртки для всего Android API выложены тут Full Android SDK Interface Files In Object Pascal For Firemonkey, но не забывайте проверять их, т.к. они сгенерированы автоматически и имеют некоторое несоответствие пунктам выше.

Спасибо за внимание.

p.s. В следующей статье вы узнаете, как подключать к проекту любой JAVA-код, как создавать файл classes.dex и т.д.

8 комментариев:

  1. Я так понимаю обертку можно писать для любого Java класса? И можно ли создавать таким образом обертки для более ранних версий Delphi, которые не поддерживают разработку под Android?

    ОтветитьУдалить
    Ответы
    1. Да, в следующей статье я покажу это на примере простейшего Java класса.
      Нет, так делать не получится.

      Удалить
  2. >>Я так понимаю обертку можно писать для любого Java класса?
    Да, и для своих классов тоже
    >>И можно ли создавать таким образом обертки для более ранних версий Delphi, которые не поддерживают разработку под Android?
    Нет. Андрей описал сам принцип написания обертки и немного забыл упомянуть как его использовать, особенно он не затронул тему своего ява кода - хотя ето очень широкая тема и использование своего ява кода возможно только при правильном написании етого кода на ява стороне - к примеру если Вы напишете в своем ява коде Toast.makeText(context, "сообщение", Toast.LENGTH_SHORT).show(); на Андроид студии, еклипсе ето будет работать отлично, но в РАД Студии получите ошибку - надо писать
    mHandler.post(new Runnable() {
    public void run() {
    Toast.makeText(context, mMsg, Toast.LENGTH_SHORT).show();
    }
    }); .
    И для начала ранние версии рада просто не содержат компиляторов под мобильные платформы и там дело не только в компиляторах
    О том как юзать свой ява почитайте на http://cppbuilderxe.blogspot.com/2014/07/cbuilder-xe6.html - там поб билдер но в делфях все точно также, я только не затронул очень важный момент - где что писать в обертке - о чем Андрей как раз очень доходчиво и рассказал, я сейчас в своей статье кину ссылку на ету статью, дабы не присваивать чужие труды

    ОтветитьУдалить
    Ответы
    1. Статья писалась именно о создании обёрток, а не использовании. Однако, я всё же добавлю информацию о дальнейшем использовании обёртки.
      В статье много раз написано, что об использовании собственного Java-кода я буду говорить в следующей статье. Читайте внимательнее :)

      Удалить
    2. Или мне повылазило или ПС дописывалось в тот момент когда я писал комент :) , ну да не в етом суть, жду следующую статью об своем коде - сравним мож я что-то упустил или не достаточно раскрыл

      Удалить
    3. Статья изначально написана/перепроверена/выложена, изменения ещё не вносились, упоминания есть в самом начале, в середине, в конце ;) Но да, это не важно…
      Следующая статья будет возможно завтра вечером.

      Удалить
  3. Доброго времени суток. Если не сложно, тыкните носом, откуда именно черпать информацию для написания оболочки?
    В частности
    function _GetBSSID: JString;
    procedure _SetBSSID(Value: JString); // И остальных ф-й и процедур

    ОтветитьУдалить
  4. Здравствуйте.

    Очень прошу помочь. )

    На руках у меня одно устройство (ККМ), которое умеет соединяться по Bluetooth с android-телефоном или планшетом. Для этого в телефон устанавливается APK-файл, который работает как сервис. По сути, драйвер.

    И из этого драйвера следует дёргать методы, которые описаны в .aidl-файле.

    Как можно из Delphi воспользоваться этим aidl?

    --------------

    package com.some.name.servicename;
    import com.come.ClassName;

    interface IDeviceDriver
    {
    int charLineLength();
    String serialNumber();
    boolean isFiscal();
    ParcelableDate dateTime();
    int mode();
    int subMode();
    int checkState();
    int checkNumber();
    int docNumber();
    boolean isSessionOpened();
    ....
    ....
    ....

    Спасибо!

    ОтветитьУдалить