В 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/
Добавить комментарий