Продолжение той самой истории.
Первая часть тут, вторая тут, третья тут.
4,5 года назад я имел неосторожность начать писать свою криптовалюту на совсем неподходящем для этого дела языке — на PHP. В итоге, конечно, написал (я упрямый), но получился костыль на костыле и то, что оно вообще работало было просто какой-то магией.
Сразу хочу предупредить, программер я самоучка-недоучка и пишу код, мягко сказать, неидеально.
Началось всё с того, что я расстался с девушкой, по имени Катя и в этот же день (4 апреля 2015-го) решил изучить Go и переписать свою криптовалюту. Писать про Катю не под спойлерами не могу, т.к. хабр всё же для IT-шных статей, а не для любовных рассказов и суровые айтишники, которым интересна тема Go, могут просто не обращать внимание на спойлеры «про Катю».
Итог 8 месяцев: приложение работает на Win (64/32), OSX(64/32), Linux(64/32), FreeBSD(64/32), Android, IOS.
Общего кода ~73к строк, кода под разные ОС где-то несколько сотен строчек.
40к — обработка/генерация блоков/тр-ий, 17.5к — контроллеры для интерфейса, 15.5к — шаблоны.
Поддерживаются PostgreSQL, SQLite, MySQL.
Тех, кто будет тестировать мое творение, предупреждаю — могут быть баги, и если у Вас есть время, черкните о них, пожалуйста, на darwin@dcoin.club или в личку на хабре. Пожелания и советы тоже приветствуются.
В первых трех частях я рассказал про то, как в dcoin функционирует веб-сервер, про html/template, базы данных, плавное завершение приложения, шифрование и парсинг блоков.
В этой статье я расскажу про работу с Go на Android.
Начало
Смешно сказать, но мой первый андроид появился у меня этим летом. До этого я просто не находил причин, чтобы заменить свою Nokia 1200. Купил дешевый ZTE за 3000 руб с 512 памяти и 2-я ядрами. Для тестирования самое то. И звонить с него тоже можно.
Хотелось сделать компиляцию через github.com/golang/mobile в apk. Посмотрел мануалы, вроде всё просто. Почти сразу получилось скомпилировать бинарник и запустить его под рутом на андроиде. Обрадовался, что всё идет как по маслу и казалось, что через пару дней у меня будет apk, запустив который я увижу Dcoin.
Собрать Apk оказалось не сложно. В принципе, всё что мне было нужно — это автоматически открыть в браузере 127.0.0.1:8089. Вот тут-то я забуксовал. Несколько дней гуглил и экспериментировал и всё чего смог добиться — это отрисовка картинки на которой я прошу пользователя открыть в браузере нужный хост.
Решил зайти через aar. Т.е. добавить его как библиотеку в андроид-студио. И средствами студии уже открыть браузер или webview. Но sqlite упорно не хотело компилиться, оказалось что ошибка в компиляторе C и решения проблемы на тот момент не было (сейчас, кстати, уже есть).
Про Катю
(Окончание из предыдущей части): Написал ей в ВК, сказала, что тел дома забыла, а сейчас у подруги. Я написал, что подожду её. После чего получил «Не звони и не пиши мне больше!!!!». Вопросов задавать не стал, позвонил в соседнюю квартиру, попросил передать цветы Кате, когда она будет дома. Приехал домой, через несколько часов принял решение переписать Dcoin на Go.
Через пару дней написал ей, что на несколько месяцев ухожу с головой в свой проект и попросил не беспокоить меня по пустякам.
Через неделю от Кати пришла смс-ка «привет. ну как ты там?». Я не ответил. Через неделю еще одна «привет. как дела?». Я снова не ответил.
Через пару дней написал ей, что на несколько месяцев ухожу с головой в свой проект и попросил не беспокоить меня по пустякам.
Через неделю от Кати пришла смс-ка «привет. ну как ты там?». Я не ответил. Через неделю еще одна «привет. как дела?». Я снова не ответил.
GoNativeActivity
В какой-то момент стало очевидно, что без этого волшебного файлика ничего не выйдет. Начал экспериментировать, внес несколько изменений, генерирую apk и ничего не меняется. Через пару дней не выдержал и решил написать одному из разработчиков gomobile, ответ пришел довольно быстро. Оказывается, после изменений в GoNativeActivity надо вызывать go generate github.com/c-darwin/mobile/cmd/gomobile, чтобы сгенерировался .dex файл и только после этого go install github.com/c-darwin/mobile/cmd/gomobile.
Научившись править GoNativeActivity я получил огромные возможности. Нужно было лишь уметь писать на Java. А я не умел, и сейчас не умею. Но кое-что всё же смог сделать. Тут мой GoNativeActivity. Чуть позже понял, как создать свой AndroidManifest.xml, что дало еще больше возможностей, в итоге вместо работы в браузере я смог добиться работы в WebView, тут моя реализация вебвьюхи.
Про Катю
Еще примерно через неделю она написала, что ей срочно нужно 7 т.р. в долг, т.к. ей не хватает на оплату за квартиру. Я ответил «OK». На следующий день она приехала ко мне домой.
Уведомления
Мне захотелось сделать уведомления, когда приходят деньги или поступает входящий запрос на обмен монет на фиат. После гугления и изучения stackoverflow получился такой код:
public void notif(String title, String text) {
Intent intent = new Intent("org.golang.app.MainActivity");
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
// с этой иконкой пришлось повозиться, нижу расскажу
mBuilder.setSmallIcon(R.drawable.icon);
mBuilder.setContentTitle(title);
mBuilder.setContentText(text);
Intent resultIntent = new Intent(this, MainActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(MainActivity.class);
// Adds the Intent that starts the Activity to the top of the stack
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// notificationID allows you to update the notification later on.
mNotificationManager.notify(2, mBuilder.build());
}
Осталось только понять, как этот notif дергать из Go. Вот тут я застрял еще на недельку. Оказалось, что надо использовать некого зверя под названием JNI. Получается что-то вроде такой конструкции: Go вызывает C, который запускает Java-машину и дергает через неё мой notif. Ужас. Особенно учитывая, что в C, я также как и в Java, почти полный ноль.
Короче, после долгих мучений я смог таки написать рабочий код и даже понять, что в нем происходит:
package notif
/*
#cgo LDFLAGS: -llog -landroid
#include <android/log.h>
#include <jni.h>
#include <stdlib.h>
#define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "Go/fatal", __VA_ARGS__)
void notif_manager_init(void* java_vm, void* ctx, char* title, char* text) {
JavaVM* vm = (JavaVM*)(java_vm);
JNIEnv* env;
int err;
int attached = 0;
err = (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6);
if (err != JNI_OK) {
if (err == JNI_EDETACHED) {
// присоединяемся к JM
if ((*vm)->AttachCurrentThread(vm, &env, 0) != 0) {
LOG_FATAL("cannot attach JVM");
}
attached = 1;
} else {
LOG_FATAL("GetEnv unexpected error: %d", err);
}
}
// преобразуем в jstring наш title
jstring javaTitle = (jstring)(*env)->NewStringUTF(env, (const char *)title);
// преобразуем в jstring наш text
jstring javaText = (jstring)(*env)->NewStringUTF(env, (const char *)text);
//указатель на класс, к которому относится объект
jclass cls = (*env)->GetObjectClass(env, ctx);
// идентификатор метода.Ljava/lang/String;Ljava/lang/String - тип передаваемых данных и V(Void) - возвращаемых
jmethodID nJmethodID = (*env)->GetMethodID(env, cls, "notif", "(Ljava/lang/String;Ljava/lang/String;)V");
// вызываем сам метод
(jstring)(*env)->CallObjectMethod(env, ctx, nJmethodID, javaTitle, javaText);
if (attached) {
// отсоединяемся от JM
(*vm)->DetachCurrentThread(vm);
}
}
*/
import "C"
import (
"github.com/c-darwin/mobile/internal/mobileinit"
)
func SendNotif(title string, text string) {
ctx := mobileinit.Context{}
C.notif_manager_init(ctx.JavaVM(), ctx.AndroidContext(), C.CString(title), C.CString(text))
}
По аналогии у меня получился пакет get_files_dir.go который получает рабочую директорию.
Про Катю
Я не знаю, что у неё было в голове, наверное думала, что я буду к ней приставать. Весь вечер она была какой-то странной. Я был холоден и старался избегать любых прикосновений к ней. Мы поели, попили чаю. Катя сказала, что ей надо домой. Я вызвал такси.
Когда провожал её до такси спросил:
— Мы ведь не вместе да? Т.е. я могу делать что захочу?
Она: — В смысле «что захочу»? нет, мы вместе (пододвигается ко мне)
Я: — Ну у нас же не было первого свидания, мы еще не начали всё с начала.
Она: — Так вот было же только что.
Я: — Какое же это свидание, ты просто за деньгами приехала.
Мы уже стояли у дверей такси, я её усадил, расплатился с водителем, она уехала.
Когда провожал её до такси спросил:
— Мы ведь не вместе да? Т.е. я могу делать что захочу?
Она: — В смысле «что захочу»? нет, мы вместе (пододвигается ко мне)
Я: — Ну у нас же не было первого свидания, мы еще не начали всё с начала.
Она: — Так вот было же только что.
Я: — Какое же это свидание, ты просто за деньгами приехала.
Мы уже стояли у дверей такси, я её усадил, расплатился с водителем, она уехала.
На Go получился вот такой код для вызова уведомления:
// +build android
package sendnotif
import (
"github.com/c-darwin/mobile/notif"
)
func SendMobileNotification(title, text string) {
notif.SendNotif(title, text)
}
Про Катю
Утром зазвонил телефон, это была Катя. Я не поднял трубку.
Сервис
Заметил, что веб-серер стал постоянно падать. Работать с кошельком было невозможно. Погуглил, понял, что надо делать сервис. На этот раз сложностей было уже не так много, исходник тут. ShortcutIcon() создает иконку на рабочем столе.
Про Катю
На следующий день она прислала смс-ку «привет. я вчера звонила, ты не взял трубку». Я не стал отвечать.
Доступ к иконке
При появлении уведомлений нужно указать иконку. Пришлось разбираться как в android устроена работа с ресурсами. Примерно понял, что нужно сгенерировать R.jar и подключить его при генерации dex-файла.
Погуглил, как генерить java файлы, получилась такая команда:
aapt package -v -f -J /home/z/go-projects/src/github.com/c-darwin/dcoin-go/ -S /home/z/go-projects/src/github.com/c-darwin/dcoin-go/res/ -M /home/z/go-projects/src/github.com/c-darwin/dcoin-go/AndroidManifest.xml -I /home/z/android-sdk-linux/platforms/android-22/android.jar
Полученный R.java помещаем в R/org/golang/app/:
mv R.java /home/z/go-projects/src/github.com/c-darwin/dcoin-go/R/org/golang/app/
И генерируем R.jar:
cd R && jar cfv /home/z/go-projects/src/github.com/c-darwin/dcoin-go/R.jar
Генерим неподписанный apk:
aapt package -v -f -J /home/z/go-projects/src/github.com/c-darwin/dcoin-go/ -S /home/z/go-projects/src/github.com/c-darwin/dcoin-go/res/ -M /home/z/go-projects/src/github.com/c-darwin/dcoin-go/AndroidManifest.xml -I /home/z/android-sdk-linux/platforms/android-22/android.jar -F unsigned.apk
Про Катю
Через неделю она написала, что получила з.п. и хочет скинуть мне на карту деньги. Я ни чего не ответил. Через несколько дней она написала «привет. как дела?». Я снова игнорировал.
Вытаскиваем в корень resources.arsc:
unzip unsigned.apk -d apk && mv apk/resources.arsc .
Дальше нужно подправить gendex.go из gomobile:
cmd := exec.Command(
"javac",
"-source", "1.7",
"-target", "1.7",
"-bootclasspath", platform+"/android.jar",
"-classpath", "/home/z/go-projects/src/github.com/c-darwin/dcoin-go/R.jar:"+androidHome+"/extras/android/m2repository/com/android/support/support-v4/22.2.1/support-v4-22.2.1-sources.jar",
"-d", tmpdir+"/work",
)
Про Катю
Еще примерно через неделю я наконец запустил Dcoin на нодах и увидев, как идет генерации блоков и как они летают между нодами, наполняя базы данных. Мне показалось, что это можно считать завершением моего временного отшельничества и я написал Кате.
Генерируем новый Dex:
ANDROID_HOME=/home/z/android-sdk-linux go generate github.com/c-darwin/mobile/cmd/gomobile/
Затем бинарник самого gomobile:
go install github.com/c-darwin/mobile/cmd/gomobile/
И наконец получаем наш apk:
CGO_ENABLED=1 GOOS=android ANDROID_HOME=/home/z/android-sdk-linux gomobile build -v github.com/c-darwin/dcoin-go
В итоге, у меня получился вот такой bash скрипт для генерации apk:
./bindata.sh
echo "######## generate R.java ########"
aapt package -v -f -J /home/z/go-projects/src/github.com/c-darwin/dcoin-go/ -S /home/z/go-projects/src/github.com/c-darwin/dcoin-go/res/ -M /home/z/go-projects/src/github.com/c-darwin/dcoin-go/AndroidManifest.xml -I /home/z/android-sdk-linux/platforms/android-22/android.jar
mv R.java /home/z/go-projects/src/github.com/c-darwin/dcoin-go/R/org/golang/app/
echo "######## generate R.jar ########"
cd R
jar cfv /home/z/go-projects/src/github.com/c-darwin/dcoin-go/R.jar .
cd ../
echo "######## generate unsigned.apk ########"
aapt package -v -f -J /home/z/go-projects/src/github.com/c-darwin/dcoin-go/ -S /home/z/go-projects/src/github.com/c-darwin/dcoin-go/res/ -M /home/z/go-projects/src/github.com/c-darwin/dcoin-go/AndroidManifest.xml -I /home/z/android-sdk-linux/platforms/android-22/android.jar -F unsigned.apk
echo "######## extract resources.arsc ########"
unzip unsigned.apk -d apk
mv apk/resources.arsc .
rm -rf apk unsigned.apk
ANDROID_HOME=/home/z/android-sdk-linux go generate github.com/c-darwin/mobile/cmd/gomobile/
go install github.com/c-darwin/mobile/cmd/gomobile/
CGO_ENABLED=1 GOOS=android ANDROID_HOME=/home/z/android-sdk-linux gomobile build -v github.com/c-darwin/dcoin-go
Про Катю
Она очень позитивно отреагировала на то, что я вышел с ней на связь, согласилась встретиться. Потом сказала, что у неё всё плохо, подружка с которой она снимала квартиру съехала и что она из-за стрессов каждый день плачет, еще и уволиться решила. Я решил, что, когда мы встретимся, предложу ей переехать жить ко мне и ни о чем не париться.
Заключение
В следующей, заключительной статье я расскажу про gomobile и IOS. И, наконец, будет финал про Катю.