Создание нативного iOS плагина для Unity3d. Недокументированные возможности

от автора

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

К сожалению в документации не описано, что делать в случае если вашему плагину необходимо перехватить событие о том, что пользователь подписался на Push Notification или приложение перешло в бэкграунд (AppDidEnterBackgound), тогда как доступ к этим событием бывает необходим для некоторых плагинов, которые вы возможно захотите написать.

Начнем с того, что в документации описано, что можно передавать параметры из C# или js кода в нативный и обратно. Однако явным образом эти правила не описаны, и возможно имеет смысл описать их подробней. Правила о передаче параметров, сформулированные ниже были получены при изучении уже существующих плагинов: Fb Unity SDK и GoogleAnalytics.

Типы параметров, которые можно передавать между нативным кодом и C# (и наоборот):

string = const char* //Из Unity в натив  char* = string  //Из натива в Unity  bool //(конвертация не нужна)  int //(конвертация не нужна)  float //(конвертация не нужна)  

При работе в нативном коде const char* можно просто конвертировать в более удобный для iOS NSString таким образом:

NSString* CreateNSString (const char* string) {      if (string)           return [NSString stringWithUTF8String: string];       else           return [NSString stringWithUTF8String: ""]; } 

Строковые значения, возвращаемые из нативного метода должны быть в кодировке UTF–8, кодироваться и выделяться в куче. Сортировочные вызовы Моно бесплатны для строк. Выделение в куче необходимо, потому что Mono компилятор для возвращаемых С строк создает .NET string и вызывает для возвращаемого значения free.

Для того, чтобы избежать проблем можно вызывать метод, который создает копию строки в куче (из пример Bonjour):

char* MakeStringCopy (const char* string) {       if (string == NULL)            return NULL;        char* res = (char*)malloc(strlen(string) + 1);       strcpy(res, string);       return res; } 

Функционал, связанный с изменением состояния приложения или его view не документирован в Unity, для получения информации был собран проект, в который был интегрирован официальный Fb SDK для Unity3d. В проекте использовалась версия Unity3d 5.1.2f1 и Fb Unity SKD версии 6.2 (Можно найти здесь), iOS SDK 8.4. Проект был собран под iOS, в Player Settings в качестве Scripting Backend был выбран IL2CPP.

Для чего может понадобится данная информация? Для разработки нативного iOS плагина, использующего нативные функции и слушающая сообщения об изменениях состояния приложения (The App Life Cycle — Apple Developer), изменениях состояния главного View приложения и рендерера. К примеру данный функционал может потребоваться для интеграции нативных плагинов социальных сетей или плагинов различных аналитик, у которых есть поддерживаемые нативные iOS плагины, но нет версий плагинов для Unity3d.

В таком случае есть 2 выхода:

  • Сгенерировать XCode проект. Найти исходный код делегата приложения, изменить его и интегрировать плагин. Минус такого подхода в том, что если версия вашего приложения далека от финала и в дальнейшем будут вноситься правки, то XCode проект придется генерировать заново и интегрировать плагин с нуля. Я использовал такой подход только однажды. Издательство настаивало на интеграции собственного трекера инсталлов и покупок, а проекте использовался плагин Unibill, соответственно трекинг платежки был завраплен этим плагином и код, вызывающий трекинг платежки от издателя я вызывал из нативной части исходников Unibill. Это решение использовалось только потому, что времени на то, чтобы написать Unity плагин на основе нативного издатель не давал и сама интеграция заняла полчаса.
  • Использовать нативный функционал Unity3d, реализованный в проекте iOS. Минус в том, что этот функционал не документирован. Однако он используется плагинами Google Analytics и Fb SKD для Unity.

Так как в текущем проекте использовался FB SDK, а времени было достаточно, было принято решение разобраться в том, что происходит под капотом XCode проекта, который генерирует Unity.

В состав FB плагина для Unity входят два файла, которые интересуют больше всего FbUnityInterface.mm и .h, в которых содержится нативный код для iOS.

В .mm файле бросается в глаза такой участок кода:

