Как стать автором
Обновить

Портирование на Android

Время на прочтение5 мин
Количество просмотров15K

Портирование на Android



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

Движок


Движок (хобби) находится в разработке уже 10 лет.
Движок полностью написан на C/C++, до начала портации на Android поддерживал iOS и Windows.
Логика, рендеринг, звук — все на C/C++.


Файловая система


Важная особенность которая сделала портацию на Android очень простой — это файловая система движка.

Псевдо код:
class IStream
{
 void setName(string name ) = 0;
 open() = 0;
 write() = 0;
 … = 0;
}

class FileStream : public IStream
{
 имплементация через fopen,fread... POSIX API.
}

class DataPackStream : public IStream
{
имплементация для работы с .pak файлами
Core::Meta* m_packFileStreamMeta;; /// m_packFileStreamMeta->Create() IStream* этого типа будет использован для работы непосредственно с .pak файлом
}


Итак: вся работа с файлами в движке основана на интерфейсе IStream, архитектура движка поддерживает фабрику объектов, и её кастомизацию.

Пример кода:
Core::FileStream::Type()->setFactoryMeta(DataPack::DataPackStream::Type());

FileStream* filestream1 = FileStream::Create();
// тоже самое будет и при new FileStream()
FileStream* filestream2 = FileStream::CreateExact();

filestream1 будет типа DataPackStream
filestream2 будет типа FileStream


Ничего нового и все довольно стандартно.

Так как все ресурсы запрепроцесенны в .pak файл, то для Android системы пришлось написать свою реализацию IStream специально для работы с Java.

Инициализация данного ресурса на Java
AssetFileDescriptor RawAssetsDescriptor = this.getApplicationContext()
				.getResources().openRawResourceFd(R.raw.data000);
if (RawAssetsDescriptor != null)
{
    FileInputStream fis = RawAssetsDescriptor.createInputStream();
    NativeMethods.dataPackChannel = fis.getChannel();
    NativeMethods.dataPackOffset = RawAssetsDescriptor.getStartOffset();
    NativeMethods.dataPackSize = RawAssetsDescriptor.getLength();
}


