Сборка iconv под Android NDK

Недавно встала передо мной в полный рост проблема перевода различных кодировок на платформе Android.

Казалось бы, любой java-разработчик тут должен усмехнуться и молча ткнуть пальцем в обычнейший String, — мол, всё уже украдено до нас, знай только Charset передай, или просто имя кодировки. И здесь бы я с ним согласился, набил нужный код и закрыл тему, но только вот задачу эту потребовалось решить в рамках NDK, на C++. И хотя форумы пестрят пикантными советами использовать всё те же вызовы Java через JNIEnv, очевидно, что метод сей сильно паршив по производительности, надёжности, да и просто по красоте, потому годится далеко не всегда и не везде.

В голову сразу же приходят (ну или: гуглом сразу же находятся) два очевидных решения: ICU и libiconv.

Муки выбора

(здесь будет вступительное слово, жаждущие материала могут промотать ниже, до конкретики)

ICU, казалось бы, хорош всем: и умеет очень многое, включая различное сравнение строк, и по лицензии категорически свободен, и даже в системе Android уже присутствует (именно поверх него тонкой прослоечкой написаны строки Java), да только на этом бочка мёда заканчивается, и начинаются ложечки менее приятной субстанции. Для начала — использовать системную libicuuc.so категорически не получится, поскольку она собрана в versioned-режиме. Это такое дьявольское изобретение IBM — добавлять суффикс номера версии ко всем функциям в таблице экспорта, что делает versioned-сборку категорически несовместимой ни вперёд, ни назад. А поскольку единства версии ICU на аппаратах нет, и варьируется она как минимум от 4.2 до 4.8, то и угадать нужную версию не представляется ни возможным, ни вообще разумным.

Дамп экспорта сборки libicuuc.so от Galaxy Note
Дамп экспорта versioned-сборки libicuuc.so. Как мы видим, функции оканчиваются на _46, что означает строго версию 4.6 данной библиотеки.

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

Но это ещё не всё. Сборка своего собственного ICU, даже из platform/externals, — весьма сложный квест, заставляющий для получения результата не один день просидеть над доками, гуглом, исходниками, да и просто основательно применить метод научного тыка — особенно при сборке с «родным» NDK, а не модификациями.  Самые хитрые, после краткого размышления, сольют с телефона готовый файл libicuuc.so и попробуют подсунуть ему соответствующий набор *.h-заголовков, но и это (уж просто поверьте!) будет непросто. Ключики в makefile придётся подбирать даже тщательнее, чем при полной сборке.

Наконец, ICU попросту увесистее libiconv, что для мобильного приложения немаловажно, а старательное вырезание ненужного способно убить почти бесконечное количество времени.

А раз цель наша — конкретно перевод кодировок, то гораздо приятнее остановить свой выбор на…

GNU libiconv

Эта библиотека, наверное, знакома очень многим, ну хотя бы «шапочно» — через её обёртки в различных языках, сохраняющие обычно слово «iconv» в названии. Она является штатной как минимум для любой GNU/Linux-системы и предоставляет API-интерфейс, незамысловатый как валенок. Главное же что для нас ценно — сборка её весьма проста и занимает немного времени. Слегка смущает разве что лицензия LGPL, но от неё пока ещё никто не умирал.

GNU

Сразу оговорюсь, что поскольку я бывший линуксоид, а ныне маковод — то и работу предполагаю из нормальной юниксовой консоли, а какие там нужно внести поправки для всяких Cygwin-ов — элементарно не ведаю. Могу разве что порекомендовать в случае сильных непоняток завести виртуалку и поставить Ubuntu на неё.

Итак, полагаю что со скачиванием и распаковкой исходного кода без проблем справятся все. Сама же процедура сборки libiconv под Android в целом не особо отличается от стандартной последовательности сборки GNU-софта (./configure && make && make install), только вот для скрипта configure потребуется задать несколько опций, а вместо GNU make у нас будет уже ndk-build и маленький самописный Android.mk.

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

Здесь переменная NDK_ROOT должна указывать на каталог, в котором лежит Android NDK, API_LEVEL — задавать, соответственно, нужный нам API level. Имевшие дело с юниксами прекрасно всё знают, для остальных — сообщаю: этот файл сохраняется в тот же каталог, куда были распакованы исходники (например, под именем android-config). Потом открывается терминал, где мы входим в этот каталог (cd имя_каталога, например: cd libiconv-1.14), после чего нашему файлу присваивается атрибут исполняемого (chmod +x android-config) и запускается он сам (./android-config).

Если всё пройдёт успешно — копируем подготовленные к сборке исходники в каталог jni своего android-проекта, после чего приступаем к написанию или редактированию Android.mk:

Здесь предполагается что исходники распакованы прямо в JNI, в реальном проекте они скорее попадут для порядка в подкаталог, так что отредактируйте пути.

Что ж, теперь Build All — и вот оно, заветное:

libiconv собрался
libiconv успешно собрался

Если видите такое в выводе ndk-build, то поздравляю: libiconv собрался. Если нет… что ж, странно. На момент написания этого текста последняя версия собиралась и работала.

А в итоге?

В итоге — мы получили готовый к использованию libiconv, который можно подключить к своему проекту. Его makefile, как мы видим, невелик и очень прост, потому сложных конфликтов с другими правилами сборки не возникнет — знай только пропиши библиотеку:

И всё, можно подключать iconv.h из каталога include, кодировки шпарит на ура — глаз радуется. С такой скоростью работы можно и простить размер собранного модуля в 901 килобайт, тем более что в apk он сожмётся отлично и это место потребуется только на телефоне пользователя.

Конечно, это не панацея. Часто строки нужно не только переводить. Ещё чаще — строк не так уж и много, а за 901 килобайт душит жаба. Наконец, кто-то может всё-таки бояться LGPL. Но об этих случаях — как-нибудь в другой раз.

Сборка iconv под Android NDK
Оцените пост


Комментарии:

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

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

Капча (решите пример) *