-(void)didBecomeActive: (NSNotification *)notification {   [FBAppCall handleDidBecomeActiveWithSession:self.session]; } -(void)willTerminate:(NSNotification *)notification {   [self.session close]; }  -(void)didFinishLaunching:(NSNotification *)notification { 

Выглядит так, будто FB перехватывает вызовы стандартных методов делегата приложения. В заголовочном файле мы видим следующие стоки:

#if UNITY_VERSION >= 430 #import "AppDelegateListener.h" @interface FbUnityInterface : NSObject <AppDelegateListener> 

AppDelegateListener.h находится в директории Classes/PluginBase/ Xcode проекта. Для того, чтобы перехватывать события делегата Unity описывает 4 протокола. Для тех, кто не знаком с понятием протокола Objective C — Английская версия и Русская версия документации.

Основным протоколом является LifeCycleListener. Именно его расширяют два из трех оставшихся протокола. Он позволяет слушать события:

- (void)didFinishLaunching:(NSNotification*)notification; - (void)didBecomeActive:(NSNotification*)notification; - (void)willResignActive:(NSNotification*)notification; - (void)didEnterBackground:(NSNotification*)notification; - (void)willEnterForeground:(NSNotification*)notification; - (void)willTerminate:(NSNotification*)notification; 

А также объявляет 2 метода:

void UnityRegisterLifeCycleListener(id<LifeCycleListener> obj); void UnityUnregisterLifeCycleListener(id<LifeCycleListener> obj); 

Первый метод подписывает объект на получение перечисленных событий, второй отписывает. Следующим рассмотрим протокол AppDelegateListener:

@protocol AppDelegateListener<LifeCycleListener> 

Он дает доступ к сообщениям о Push Notifications, открытии ULR приложениям и некоторым другим.Все сообщения, которые доступны классу, реализующему AppDelegateListener можно посмотреть в AppDelegateListener.mm.

Протокол RenderPluginDelegate дает доступ к событиям рендера Unity и также расширяет LifeCycleListener. Исходник содержит хорошие комментарии и информацию обо всех методах можно получить оттуда.

@protocol RenderPluginDelegate<LifeCycleListener, NSObject> 

Последний в очереди протокол UnityViewControllerLIstener. Он позволяет получить доступ к основным событиям главного view приложения. Этот протокол не имеет зависимости от LifeCycleListener. Список событий:

- (void)viewDidDisappear: - (void)viewWillDisappear: - (void)viewDidAppear: - (void)viewWillAppear:  - (void)interfaceWillChangeOrientation: - (void)interfaceDidChangeOrientation: 

Из исходного кода Fb SDK видно, что данные возможности появились в Unity3d только начиная с версии 4.3.

#if HAS_UNITY_VERSION_DEF   #include "UnityTrampolineConfigure.h" #endif #if UNITY_VERSION >= 430 #import "AppDelegateListener.h" @interface FbUnityInterface : NSObject <AppDelegateListener> #else 

Константа HAS_UNITY_VERSION_DEF, хранящая информацию о том, что в проекте есть информация о версии Unity3d, использованной для сборки проекта объявляется в заголовочном файле RegisterMonoModules.h:

void RegisterMonoModules(); #define HAS_UNITY_VERSION_DEF 1 

Это все содержимое RigisterMonoModules.h. Если HAS_UNITY_VERSION_DEF объявлена в проекте, тогда за значением версии Unity3d можно обратиться к константе UNITY_VERSION. Таким образом, если вы используете Unity3d версии ниже 4.3, то не стоит расчитывать на наличие четырех описанных выше протоколов.

Для некоторых нативных плагинов может потребоваться наличие того или иного ключа в файле .plist нативного приложения. По факту plist можно принять эквивалентным xml. В состав Fb SDK входят исходники FbPlistParser и PlistDic, которые можно использовать для добавления ключа, который потребуется вашему плагину. К примеру можно добавить в проект скрипт с PostProcessBuildAttribute для того, чтобы по окончании сборки проекта найти в XCode проекте plist файл и добавить к нему необходимые ключи со значениями.

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

ссылка на оригинал статьи http://habrahabr.ru/post/266893/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *