Как подружить SQLite андроида с языком, отличным от английского


    Здравствуй, дорогой читатель!
    В этой статье я хочу затронуть проблему хранения кириллических данных в SQLite, попробовать разобраться с Android NDK, и вообще зажить прекрасной жизнью! Однако, с этого момента, считаю важными первые два пункта. О них и поговорим.

    Известная проблема SQLite состоит в том, что он не любит никаких символов, кроме латинских, поэтому выполняется такое [1]:
    SELECT "ы" LIKE "Ы";
    0
    SELECT "s" LIKE "S";
    1


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


    Android NDK


    Чтобы расширить твой кругозор, дорогой читатель, а так же создать годные костыли, я предлагаю использовать Android NDK [2] (скачайте и распакуйте в доступную папку)

    Немного скучного

    Android NDK — это иструмент для компилирования C/C++ кода в нативный код платформы Android. Кроме того, предоставляются многие библиотеки Android, так что определенные интенсивные участки программы можно написать в C/C++, а затем использовать в Java.
    NDK поддерживается начиная с Android 1.5, и в данный момент поддерживаются следующие архитектуры процессоров ARMv5TE (включая инструкции Thumb-1), ARMv7-A (включая инструкции Thumb-2 и VFPv3-D16, с опциональной поддержкой NEON/VFPv3-D32)
    Будущие релизы обещают поддержку x86

    Короче, воспользуемся же этим инструментом!

    Итак...


    Что мы будем делать? Будем объединять исходный код sqlite и icu.
    Дабы не нагружать процесс поиском нужных библиотек и исходников, предлагается собранный комплект для компиляции, здесь [3]. Прошу прощения, если случайно включено что-то лишнее.
    Если вас не интересует процесс компиляции, вы можете скачать скомпилированную библиотеку здесь [7], и пропустить несколько пунктов

    Скачав файл, распаковываем, и размещаем это драгоценное в ваш проект
    project/jni
    Где project здесь и далее — полный пусть до проекта

    Прежде, чем перейти к шагу компиляции, хочу обратить ваше внимание на структуру некоторых файлов.

    common/Android.mk

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

    common/android_sqlite...

    Эти файлы осуществляют андроидоподобный доступ к базе, а так же интерфейс для взаимодействия с базой из Java.
    Обратите внимание на сигнатуры функций
    void Java_biz_sneg_sqlite_SQLiteDatabase_closedb()

    Чтобы успешно использовать нативные функции, нужно описать их в формате Java_имя_пакета_класс_имяФункции
    Так же я включил функцию определения географического расстояния GEODISTANCE(x1, y1, x2, y2), потому что, лично мне, ее не хватало.
    Теперь легко подключить свои нужные функции, будь то sin, cos или ваши экзотические

    Компиляция и подключение библиотеки


    Сначала опишу шаги для Windows, потому как решение в лоб не работает.
    Делай раз! Скачиваем и устанавливаем Cygwin [6], при установке выбираем пакеты Devel для установки. И… временно удаляемся на сон или другие приятные дела, потому что это адски долго. Хотя, возможно, я просто выбрал неправильный сервер.
    Делай два! Запускаем консоль Cygwin (просто консоль для других ОС) и переходив в директорию с проекта:
    Windows:
    $ cd /cygdrive/project/jni
    Non-windows:
    $ cd project/jni

    Делай три! Запускаем NDK
    Windows:
    $ /cygdrive/ndk-path/ndk-build
    Non-windows:
    $ ndk-path/ndk-build


    Где project — путь к вашему проекту, ndk-path — путь, куда вы распаковали NDK
    Должен запуститься процесс компиляции:
    ...
    Compile++ thumb : android_sqlite <= tmutfmt.cpp
    Compile++ thumb : android_sqlite <= colldata.cpp
    Compile++ thumb : android_sqlite <= bmsearch.cpp
    Compile++ thumb : android_sqlite <= bms.cpp
    Compile++ thumb : android_sqlite <= currpinf.cpp
    Compile++ thumb : android_sqlite <= uspoof.cpp
    Compile++ thumb : android_sqlite <= uspoof_impl.cpp
    ...

    Если не запустился — сочувствую О_о

    На выходе мы должны получить скомпилированную библиотеку, по пути project/libs/armeabi/libandroid_sqlite.so

    Теперь нам нужно подключить библиотеку к проекту.
    Во-первых, в главном Activity мы должны описать ее подключение
    public class MainActivity extends TabActivity {
    	static {
    	    System.loadLibrary("android_sqlite");
    	}
    ...
    }


    Далее, нам нужен код, который бы работал с нативной библиотекой, и его можно скачать здесь [4]

    Как работать?


    Как, как, сели и поехали!
    Открываем базу
    try 
    {
    	dataBase_ = new biz.sneg.sqlite.SQLiteDatabase(DB_NAME);		
    }
    catch (Exception e) {
    	Log.wtf("Database", e);
    }
    


    Достаем данные как-нибудь так, или как вам нравится
    try {
    	biz.sneg.sqlite.SQLiteCursor cursor = 
    		dataBase_.query("SELECT * FROM tablename WHERE rus_text_field LIKE ?", new Object[]{"ура%"});
    
    	List<Map<String, String>> results = new ArrayList<Map<String,String>>();
    
    	while (cursor.next())
    	{
    		final int columnCount = cursor.count();
    		HashMap<String, String> currentMap;
    		currentMap = new HashMap<String, String>();
    		for (int i = 0; i < columnCount; i++) 
    		{
    			currentMap.put(cursor.columnName(i), cursor.stringValue(i));
    		}
    		results.add(currentMap);
    	}
    	cursor.dispose();
    }
    catch (Exception e) {
    	Log.wtf("Database", e);
    }
    


    В общем, желаю удачи!

    Автор идеи и сборки Николай Кудашов, инвайт можно прислать сюда drklo.2kb<известный_символ>gmail.com

    Ссылки:
    1. SQLite и полноценный UNICODE
    2. Android NDK
    3. Исходники для библиотеки
    4. Исходники для java
    5. SQLite
    6. Cygwin
    7. Скомпилированная библиотека
    Share post

    Comments 37

      –11
      мне кажется уже давно в БД хорошим тоном стало не называть поля по русски.
        +12
        и не держать там русский текст?
          +1
          а ну если оно и русский текст не ищет, тогда да кошмар однозначный!
        • UFO just landed and posted this here
            +20
            1С, не входи, 1С, уходи.
            • UFO just landed and posted this here
                +4
                Нет, не лучше.
                • UFO just landed and posted this here
                    +1
                    Населённый пункт можно City. Менее общее слово, но смысл отражает, если не нужно отличать деревни от посёлков городского типа.
                    ОКПО — RussianCompanyUberID. А если серьёзно, такие укоренившиеся аббревиатуры можно бы и транслитом оставить, на мой взгляд.
                    • UFO just landed and posted this here
                        0
                        Признайтесь, Вам нравится несколько раз переключать раскладку при написании одной строки кода? Ну, я сейчас говорю не про 1С, разумеется.
                        • UFO just landed and posted this here
                            +1
                            Да нет… Эти люди просто никогда с чужим кодом не работали. Совсем чужим. Например, содержащим китайские или индусские комментарии, имена и т.п. Очень приятно читать такой национализированный код.
                  0
                  Понимаю вашу неприязнь. Но почему он тогда так востребован?
                    +3
                    Ну это же моя неприязнь, а не всего человечества. Раз пользуются, значит, работает. Глупо отказываться от хорошего (наверное) продукта только из эстетических соображений совершенно посторонних людей.
              +2
              Известная проблема SQLite состоит в том, что он не любит никаких символов, кроме латинских, поэтому выполняется такое [1]:
              SELECT «ы» LIKE «Ы»;
              0
              SELECT «s» LIKE «S»;
              1
              Дело в LIKE, а не в SQLite вообще :-)
              FTS3 вас спас бы, если дело только в этих LIKE'ах.

              P.S. Русский текст оно ищет, но ен понимает буквы в разном регистре. Т.е «Ы» != «ы», но «Ы» == «Ы».
                +3
                UPPER, LOWER не реагируют на любые символы, кроме латиницы.
                Решение выше позволяет исправить этот недостаток в андроиде, и еще и дописывать собственные функции для бд
                  0
                  дописывать собственные функции для бдВот это уже аргумент, ибо проблема с регистром обходится и без NDK.
                    +1
                    Нужно редактирование комментариев, хотя бы в пределах пяти минут :-)

                    P.S. Блог Разработка под Андроид подходит лучше для этой заметки.
                      0
                      Действительно, перенес :)
                +1
                «SELECT * FROM tablename WHERE rus_text_field LIKE ура%»
                простите, а это работает?! без кавычек вокруг «ура%» ?!!!
                  +2
                  Вы знаете, действительно не работало, исправил на корректный вариант
                  0
                  Странно, откуда такая проблема. Под WinCE 4.2+ sqlite отлично работает с русским текстом, в том числе и с like 'Перемещение%'. Версия 3.7.4. Правда под WinCE используется только Unicode двухбайтовый. Мне удается даже на сервере генерировать sqlite-базу в UTF-8 и в UTF-16 отлично её открывать и работать с ней и русским текстом в ней (даже не имея на девайсе русской локали), передав её по сети.

                  Возможно работа с sqlite только в UTF-16 решает проблему?
                    0
                    Ничего не понял зачем JNI для работы c SQLite русским языком. Мое приложение прекрасно работает с русскими данными и без костылей :)
                    Есть только одна загвоздка оно не умеет работать Lowercase/Uppercase надо поставить что-то дополнительное, но я просто перевел все строки в lowercase и забил на эту проблему, так как было не важно :)
                      0
                      Да, с русскими данными работает прекрасно, но не тогда, когда критичны эти самые верхне/нижние регистры :)
                      Допустим, я не могу просто перевести в ловеркейс строку «ОФИГИТЕЛЬНАЯ штука Баян!», а искать по таким полям нужно
                        0
                        Просто из самого топика было не сразу понятно, в чем собственно состоит проблема. Неплохо бы было изучить альтернативные решения или хотя бы их упомянуть. Такие как хранить 2 колонки одна с оригинальным описанием, вторая с индексом или lowercase.
                        Проблема любого использования NDK, что это решение не универсально для всех Android, вполне возможно придется перекомпилировать для некоторых нестандартных ARM, именно по этой причине пришлось отказаться от этого.
                        P.S.: постановка проблема, существующие решения и их преимущества/недостатки, ваше решение и преимущество/недостатки, все по-взрослому, шучу :)
                          0
                          Как я понял из документации, нативный код работает с 1.5, и думаю, сейчас мало кто ориентируется на 1.5 и ниже.
                          Если я не прав, прошу камнями не кидаться :)
                          А по поводу хранения двух полей — ну это уж по задаче. Возможно, моя статья и не про то совсем, а просто про нативный код :)
                            +1
                            Как я так понимаю, vics001 говорил об архитектуре процессора, а не о версии андройда.
                              0
                              На всякий случай дописал информацию о поддерживаемых архитектурах, но, боюсь, это мало кому интересно :)
                        0
                        Проблема проявляется, например, в том, что для поиска, например, города для того же виджета погоды, надо начитать вводить его с большой буквы, что вроде как глупое требования в наше время заботы о пользователе. Аналогично с вводом имени-фамилии при вводе адресата в смс для автоподстановки, если это не реализовано специально смс-менеджером.
                        0
                        Странно, но я сталкивался только с проблемой MATCH (учет регистра), остальное все отлично работает с кирилицой в sqlite на android.
                          0
                          вопрос не по теме: а как научить стандартную медиа-библиотеку читать тэги из музыкальных файлов в кодировке 1251, а не utf-8? а то из-за этого невозможно пользоваться большинством плееров. только пара плееров идут в обход библиотеки и читают тэги напрямую и при этом позволяют указать кодировку.
                            0
                            Никак, IDv*, начиная с какой-то версии, по стандарту(!) требует UTF-8, хранение в cp1251 – это проблемы плееров, которые так делают. А кто, кстати, так делает-то?
                              0
                              откуда я знаю? большинство mp3 в диких интернетах в 1251 и заниматься перекодированием мне совершенно влом.
                                +1
                                А до какой-то версии, требовало по стандарту ISO-8859-1, поэтому в диких интернетах столько mp3шек с cp1251.
                                  0
                                  Я перекодирую на стороне компьютера, просто вырезая IDv1, в IDv2 все в UTF-8
                                0
                                Проблема не решилась. Пришлось через SQLiteDatabase.CustomFunction пробрасывать

                                private final SQLiteDatabase.CustomFunction mLowerFnc =
                                                         new SQLiteDatabase.CustomFunction() {
                                                 @Override
                                                 public String callback(String[] args) {
                                                     String text = args[0];
                                                     text = text.toLowerCase();
                                                     Log.d(LOG, "LOWER_FNC : " + text);
                                                     return text;
                                                }
                                        };
                                
                                


                                Ну и добавить функцию

                                database.addCustomFunction("LOWER_FNC", 1, mLowerFnc);
                                

                                Only users with full accounts can post comments. Log in, please.