четверг, 20 октября 2011 г.

Как написать программу на C++ для Android.
Часть 2: Используем простейшую С++ функцию.

Часть 1 | Часть 2 | Часть 3 | Часть 4 | Часть 5 (Mac OS)

В предыдущей части я рассказал как настроить окружение для разработки программ на C++ под Android. В этой части попробуем создать простейший проект с примитивным C++ кодом.


Несмотря на то, что C++ в Android проектах можно использовать, основным языком все-таки остается Java. А С++ можно использовать для оптимизации сложных участков кода или при портировании существующих библиотек, которые выбросить жалко, а переписывать на Java слишком затратно. Итак, создаем пустой проект через меню File > New > Android Project:


В появившемся окне заполняем поля Project Name и Package Name. Если в поле Project Name можно написать почти любое название, то в поле Package Name текст должен быть вида com.adobe.reader. Это соглашение исторически используется для идентификации Java пакетов и является уникальным идентификатором вашей программы на Android Market и на устройстве. Чтобы избежать конфликтов имен, следует выбирать действительно уникальное название. Интересно, что обратный порядок в названии пакета объясняется тем как проекты расположены на носителе. Если заменить каждую точку на слэш, то получится, что все программы от одного издателя сгруппированы в одном каталоге. Я в этом окне использовал название com.blogspot.jia3ep.test.

Обратите внимание, что по умолчанию стоит галка напротив пункта Create Activity. На самом деле, так называемые Activities являются основным элементом Android проекта. Для простоты можно считать, что каждая Activity — это модальный диалог, который раскрывается на весь экран. Какие-то действия могут открыть следующий такой диалог, а кнопка Back на устройстве возвращает к предыдущему диалогу. Наглядно это можно увидеть на следующей картинке:


Чтобы завершить создание проекта жмем кнопку Finish (можно нажать Next, но там предлагается создать Test Project, который выходит за рамки этой статьи).

Можно попробовать запустить созданный проект, чтобы убедится, что все идет по плану. Это можно сделать либо на эмуляторе, предварительно создав виртуальное устройство через меню Window > AVD Manager. Либо на реальном устройстве. Надо отметить, что первый запуск эмулятора происходит мучительно долго. Спасает то, что при последующих запусках нашей программы эмулятор уже запущен и дело идет гораздо веселей.

Если вы запускаете на реальном устройстве, то следует убедиться, что разрешена отладка по USB:


Если программа запустилась, то все было сделано правильно:


Теперь попробуем добавить немного C++ в наш проект. Если вы установили Sequoyah Android Native Code Support, то в меню появился пункт Add Native Support (тыкаем правой кнопкой в проект, далее выпадает меню, в котором выбираем Android Tools > Add Native Support):


В результате появляется диалог, в котором ничего менять не требуется, если ранее все было настроено корректно:


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

К нему мы ещё вернемся, а сначала нужно добавить прототип Java функции, который будет реализован на C++. Для этого открываем файл TestActivity.java. Сначала его содержание следующее:
package com.blogspot.jia3ep.test;

import android.app.Activity;
import android.os.Bundle;

public class TestActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
      
    }
}

Для того, чтобы C++ библиотека была доступна, первым делом нужно её загрузить. В диалоге Add Native Support название библиотеки было задано как test. Таким образом, файл библиотеки будет называться libtest.so, но в Java коде следует писать название test. Итак, добавляем в статический инициализатор вызов loadLibrary:
static {
    System.loadLibrary("test");
}

Смысл static в Java примерно такой же как в C++. В данном случае я хочу, чтобы библиотека загружалась только один раз на все экземпляры класса.

Теперь определим функцию, реализация которой будет на C++ (ключевое слово native как раз говорит, что реализация будет на C++):
private native String get_text_from_cpp();

И вызовем эту функцию в функции OnCreate добавив вызов setTitle, который, как можно догадаться, заменит заголовок окна:
setTitle( get_text_from_cpp() );

В итоге получим следующий файл TestActivity.java:
package com.blogspot.jia3ep.test;

import android.app.Activity;
import android.os.Bundle;

public class TestActivity extends Activity {
    static {
       System.loadLibrary("test");
    } 
 
    private native String get_text_from_cpp();
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        setTitle( get_text_from_cpp() );
    }
}

Теперь, если программу запустить, то она упадет, т.к. нет реализации функции get_text_from_cpp. Чтобы добавить реализацию, нужно сначала написать объявление функции на C++. Можно это сделать вручную или воспользоваться специальными тулзами. Для начала попробуем автоматизированный путь — открываем консоль и переходим в каталог проекта, после этого запускаем javah следующим образом:
cd ~/workspace/test
javah -classpath .:bin:/home/user/Android/android-sdk-linux_x86/platforms/android-8/android.jar -jni com.blogspot.jia3ep.test.TestActivity

Из команды видно, что нужно указать полное название класса TestActivity. В результате получаем файл com_blogspot_jia3ep_test_TestActivity.h. В нем интерес представляют только следующие строки (собственно, прототип функции):
/*
 * Class:     com_blogspot_jia3ep_test_TestActivity
 * Method:    get_text_from_cpp
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_blogspot_jia3ep_test_TestActivity_get_1text_1from_1cpp
  (JNIEnv *, jobject);

Инклудим этот файл в test.cpp и добавляем реализацию для функции get_text_from_cpp:
#include <string.h>
#include <jni.h>
#include "../com_blogspot_jia3ep_test_TestActivity.h"

JNIEXPORT jstring JNICALL Java_com_blogspot_jia3ep_test_TestActivity_get_1text_1from_1cpp
  (JNIEnv * env, jobject)
{
    return env->NewStringUTF( "hello from C++" );
}

На этом наша программа готова. Запускаем и убеждаемся, что все работает:


Возможно, в Eclipse статический анализатор С++ кода будет ругаться и не даст запустить программу. В этом случае нужно прописать путь к инклудам в свойствах проекта (у меня прописан /home/user/Android/android-ndk-r6b/platforms/android-8/arch-arm/usr/include), закрыть все файлы и пересобрать проект:


В следующей статье я надеюсь показать как работать с C++ классами и как использовать Standard Template Library в Android проектах. И, забегая вперед, могу сообщить, что под Android возможно создать программу используя только C++.

Продолжение следует...

Книги по теме:

Комментировать в ВКонтакте