понедельник, 16 января 2012 г.

Как написать программу на C++ для Android.
Часть 4: Без Java.

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

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

Нам нужна поддержка фич NDK, которые появились только в версии Android 2.3. Поэтому сначала нужно установить SDK с поддержкой Android 2.3:


Если ваша железка не поддерживает такую ОС, то ничего страшного — мой телефон тоже безнадежно устарел, я же купил его целых 6 месяцев назад :) А более новое устройство мне заполучить для тестов не удалось, поэтому я буду запускать примеры на эмуляторе, который входит в состав SDK. Если компьютер, на котором ведется разработка, достаточно быстрый, то неудобств немного. На реальной железке эти примеры также должны работать.

Далее, создаем проект так, как это было описано во второй части. Только Build Target у нас теперь Android 2.3 и убираем галку с пункта Create Activity, так как мы договорились обойтись в этот раз без Java:


В конце не забываем вызвать Add Native Support (тыкаем правой кнопкой в проект, далее выпадает меню, в котором выбираем Android Tools > Add Native Support). В итоге получим почти пустой проект, но в нем уже есть AndroidManifest.xml, который нужно поправить в соответствии со следующим примером:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blogspot.jia3ep.test_native"
    android:versionCode="1"
    android:versionName="1.0" >

    <!-- В документации ошибочно указано, что можно писать 8 - это не так.
         NativeActivity появилась только в Android 2.3 -->
    <uses-sdk android:minSdkVersion="9" />

    <!-- Этот .apk не содержит Java кода, поэтому ставим hasCode = false. -->        
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" 
        android:hasCode="false">

        <!-- Используем встроенную NativeActivity. -->
        <activity android:name="android.app.NativeActivity"
                android:label="@string/app_name"
                android:configChanges="orientation|keyboardHidden">
            <!-- Указываем название библиотеки, которая содержит NativeActivity -->
            <meta-data android:name="android.app.lib_name"
                    android:value="test_native" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
                        
    </application>
</manifest>

Вместо activity, которую мы создавали в предыдущих примерах, указываем встроенную в NDK NativeActivity. Её реализация находится в библиотеке android_native_app_glue, которую нужно прилинковать. Для этого меняем Android.mk, заодно добавим подгрузку библиотек, которые нам пригодятся. После этого файл будет выглядеть так:
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := test_native
### Add all source file names to be included in lib separated by a whitespace
LOCAL_SRC_FILES := test_native.cpp
LOCAL_ALLOW_UNDEFINED_SYMBOLS := true
### Load additional libs 
LOCAL_LDLIBS    := -llog -landroid -lEGL -lGLESv1_CM
### Link NativeActivity support
LOCAL_STATIC_LIBRARIES := android_native_app_glue

include $(BUILD_SHARED_LIBRARY)

$(call import-module,android/native_app_glue)

В проекте уже есть cpp файл test_native.cpp, который был создан автоматически при конвертации проекта. В него добавим функцию android_main, которая использует android_native_app_glue. Она запускается в отдельном потоке со своим циклом обработки сообщений. Все очень похоже на WndProc в Windows. Но для начала в test_native.cpp добавляем всего несколько строк:
#include <android_native_app_glue.h>

void android_main(struct android_app* state) {
    // Make sure glue isn't stripped.
    app_dummy();
}

Чтобы синтаксический анализатор Eclipse не ругался, добавляем путь к android_native_app_glue.h как это было описано во второй части. У меня получился такой путь: /home/user/Android/android-ndk-r6b/sources/android/native_app_glue .

Получаем минимальный пример, который можно скомпилировать и запустить. Делать он ничего не будет и не будет отвечать на кнопки устройства, так как цикла обработки сообщений тут пока нет:


Теперь добавим примитивный цикл обработки сообщений. Для этого нужно в цикле вызывать функцию ALooper_pollAll, которая вычитывает все сообщения из очереди. Для обработки этих сообщений определим два почти пустых метода: engine_handle_input и engine_handle_cmd. Получаем следующий код:
#include <string.h>
#include <jni.h>

#include <android_native_app_glue.h>

#include <android/log.h>

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "test_native", __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "test_native", __VA_ARGS__))

/**
 * Process the next input event.
 */
static int32_t engine_handle_input(struct android_app* app,
  AInputEvent* event) {
 return 0;
}

/**
 * Process the next main command.
 */
static void engine_handle_cmd(struct android_app* app, int32_t cmd) {
 switch (cmd) {
 case APP_CMD_SAVE_STATE:
  // The system has asked us to save our current state.
  break;
 case APP_CMD_INIT_WINDOW:
  // The window is being shown.
  break;
 case APP_CMD_TERM_WINDOW:
  // The window is being hidden or closed.
  break;
 case APP_CMD_GAINED_FOCUS:
  // Our app gains focus.
  break;
 case APP_CMD_LOST_FOCUS:
  // Our app looses focus.
  break;
 }
}

void android_main(struct android_app* state) {
 // Make sure glue isn't stripped.
 app_dummy();

 LOGW( "test_native entered main" );

 state->userData = NULL;
 state->onAppCmd = engine_handle_cmd;
 state->onInputEvent = engine_handle_input;

 // loop waiting for stuff to do.

 while (1) {
  // Read all pending events.
  int ident;
  int events;
  struct android_poll_source* source;

  // We will block forever waiting for events.
  while ((ident = ALooper_pollAll(-1, NULL, &events, (void**) &source))
    >= 0) {

   // Process this event.
   if (source != NULL) {
    source->process(state, source);
   }

   // Check if we are exiting.
   if (state->destroyRequested != 0) {
    LOGW( "test_native exited main" );
    return;
   }
  }

 }
}

При отладке можно видеть сообщения о старте и выходе, которые я добавил в android_main:


На данном этапе вы уже можете сами посмотреть в примерах NDK как можно использовать OpenGL, чтобы увидеть что-то кроме черного экрана. Например, обратите внимание на samples/native-plasma.

В следующей части читайте про работу в Mac OS.

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

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