Образец нативной активности находится в корне образцов NDK , в папке native-activity
. Это очень простой пример чисто собственного приложения без исходного кода Java. При отсутствии исходного кода Java компилятор Java все равно создает исполняемую заглушку для запуска виртуальной машины. Заглушка служит оболочкой для реальной программы, которая находится в файле .so
.
Само приложение просто отображает цвет на весь экран, а затем частично меняет цвет в ответ на обнаруженное движение.
AndroidManifest.xml
Приложение только с собственным кодом не должно указывать уровень Android API ниже 9, который представляет класс платформы NativeActivity
.
<uses-sdk android:minSdkVersion="9" />
В следующей строке android:hasCode
объявляется как false
, поскольку это приложение имеет только собственный код, а не Java.
<application android:label="@string/app_name" android:hasCode="false">
Следующая строка объявляет класс NativeActivity
.
<activity android:name="android.app.NativeActivity"
Наконец, в манифесте в качестве имени создаваемой общей библиотеки указывается android:value
, за вычетом исходной lib
и расширения .so
. Это значение должно совпадать с именем LOCAL_MODULE
в Android.mk
.
<meta-data android:name="android.app.lib_name" android:value="native-activity" />
Android.mk
Этот файл начинается с указания имени создаваемой общей библиотеки.
LOCAL_MODULE := native-activity
Затем он объявляет имя файла собственного исходного кода.
LOCAL_SRC_FILES := main.c
Далее перечислены внешние библиотеки, которые система сборки будет использовать при сборке двоичного файла. Параметр -l
(ссылка против) предшествует каждому имени библиотеки.
-
log
- это библиотека журнала. -
android
включает стандартные API-интерфейсы поддержки Android для NDK. Дополнительные сведения об API, которые поддерживают Android и NDK, см. в разделе Собственные API Android NDK . -
EGL
соответствует части графического API, зависящей от платформы. -
GLESv1_CM
соответствует OpenGL ES, версии OpenGL для Android. Эта библиотека зависит от EGL.
Для каждой библиотеки:
- Фактическое имя файла начинается с
lib
и заканчивается расширением.so
. Например, фактическое имя файла библиотекиlog
—liblog.so
. - Библиотека находится в следующем корневом каталоге NDK:
<ndk>/platforms/android-<sdk_version>/arch-<abi>/usr/lib/
.
LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv1_CM
В следующей строке указывается имя статической библиотеки android_native_app_glue
, которую приложение использует для управления событиями жизненного цикла NativeActivity
и сенсорного ввода.
LOCAL_STATIC_LIBRARIES := android_native_app_glue
Последняя строка указывает системе сборки собрать эту статическую библиотеку. Скрипт ndk-build
помещает собранную библиотеку ( libandroid_native_app_glue.a
) в каталог obj
, созданный в процессе сборки. Дополнительные сведения о библиотеке android_native_app_glue
см. в ее заголовке android_native_app_glue.h
и соответствующем исходном файле .c
.
$(call import-module,android/native_app_glue)
Дополнительные сведения о файле Android.mk
см. в разделе Android.mk .
Main.c
Этот файл по сути содержит всю программу.
Следующие включения соответствуют библиотекам, как общим, так и статическим, перечисленным в Android.mk
.
#include <EGL/egl.h> #include <GLES/gl.h> #include <android/sensor.h> #include <android/log.h> #include <android_native_app_glue>
Библиотека android_native_app_glue
вызывает следующую функцию, передавая ей предопределенную структуру состояния. Он также служит оболочкой, упрощающей обработку обратных вызовов NativeActivity
.
void android_main(struct android_app* state) {
Далее программа обрабатывает события, поставленные в очередь библиотекой клея. Обработчик событий следует структуре состояния.
struct engine engine; // Suppress link-time optimization that removes unreferenced code // to make sure glue isn't stripped. app_dummy(); memset(&engine, 0, sizeof(engine)); state->userData = &engine; state->onAppCmd = engine_handle_cmd; state->onInputEvent = engine_handle_input; engine.app = state;
Приложение готовится начать мониторинг датчиков, используя API-интерфейсы в sensor.h
.
engine.sensorManager = ASensorManager_getInstance(); engine.accelerometerSensor = ASensorManager_getDefaultSensor(engine.sensorManager, ASENSOR_TYPE_ACCELEROMETER); engine.sensorEventQueue = ASensorManager_createEventQueue(engine.sensorManager, state->looper, LOOPER_ID_USER, NULL, NULL);
Далее начинается цикл, в котором приложение опрашивает систему на предмет сообщений (событий датчика). Он отправляет сообщения в android_native_app_glue
, который проверяет, соответствуют ли они любым событиям onAppCmd
определенным в android_main
. При возникновении совпадения сообщение отправляется обработчику для выполнения.
while (1) { // Read all pending events. int ident; int events; struct android_poll_source* source; // If not animating, we will block forever waiting for events. // If animating, we loop until all events are read, then continue // to draw the next frame of animation. while ((ident=ALooper_pollAll(engine.animating ? 0 : -1, NULL, &events, (void**)&source)) >= 0) { // Process this event. if (source != NULL) { source->process(state, source); } // If a sensor has data, process it now. if (ident == LOOPER_ID_USER) { if (engine.accelerometerSensor != NULL) { ASensorEvent event; while (ASensorEventQueue_getEvents(engine.sensorEventQueue, &event, 1) > 0) { LOGI("accelerometer: x=%f y=%f z=%f", event.acceleration.x, event.acceleration.y, event.acceleration.z); } } } // Check if we are exiting. if (state->destroyRequested != 0) { engine_term_display(&engine); return; } }
Как только очередь пуст, и программа выходит из цикла опроса, программа вызывает OpenGL, чтобы нарисовать экран.
if (engine.animating) { // Done with events; draw next animation frame. engine.state.angle += .01f; if (engine.state.angle > 1) { engine.state.angle = 0; } // Drawing is throttled to the screen update rate, so there // is no need to do timing here. engine_draw_frame(&engine); } }