Нейтив класс все вызовы open,read,seek транслирует опять в Java
class AndroidPackStream : public IStream
{
//....
    // пример работы read
    size_t read(void *buffer, size_t size, size_t count)
    {
      if( JavaHelpers::m_pClass )
      {
        jmethodID mid = JavaHelpers::GetEnv()->GetStaticMethodID(JavaHelpers::m_pClass, "freadDataPack", "(I)I");
        int res = JavaHelpers::GetEnv()->CallStaticIntMethod(JavaHelpers::m_pClass, mid, (int)size*count);
	jfieldID field = JavaHelpers::GetEnv()->GetStaticFieldID(JavaHelpers::m_pClass,"byteBuffer","Ljava/nio/MappedByteBuffer;");
	jobject obj = JavaHelpers::GetEnv()->GetStaticObjectField(JavaHelpers::m_pClass,field);
	uint8_t* pData=(uint8_t*)JavaHelpers::GetEnv()->GetDirectBufferAddress(obj);
	memcpy(buffer,pData,size*count);
	JavaHelpers::GetEnv()->DeleteLocalRef(obj);
	return res/size;
    }
}


Java реализации чтения данного стрима с возвратом данных назад в нейтив
public static int freadDataPack(int count)
{
    long curPos = dataPackChannel.position();
    int countReaded = count;
    byteBuffer = dataPackChannel.map(MapMode.READ_ONLY,dataPackChannel.position(), count);
    byteBuffer.load();
    dataPackChannel.position(curPos+countReaded);
    return countReaded;
}


Важный момент — сборщик apk сжимает все ресурсы, а мы хотим читать файл как будто он просто лежит в файловой системе. Для того чтобы наш .pak файл не сжимался внутри apk — вы должны сменить ему расширение на одно из тех которые будут говорить упаковщику не сжимать данные файлы при упаковке (детали ponystyle.com/blog/2010/03/26/dealing-with-asset-compression-in-android-apps). Я выбрал .imy.
Загрузка ресуров таким способом очень быстра, например на Kindle Fire работает быстрее чем на iPad 1
Оговорю стразу что такой финт можно провернуть если у вас не очень много данных.
Для большого объема данных — вы можете сами распаковать данные на внутрений носитель(например при первом запуске) и напрямую их использовать посредством функций fopen fread (в моем случае, через не подмененный FileStream) и.т.д.

Звук

Для Windows и iOS был использован OpenAL. OpenAL для Android был собран благодаря pielot.org/2010/12/14/openal-on-android. Заработал не сразу, а только после исправлений которые описаны в комментариях на вебсайте.
Сборка под Android собрана только для Arm v7 — потому что сборка порта под Arm v6 OpenAL иногда приводила к лагам, в iOS OpenAL микшер работает быстрее и без лагов (даже на ArmV6, например на iPod Touch 2G).

Взаимодействие с нативным кодом из С/C++

За обработку вызовов платформо зависимых реализаций отвечает очень простая связка классов
Псевдо код:

class IPlatfomCommandFeedback
{
    void onResponse(string&);
};
class IPlatfomCommand
{
    string execute(string& command, IPlatfomCommandFeedback* feebback);
};


Для iOS своя реализация на Objective-C а для Android своя реализация на JNI->Java.

Рендеринг

Нейтив часть работает с использованием OpenGL, для Android были сделаны минимальные изменения. Как в последствии оказалось этого было недостаточно. Дело в том что на Windows и на iOS текстуры не теряются когда приложение уходит в бакграунд, а вот для Android это происходит всегда. В движке уже был менеджер текстур (в основном для отладки) и добавить перезагрузку ресурсов оказалось не сложно.

Сборка

В самой первой статье я писал о том, что изначально компилировал iOS с использованием toolchain и у меня были настроенные makefile. Вот тут то мне это и пригодилось. Были дописаны таргеты для сборки с использованием Android NDK, добавлен степ в билдеры Eclipse и все взлетело. Да, можно использовать билд систему из Android NDK samples. Её я использовал только для выяснения параметров которые используются для вызовов gcc.

OpenFenit AdMob

Интеграция обоих библиотек прошла без проблем — четко по инструкции разработчиков

Proguard

Тут все более или менее понятно — нужно только позаботится о том чтобы ваши JNI Native бинды не обускейтились

Средства разработки

Железо: Kindle Fire, и пару планшетов и мобильных телефонов друзей(для тестирования).
Софт: Eclipse с плагинами

Шок номер 1

Android Market это не AppStore. Там все по другому. Там нет категории New.
Для сравнения на AppStore в первый день релиза было 800+ скачек, во второй 2000+, на Andoid Market официальная первая сотня скачек была получена только на третий день. Активность по раскрутке для обоих платформ была одинакова

Шок номер 2

Если ваше приложение бесплатное вы можете свободно распространять apk, любой человек может проинсталировать себе данное приложение с любого источника. Если это сделаете не вы, то за вас это сделают другие.

Шок номер 3

Бестиарий устройств велик. Количество проблем соответственно.

Amazon

Процесс ревью на Amazon AppStore for Android занимает неделю, плюс еще несколько дней пока приложение появится в списке AppStore на Kindle Fire. Количество скачек и динамика соответствует Android Market

Реклама

Лучи ненависти Корпорации Добра — уже неделя как рекламный баннер был отправлен на апрув. До сих пор тишина.

Деньги

На данный момент количество денег (напомню в игре только реклама) которые приносит iOS версия в 40 (сорок) раз больше чем версия для Android. Понятно, что сравнение не корректно и надо подождать как минимум еще пару месяцев пока не стабилизируется количество постоянных игроков в день.

UPDATE: В секции звука я описал причину сборки только для ArmV7 Android устройств.
Теги:
Хабы:
+22
Комментарии17

Публикации

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн