Поиск по этому блогу

воскресенье, 19 августа 2012 г.

Написание программ под Android на С++


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

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



Если ваша железка не поддерживает такую ОС, то ничего страшного — мой телефон тоже безнадежно устарел, я же купил его целых 6 месяцев назад :) А более новое устройство мне заполучить для тестов не удалось, поэтому я буду запускать примеры на эмуляторе, который входит в состав SDK. Если компьютер, на котором ведется разработка, достаточно быстрый, то неудобств немного. На реальной железке эти примеры также должны работать.
Далее, создаем проект так, как это было описано в статье по установке Eclipese + Android 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:



В следующей части посмотрим как в рамках этого примера можно использовать OpenGL, чтобы увидеть что-то кроме черного экрана.

Скажем спасибо: ему