Разработка мобильных приложений — это всегда компромисс между тем, что хочется сделать и тем, что позволяет сделать платформа устройства. В этой статье рассказывается о том, как увеличить возможности приложения Android с помощью Golang.
Здесь вы не найдете утечки секретного api android. Используемые механизмы — это стандартные (или почти стандартные) инструменты android и golang, описание которых есть на официальных сайтах и профильных форумах. Но в виде единого плана действий, да еще и на русском языке — такое публикуется впервые и эксклюзивно для Хабра.
Предыстория
По многочисленным просьбам пользователей одного из приложений были разработаны предсказания текста. Предсказания обслуживал алгоритм radix tree, реализованный в отдельной android-библиотеке. Алгоритм показывал хорошую производительность наряду с экономным потреблением ресурсов — выдавал результат за десятки миллисекунд при потреблении 1,5 Мб памяти. Вполне рабочие показатели. Но так было только при изолированном тестировании библиотеки на JVM.
Проблемы начались при подключении библиотеки к приложению. На android алгоритм предсказаний стал тормозить, выдача результатов затянулась до 7-ми секунд. Ничего себе! Да за 7 секунд можно вручную набрать слово, стереть и набрать его правильно. Такие предсказания никуда не годились.
Проведенный анализ не выявил проблем кода. Алгоритм работал безупречно, памяти потреблял в строго отведенных количествах, не отвлекался на посторонние процессы, все асинхронно, аппаратных ресурсов предостаточно. Вывод напрашивался только один — виртуальная машина android dalvik имеет производительность, отличную от JVM, и в рамках dalvik данный код обречен на тормоза.
Тогда и было решено, что алгоритм предсказаний нужно выносить из dalvik. Для этого существует JNI — Java Native Interface. Механизм, позволяющий вызывать из java методы библиотек, написанных на C/C++. И обратно, — из библиотеки C/C++ вызывать методы java.
Решение
Для создания нативной библиотеки был выбран язык Go. Просто потому что имеется опыт работы с ним, в отличие от C/C++. Golang восхитителен, но этот путь имеет свои трудности, поскольку мы получаем дополнительный уровень сложности. Для чистого C/C++ достаточно использовать NDK и следовать инструкциям, описанным на сайте android developers. Для Golang придется изучить еще и компиляцию golang под android, начать можно здесь.
Впереди лежал путь, полный опасностей и приключений. Но сейчас этот путь уже пройден, и я с удовольствием поделюсь его планом.
План
Среда разработки android:
- ОС: Windows 7.
- IDE: Eclipse with ADT. Но этот план подойдет и для Android Studio.
Среда разработки golang:
- ОС: Для компиляции golang под android используется linux. Я использую Ubuntu 14.04, запущенную на VirtualBox.
- Android SDK.
— Cкачать архив с официального сайта android
— Распаковать, допустим, в
$HOME/android/android-sdk-linux
- Java JDK.
$ apt-get default-jdk
- Go 1.5.
Текущая версия релиза Go — 1.4. Но у меня не получилось скомпилировать библиотеку с его помощью, где-то ошибся при сборке toolchain. Поэтому использовал пакет gomobile из девелоперской версии Go 1.5, релиз которой еще только планируется. Пока нет релиза, установка Go 1.5 описана здесь:
— Установить Go 1.4 golang.org/doc/install
— Клонировать текущий репозиторий golang:
— Скомпилировать текущую версию Go с помощью Go 1.4$ git clone https://go.googlesource.com/go $HOME/go
$ export GOROOT_BOOTSTRAP=/usr/local/go $ cd $HOME/go/src && ./make.bash $ export PATH=$PATH:$HOME/go/bin
- Установить и инициализировать пакет gomobile.
Инициализация пакета занимает приличное время$ go get golang.org/x/mobile/cmd/gomobile $ gomobile init
Разработка библиотеки golang.
- Разрабатывается обычный golang package, никаких дополнительных рекомендаций. Экспортные методы будут доступны из android.
Компиляция библиотеки golang в библиотеку android *.aar.
- Использовать команду gomobile bind с указанием пути до android sdk
$ cd <golang project folder> $ ANDROID_HOME=$HOME/android/android-sdk-linux gomobile bind
Подключение библиотеки *.aar к приложению:
Прежде всего, передать библиотеку *.aar на машину разработки android. Далее в зависимости от IDE.
для Eclipse:
- Распаковать библиотеку *.aar во временный каталог
- В Eclipse создать новый проект из существующего кода, указав временный каталог из п.1
- Отметить, что данный проект является библиотекой android
- Подключить каталог jni и файл classes.jar в Build Path нового проекта
- Подключить новый проект как библиотеку в проект приложения
- Скопировать содержимое папки jni в папку libs проекта приложения.
- Скопировать содержимое proguard.txt библиотеки в proguard.txt проекта приложения (защита классов go* от обфускации)
- При последующих обновлениях библиотеки *.aar достаточно обновить файлы в папках jni, libs и файл classes.jar
для Android Studio:
- Подключить готовую библиотеку *.aar к проекту, используя стандартные средства Android Studio. К сожалению, у меня нет такого опыта, поэтому оставляю это в качестве домашнего задания.
Вызов методов нативной библиотеки из android.
- Перед использованием нативной библиотеки в приложении, нужно выполнить её инициализацию. Подходящее для этого место — в методе onCreate:
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); Go.init(getContext()); } }
- Методы библиотеки golang доступны через package, одноименные с теми, что вы определили в golang. Например:
golang:
package radix … func Suggest(params string) string { … }
java:
suggestions = Radix.Suggest(params);
Результат
После переноса алгоритма в нативную библиотеку, предсказания стали работать даже быстрее, чем в JVM — результат отдается менее чем за 10 мс. Также уменьшилось количество garbage collection, потому что часть ресурсов была передана на сторону нативного кода.
Эффект от внедрения JNI превзошел ожидания. С такими показателями клавиатура и отправилась в релиз.
Заключение
Ускорить приложение android с помощью golang — это вовсе не теория, а реальная возможность, которая уже используется в приложениях маркета.
Конечно, это усложняет разработку приложений, зоопарк инструментов удваивается. Но это даёт возможность реализовать интересные решения.
С выходом Go 1.5 ожидается, что интеграция android-golang станет еще доступнее, за счет инструментария gomobile, который сокращает весь процесс до 2-х команд.
Комментарии (29)
Suvitruf
18.06.2015 16:21+3Неделя Go на Хабре прям.
Проведенный анализ не выявил проблем кода. Алгоритм работал безупречно, памяти потреблял в строго отведенных количествах, не отвлекался на посторонние процессы, все асинхронно, аппаратных ресурсов предостаточно. Вывод напрашивался только один — виртуальная машина android dalvik имеет производительность, отличную от JVM, и в рамках dalvik данный код обречен на тормоза.
Вот так просто — причина в dalvik? Или причина в каких-то участках кода, которые тормозят под dalvik? Вы профилирование кода когда проводили, не нашли этих участков?
Мне просто видится странным полностью переписывать реализацию на другом языке, при этом не попытавшись оптимизировать уже существующий вариант. Или, по крайней мере, разобраться, что именно тормозит.ReshetnikovAF Автор
18.06.2015 16:48+1Переход на другой язык не было простым решением. В анализ сложившейся ситуации было вложено немало времени. Да, был небольшой маневр для оптимизации, но кардиналных улучшений не предвещал. Против идеи продолжить оптимизировать java-код также был тот факт, что Google Keyboard использует для своих предсказаний именно нативный код, правда не на golang. Возможно, есть решение этого вопроса и в dalvik, но это уже будет совсем другой алгоритм, и с неопределенными сроками завершения.
divan0
18.06.2015 16:32Ничего себе. Даже будучи оптимистически настроенным касательно Go на Android/iOS, уж никак не ожидал подобной истории так быстро (особенно учитывая совсем свежий статус gomobile).
Разрабатывается обычный golang package, никаких дополнительных рекомендаций. Экспортные методы будут доступны из android.
А в «обычный golang package» всё же нужно импортить что-то из gomobile и вызывать какие-то функции или это может быть уже существующий пакадж, который писался ещё до того, как gomobile был в мыслях?ReshetnikovAF Автор
18.06.2015 17:01+8Ничего дополнительного с кодом golang делать не надо. Ему вобще не интересно, где и кто его запускает. Всю работу сборки библиотеки для android проделает gomobile.
powerman
18.06.2015 18:29+1Вроде ходили слухи что скоро можно будет на Go полностью разрабатывать приложения для Android (т.е. обходиться одним Go, без Java). Есть какие-то подвижки в этом направлении?
ReshetnikovAF Автор
18.06.2015 19:30+1В настоящее время есть возможность создать android приложение полностью на golang, но доступны не все api android. Полная поддержка api пока в планах. Здесь подробнее.
selenite
18.06.2015 19:12Да-да, нужно только исправить линкер, а то его писало 2.5 хипстера в обеденные перерывы, сделать нормальную поддержку x86 архитектуры (а то мои appium-тесты и android-x86 «чота не работают» (ц)), и добавить вменяемый механизм вызова java-методов, не требующий вставки кода на C (https://github.com/golang/mobile/blob/master/audio/al/al_android.go — ШТА ЭТО?).
ReshetnikovAF Автор
18.06.2015 19:36+1Мне даже страшно представить то время, когда golang покроет весь функционал android, а заодно и ios. Это какая-то утопия. Но судя по происходящему, это весьма вероятная реальность недалекого будущего.
ScratchBoom
18.06.2015 20:34+4В java коде была явно какая-то проблема (а скорее всего — в его интеграции на андроид). Не может время работы уменьшиться с 7 секунд на java до 10мс на Go.
ScratchBoom
18.06.2015 20:52+2Есть ли возможность попробовать снять трейс методов на java коде? (кнопочка start method tracing в Android Studio на вкладке Android). Уж больно интересно, что там может тормозить.
ReshetnikovAF Автор
18.06.2015 21:05+1Боюсь, такой возможности нет. Но при разработке следующего приложения обязательно обращусь за помощью.
ReshetnikovAF Автор
18.06.2015 21:02+1Согласен, вы можете сомневаться в качестве проведенного анализа проблемы. Но даже в этом случае мы стоим перед фактом, что библиотека на golang заработала, а на java — нет. И у меня даже нет сомнений, на чем реализовывать требовательный функционал в следующий раз.
ScratchBoom
18.06.2015 21:06+5То что вы завели Go на андроиде это похвально, но не разобравшись с ситуацией, мы получим ещё один миф, что java тормозит.
ReshetnikovAF Автор
18.06.2015 21:09Ни в коем случае не хотел бы поучаствовать в создании такого мифа. Люди, слышите, java не тормозит! Статья не об этом, если кому-то показалось.
SamKrew
19.06.2015 02:06+1Вообще показалось. Из статьи выходит, что проще переписать на Go, чем пытаться оптимизировать java.
vics001
19.06.2015 02:24+1Да нет такого мифа есть куча измерений. Посмотрите на измерение сортировки на Dalvik. Главное упущение в статье, что Android уже не использует Dalvik (ADT). Во-вторых Dalvik тоже сильно отличается 1.5 от 2.3 (появился первый JIT). У меня есть достаточно примеров, которые тормозят, в свое время когда пытался оптимизировать, приходилось делать совершенно идиотские оптимизации как реиспользование объектов, лишь бы не выделять память заново. В конце концов, эти оптимизации устарели, а еще, хуже всего, вели к логическим ошибкам. Лучше уже было на С важные куски написать. В общем, фризы — бич Dalvik интерпретатора.
И не java тормозит, а Dalvik совсем никакой по сравнению с JVM.ReshetnikovAF Автор
19.06.2015 06:35На самом деле, в новом android runtime ART получились такие-же рузультаты, как на dalvik. Не стал включать это в статью, наверное, зря. Вынос требовательного функционала в нативный код остается пока актуальным вариантом.
vba
19.06.2015 09:40Добрый день, подскажите пожалуйста где можно найти туториал для ознакомления с golang для людей без С++ прошлого. Спасибо.
ReshetnikovAF Автор
19.06.2015 10:34+1Знания с++ при разработке на golang потребуются только в специальных случаях. Так что начинать изучать можно с любого доступного места. Например, отсюда. На Хабре также полно материала.
asmaster
19.06.2015 13:49+1Очень круто, спасибо!
Можно ссылку на приложение?
Зарубежное Go-community заинтересовано в переводе: www.reddit.com/r/golang/comments/3aam6g/faster_android_application_using_golang_autoReshetnikovAF Автор
19.06.2015 14:06Надеюсь, это не будет считаться рекламой play.google.com/store/apps/details?id=com.rpkue.android.qwas
Да, реддит видел, спасибо, перевод уже в процессе.
ivanrt
25.06.2015 12:20А каков размер библиотеки получился? По моим наблюдениям бинарники у Go довольно не маленькие. А так идея заманчивая.
ReshetnikovAF Автор
25.06.2015 13:59Да, библиотека получается большая. В моем случае — 3,5 Мб. Но она не отъедает dalvik heap, а в сжатом виде имеет вполне приличный размер: в моем случае — 1 Мб. Поэтому размер библиотеки не должен стать ограничением этого метода.
ingrysty
Чувак, большое тебе спасибо за продвижение Go в сторону Андройда :)
ReshetnikovAF Автор
Спасибо ребятам, которые стоят за golang
ingrysty
Им потом памятник обязательно поставят.
А вот развитие Go в русском сообществе, а особенно популяризация в мобильной составляющей, это сейчас достаточно важно.