NDK系列-如何使用C/C++编写带EGL功能的NativeActivity
1、示例地址:
https://github.com/android/ndk-samples/tree/master/native-activity
2、示例功能:
1、示例应用会使用EGL在整个屏幕上渲染一种颜色,然后根据检测到的运动,随之更改局部的颜色。
2、示例应用只需要C/C++代码编写,不包含任Java源代码,但Java 编译器仍然会创建一个可由虚拟机运行的可执行存根。该存根用作 .so 文件中实际原生程序的封装容器。
3、开发步骤
限制最低SDK版本:
<uses-sdk android:minSdkVersion="9" />
声明应用只包含原生代码,而不含 Java 代码:
<application android:hasCode="false">
声明android.app.NativeActivity类,作为Java层进入Native层的入口:
<activity android:name="android.app.NativeActivity">
</activity>
使用android:value指定NativeActivity类所使用的共享库名称:
<meta-data android:name="android.app.lib_name"
android:value="native-activity" />
名称需是Android.mk或CMakeList.txt指定的共享库的名称。
Gradle配置CMake编译方式
android {
ndkVersion '21.0.6113669'
defaultConfig {
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_static'
}
}
}
externalNativeBuild {
cmake {
version '3.10.2'
path 'src/main/cpp/CMakeLists.txt'
}
}
}
编写CMakeList.txt
使用ndk中的native_app_glue功能源码实现Java与C/C++之间的连接。
cmake_minimum_required(VERSION 3.4.1)
set(${CMAKE_C_FLAGS}, "${CMAKE_C_FLAGS}")
//添加关联的静态库
add_library(native_app_glue STATIC
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wall -Werror")
set(CMAKE_SHARED_LINKER_FLAGS
"${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
add_library(native-activity SHARED main.cpp)
//目标关联目录
target_include_directories(native-activity PRIVATE
${ANDROID_NDK}/sources/android/native_app_glue)
target_link_libraries(native-activity
android
native_app_glue
EGL
GLESv1_CM
log)
关联库native_app_glue的Android.mk文件:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= android_native_app_glue
LOCAL_SRC_FILES:= android_native_app_glue.c
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
LOCAL_EXPORT_LDLIBS := -llog -landroid
# The linker will strip this as "unused" since this is a static library, but we
# need to keep it around since it's the interface for JNI.
LOCAL_EXPORT_LDFLAGS := -u ANativeActivity_onCreate
include $(BUILD_STATIC_LIBRARY)
编写main.cpp,实现具体业务功能
这是使用android_native_app_glue的本地应用程序的主入口点。它在自己的线程中运行,有自己的事件循环来接收输入事件和做其他事情。
void android_main(struct android_app* state) {
//TODO
}
4、流程解析
4.1、NativeActivity.java
实现SurfaceHolder.Callback2,InputQueue.Callback, OnGlobalLayoutListener,用于处理surface、input和view相关的逻辑。
implements SurfaceHolder.Callback2,InputQueue.Callback, OnGlobalLayoutListener
默认使用的共享库和函数名称,若清单文件有配置则使用配置的:
String libname = "main";
String funcname = "ANativeActivity_onCreate";
关键代码,加载原生代码,获取原生句柄:
BaseDexClassLoader classLoader = (BaseDexClassLoader) getClassLoader();
String path = classLoader.findLibrary(libname);
mNativeHandle = loadNativeCode(path, funcname, Looper.myQueue(),
getAbsolutePath(getFilesDir()), getAbsolutePath(getObbDir()),
getAbsolutePath(getExternalFilesDir(null)),
Build.VERSION.SDK_INT, getAssets(), nativeSavedState,
classLoader, classLoader.getLdLibraryPath());
此方法将会调用android_native_app_glue.c的ANativeActivity_onCreate函数:
JNIEXPORT
void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState,
size_t savedStateSize) {
LOGV("Creating: %p\n", activity);
activity->callbacks->onDestroy = onDestroy;
activity->callbacks->onStart = onStart;
activity->callbacks->onResume = onResume;
activity->callbacks->onSaveInstanceState = onSaveInstanceState;
activity->callbacks->onPause = onPause;
activity->callbacks->onStop = onStop;
activity->callbacks->onConfigurationChanged = onConfigurationChanged;
activity->callbacks->onLowMemory = onLowMemory;
activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
activity->callbacks->onInputQueueCreated = onInputQueueCreated;
activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
activity->instance = android_app_create(activity, savedState, savedStateSize);
}
其中的回调,均是在NatveActivity中Activity生命周期、surface、input、view相关的原生代码调用:
private native String getDlError();
private native void unloadNativeCode(long handle);
private native void onStartNative(long handle);
private native void onResumeNative(long handle);
private native byte[] onSaveInstanceStateNative(long handle);
private native void onPauseNative(long handle);
private native void onStopNative(long handle);
private native void onConfigurationChangedNative(long handle);
private native void onLowMemoryNative(long handle);
private native void onWindowFocusChangedNative(long handle, boolean focused);
private native void onSurfaceCreatedNative(long handle, Surface surface);
private native void onSurfaceChangedNative(long handle, Surface surface,
int format, int width, int height);
private native void onSurfaceRedrawNeededNative(long handle, Surface surface);
private native void onSurfaceDestroyedNative(long handle);
private native void onInputQueueCreatedNative(long handle, long queuePtr);
private native void onInputQueueDestroyedNative(long handle, long queuePtr);
private native void onContentRectChangedNative(long handle, int x, int y, int w, int h);
4.2、android_native_app_glue.c
android_native_app_glue库是Java代码NativeActivity和原生代码main.cpp中android_main函数入口的连接中介,这也是这个库为什么取名为胶水glue的原因。
调用路径为:
1、NativeActivity的loadNativeCode函数调用android_native_app_glue的ANativeActivity_onCreate函数。
2、ANativeActivity_onCreate函数调用android_app_create函数,进而调用android_app_entry函数,最后调用main.c的android_main函数,android_main函数则由工程师编写具体的业务代码。
其他Activity生命周期、surface、input、view的函数同理类推。
4.3、main.cpp
编写业务代码,此处为监听传感器、输入事件、View树的变化等,进行EGL的绘图功能,将屏幕全屏绘制成某个颜色。
void android_main(struct android_app* state) {
struct engine engine{};
memset(&engine, 0, sizeof(engine));
//用户信息
state->userData = &engine;
//应用指令
state->onAppCmd = engine_handle_cmd;
//输入事件
state->onInputEvent = engine_handle_input;
engine.app = state;
// Prepare to monitor accelerometer
// 监听加速器
engine.sensorManager = AcquireASensorManagerInstance(state);
engine.accelerometerSensor = ASensorManager_getDefaultSensor(
engine.sensorManager,
ASENSOR_TYPE_ACCELEROMETER);
engine.sensorEventQueue = ASensorManager_createEventQueue(
engine.sensorManager,
state->looper, LOOPER_ID_USER,
nullptr, nullptr);
if (state->savedState != nullptr) {
// We are starting with a previous saved state; restore from it.
engine.state = *(struct saved_state*)state->savedState;
}
// loop waiting for stuff to do.
while (true) {
// 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, nullptr, &events,
(void**)&source)) >= 0) {
// Process this event.
if (source != nullptr) {
source->process(state, source);
}
// If a sensor has data, process it now.
//处理传感器数据
if (ident == LOOPER_ID_USER) {
if (engine.accelerometerSensor != nullptr) {
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;
}
}
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);
}
}
}
应用状态的数据引擎,包含应用信息、传感器信息以及EGL图形绘制信息:
/**
* Shared state for our app.
*/
struct engine {
struct android_app* app;
ASensorManager* sensorManager;
const ASensor* accelerometerSensor;
ASensorEventQueue* sensorEventQueue;
int animating;
//显示图形
EGLDisplay display;
//存储图形
EGLSurface surface;
//上下文
EGLContext context;
int32_t width;
int32_t height;
struct saved_state state;
};
5、EGL绘图
EGL 是渲染 API(如 OpenGL ES)和原生窗口系统之间的接口。通常来说,OpenGL 是一个操作 GPU 的 API,它通过驱动向 GPU 发送相关指令,控制图形渲染管线状态机的运行状态,但是当涉及到与本地窗口系统进行交互时,就需要这么一个中间层,且它最好是与平台无关的。因此 EGL 被设计出来,作为 OpenGL 和原生窗口系统之间的桥梁。
使用 EGL 绘图的基本步骤:
Display(EGLDisplay) 是对实际显示设备的抽象。
Surface(EGLSurface)是对用来存储图像的内存区域FrameBuffer 的抽象,包括 Color Buffer, Stencil Buffer ,Depth Buffer。
Context (EGLContext) 存储 OpenGL ES绘图的一些状态信息。
使用EGL的绘图的一般步骤:
获取 EGL Display 对象:eglGetDisplay()
初始化与 EGLDisplay 之间的连接:eglInitialize()
获取 EGLConfig 对象:eglChooseConfig()
创建 EGLContext 实例:eglCreateContext()
创建 EGLSurface 实例:eglCreateWindowSurface()
连接 EGLContext 和 EGLSurface:eglMakeCurrent()
使用 OpenGL ES API 绘制图形:gl_*()
切换 front buffer 和 back buffer 送显:eglSwapBuffer()
断开并释放与 EGLSurface 关联的 EGLContext 对象:eglRelease()
删除 EGLSurface 对象
删除 EGLContext 对象
终止与 EGLDisplay 之间的连接
为当前连接初始化OpenGL ES 和EGL:
/**
* Initialize an EGL context for the current display.
*/
static int engine_init_display(struct engine* engine) {
// initialize OpenGL ES and EGL
/*
* Here specify the attributes of the desired configuration.
* Below, we select an EGLConfig with at least 8 bits per color
* component compatible with on-screen windows
*/
const EGLint attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_NONE
};
EGLint w, h, format;
EGLint numConfigs;
EGLConfig config = nullptr;
EGLSurface surface;
EGLContext context;
//创建连接
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
//初始化连接
eglInitialize(display, nullptr, nullptr);
/* Here, the application chooses the configuration it desires.
* find the best match if possible, otherwise use the very first one
*/
eglChooseConfig(display, attribs, nullptr,0, &numConfigs);
std::unique_ptr<EGLConfig[]> supportedConfigs(new EGLConfig[numConfigs]);
assert(supportedConfigs);
eglChooseConfig(display, attribs, supportedConfigs.get(), numConfigs, &numConfigs);
assert(numConfigs);
auto i = 0;
for (; i < numConfigs; i++) {
auto& cfg = supportedConfigs[i];
EGLint r, g, b, d;
if (eglGetConfigAttrib(display, cfg, EGL_RED_SIZE, &r) &&
eglGetConfigAttrib(display, cfg, EGL_GREEN_SIZE, &g) &&
eglGetConfigAttrib(display, cfg, EGL_BLUE_SIZE, &b) &&
eglGetConfigAttrib(display, cfg, EGL_DEPTH_SIZE, &d) &&
r == 8 && g == 8 && b == 8 && d == 0 ) {
config = supportedConfigs[i];
break;
}
}
if (i == numConfigs) {
config = supportedConfigs[0];
}
if (config == nullptr) {
LOGW("Unable to initialize EGLConfig");
return -1;
}
/* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
* guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
* As soon as we picked a EGLConfig, we can safely reconfigure the
* ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */
//获取配置
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
//创建渲染区域
surface = eglCreateWindowSurface(display, config, engine->app->window, nullptr);
//创建渲染上下文
context = eglCreateContext(display, config, nullptr, nullptr);
//关联上下文和渲染区域
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
LOGW("Unable to eglMakeCurrent");
return -1;
}
eglQuerySurface(display, surface, EGL_WIDTH, &w);
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
engine->display = display;
engine->context = context;
engine->surface = surface;
engine->width = w;
engine->height = h;
engine->state.angle = 0;
// Check openGL on the system
auto opengl_info = {GL_VENDOR, GL_RENDERER, GL_VERSION, GL_EXTENSIONS};
for (auto name : opengl_info) {
auto info = glGetString(name);
LOGI("OpenGL Info: %s", info);
}
// Initialize GL state.
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
glEnable(GL_CULL_FACE);
glShadeModel(GL_SMOOTH);
glDisable(GL_DEPTH_TEST);
return 0;
}
使用EGL绘制当前帧:
/**
* Just the current frame in the display.
*/
static void engine_draw_frame(struct engine* engine) {
if (engine->display == nullptr) {
// No display.
return;
}
// 往屏幕填充颜色
// Just fill the screen with a color.
glClearColor(((float)engine->state.x)/engine->width, engine->state.angle,
((float)engine->state.y)/engine->height, 1);
glClear(GL_COLOR_BUFFER_BIT);
//交换缓冲区
eglSwapBuffers(engine->display, engine->surface);
}
技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。
推荐阅读:
觉得不错,点个在看呗~