NativeActivity的胶水层android_native_app_glue详解和使用

Android 专栏收录该内容
29 篇文章 0 订阅

    使用NativeActivity可以完全不使用java代码,全部使用native code来开发android程序。NativeActivity 为我们定制了native代码的各种接口回调,在ndk的samples里面,提供了一个例子如何使用NativeActivity。我们会发现,demo中使用了一个胶水层android_native_app_glue.h封装了native层面的ANativeActivity的事件回调。


    在native层面ANativeActivity对应的就是,java层面的NativeActivity。通过NativeActivity的java会代码会看到,ANativeActivity的回调函数都是在NativeActivity中被调用的。android_native_app_glue.c 主要做了以下几件事情。

  • 实现ANativeActivity启动函数,挂载ANativeActivity的事件回调函数。包括了事件处理,窗口生命周期等等。
  • 启动一个独立的线程,给使用者传递事件,和实现绘制。
  • 使用ALooper重新定义和实现了事件类型,让使用者实现回调处理。
    在整个android_native_app_glue代码的核心,就是如何使用ALooper串联起主线程事件,输入输出事件的。主线程事件,就是指在NativeActivity的回调事件。输入输出事件,就是input事件。


    简单介绍一个下ALooper,通过阅读java和c的定义代码。我的理解是,Looper可以把其它线程的事件放到队列里面,以后再一个线程里面独立处理。比如,OpenGL的调用就需要和Context在一个线程里面,就是典型的使用Looper的场景。我们从主线程拿到事件,然后在openGL的绘制线程里,处理looper收集的事件,这样事件处理和绘制就会在同一个线程里面了。


    从android/input.h和android/sensor.h我们可以了解到,要使用系统的输入输出和重力感应事件,我们需要传入Looper对象指针。在android/looper.h中可以了解到Looper的使用方式,每个线程可以附加一个Looper。我们在线程代码里看到下面的代码。
    
    ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
    ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL,
            &android_app->cmdPollSource);

android_native_app_glue 使用Looper的方式是非回调模式,回调函数传入NULL。就是Looper负责收集和释放事件,事件的处理需要自己调用。当然也有回调模式。ALooper_addFd 就是向Looper注册事件处理的方法。fd从何而来呢,如下:
    int msgpipe[2];
    if (pipe(msgpipe)) {
        LOGE("could not create pipe: %s", strerror(errno));
        return NULL;
    }
    android_app->msgread = msgpipe[0];
    android_app->msgwrite = msgpipe[1];

就是利用管道的读写句柄来进行数据通信的。当ANativeActivity的事件在主线程触发的时候,向msgwrite写入了一个自定义的数据,然后调用ALooper_pollAll来处理Looper中事件,这时候利用msgwrite去读取写入的数据。由于是非回调Looper处理,所以android_native_app_glue使用一个结构在存放通用的事件处理,作为最后一个自定义参数传递给ALooper_addFd。如下:
/**
 * Data associated with an ALooper fd that will be returned as the "outData"
 * when that source has data ready.
 */
struct android_poll_source {
    // The identifier of this source.  May be LOOPER_ID_MAIN or
    // LOOPER_ID_INPUT.
    int32_t id;

    // The android_app this ident is associated with.
    struct android_app* app;

    // Function to call to perform the standard processing of data from
    // this source.
    void (*process)(struct android_app* app, struct android_poll_source* source);
};

我们继续查看process的实现和赋值的代码如下:
    android_app->cmdPollSource.id = LOOPER_ID_MAIN;
    android_app->cmdPollSource.app = android_app;
    android_app->cmdPollSource.process = process_cmd;
    android_app->inputPollSource.id = LOOPER_ID_INPUT;
    android_app->inputPollSource.app = android_app;
    android_app->inputPollSource.process = process_input;

static void process_input(struct android_app* app, struct android_poll_source* source) {
    AInputEvent* event = NULL;
    while (AInputQueue_getEvent(app->inputQueue, &event) >= 0) {
        LOGV("New input event: type=%d\n", AInputEvent_getType(event));
        if (AInputQueue_preDispatchEvent(app->inputQueue, event)) {
            continue;
        }
        int32_t handled = 0;
        if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event);
        AInputQueue_finishEvent(app->inputQueue, event, handled);
    }
}

static void process_cmd(struct android_app* app, struct android_poll_source* source) {
    int8_t cmd = android_app_read_cmd(app);
    android_app_pre_exec_cmd(app, cmd);
    if (app->onAppCmd != NULL) app->onAppCmd(app, cmd);
    android_app_post_exec_cmd(app, cmd);
}


    process 有两个实现,一个是处理input的事件,一个是处理主线程事件的。然后,process_input使用input.h的函数去处理,然后利用onInputEvent分发输入输出事件。process_cmd调用自己实现的pre和post以及onAppCmd去对主线程事件进行处理和分发。


    所以,使用android_native_app_glue的时候,只要我们去实现onAppCmd和onInputEvent,就可以挂载主线程的事件和输入输出事件处理。然后在循环绘制线程中不断的调用ALooper_pollAll去检测Looper是否有事件需要处理,有的话就调用process方法去处理分发我们自己的挂载。


    这里有些值得思考的地方。

  • 如果我们自己实现java版本的OpenGL绘制,我们会使用GLSurfaceView去处理openGL上下文初始化以及回调。native的方式我们查看NativeActivity的java代码发现并没有使用GLSurfaceView,而是使用了简单的View。这意味着,我们需要在native端自己去对EGL初始化以及销毁。
  • native的实现方式,绘制线程是用native 代码循环控制的。并不是java的循环利用jni回调给native code。我觉得这里就是提高绘制效率的一个地方。
  • android_native_app_glue 使用了大量互斥条件锁进行线程的同步工作。但是我发现这些不是必要的,甚至主线程的事件都可以简单的处理,不必使用Looper来控制。
    下一篇文章会介绍一下,我重写替换掉了android_native_app_glue这层代码。去除了线程同步,去除了自定义事件回调,没有使用Looper去控制主线程事件,简化了大量的代码和流程,直接使用ANativeActivity的生命周期回调去控制。

    最后,给出使用android_native_app_glue代码的框架结构。代码给出了一些思路可以作为参考。
  • 处理input事件
  • 处理senor事件
  • 绘制循环处理Looper事件
  • 挂载onAppCmd和onIputEvent回调
  • 使用c语言的接口计算每帧消耗的时间
  • OpenGL初始化和使用流程

/*
 * AndroidGlue.c
 *
 * Author: scott.cgi
 */

#include <android/asset_manager.h>
#include <android/input.h>
#include <android/looper.h>
#include <android/native_window.h>
#include <android/sensor.h>
#include <android_native_app_glue.h>
#include <EGL/egl.h>
#include <EGL/eglplatform.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>

//-------------------------------------------------------------------------

typedef struct android_app         AndroidApp;
typedef struct android_poll_source AndroidPollSource;


//static const long EVENT_RATE =  (1000l / 60) * 1000l;

static struct
{
    EGLDisplay         display;
    EGLSurface         surface;
    EGLContext         context;

    AndroidApp*        app;
    ASensorManager*    sensorManager;
    const ASensor*     accelerometerSensor;
    ASensorEventQueue* sensorEventQueue;
    bool               isRunning;

	struct timespec    last;
}
appData[1];


static void OnResized()
{
    EGLint w, h;
    eglQuerySurface(appData->display, appData->surface, EGL_WIDTH,  &w);
    eglQuerySurface(appData->display, appData->surface, EGL_HEIGHT, &h);

    AGLConvert->Init(w, h);
    AApplication->callbacks->OnResized(w, h);
}



static void OnInit()
{
	EGLConfig config;
	bool result = AGLUtils->CreateEGL(appData->app->window, &appData->display, &appData->context, &appData->surface, &config);

	ALog_A(result, "createEGLContext fail");

	EGLint format;
    // 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(appData->display, config, EGL_NATIVE_VISUAL_ID, &format);
    ANativeWindow_setBuffersGeometry(appData->app->window, 0, 0, format);

    OnResized();
    AApplication->Init();

    // start clock
    clock_gettime(CLOCK_MONOTONIC, &appData->last);
}



static void OnDestroy()
{
	AGLUtils->DestroyEGL(&appData->display, &appData->context, &appData->surface);
}

/**
 * Process the next input event
 */
static int32_t OnInputEvent(AndroidApp* app, AInputEvent* event)
{
    switch (AInputEvent_getType(event))
    {

		case AINPUT_EVENT_TYPE_MOTION:
		{
			int32_t action = AMotionEvent_getAction(event);
			switch (action & AMOTION_EVENT_ACTION_MASK)
			{
				// first pointer down
				case AMOTION_EVENT_ACTION_DOWN:
				{

					AApplication->OnTouch
					(
						application_subject_touch,
						AArray_Create
						(
							EventTouchPoint, 1,
							{
								AGLConvert_ToGLX(AMotionEvent_getX(event, 0)),
								AGLConvert_ToGLY(AMotionEvent_getY(event, 0)),
								AMotionEvent_getPointerId(event, 0),
								event_touch_down,
							}
						)
					);
				} 	break;


				// not first pointer down
				case AMOTION_EVENT_ACTION_POINTER_DOWN:
				{
					int indexDown = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;

					AApplication->OnTouch
					(
						application_subject_touch,
						AArray_Create
						(
							EventTouchPoint, 1,
							{
								AGLConvert_ToGLX(AMotionEvent_getX(event, indexDown)),
								AGLConvert_ToGLY(AMotionEvent_getY(event, indexDown)),
								AMotionEvent_getPointerId(event, indexDown),
								event_touch_down,
							}
						)
					);
				} 	break;


				// first pinter up
				case AMOTION_EVENT_ACTION_UP:
				{

					AApplication->OnTouch
					(
						application_subject_touch,
						AArray_Create
						(
							EventTouchPoint, 1,
							{
								AGLConvert_ToGLX(AMotionEvent_getX(event, 0)),
								AGLConvert_ToGLY(AMotionEvent_getY(event, 0)),
								AMotionEvent_getPointerId(event, 0),
								event_touch_up,
							}
						)
					);
				} 	break;


				// not first pointer up
				case AMOTION_EVENT_ACTION_POINTER_UP:
				{
					int  indexUp = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;

					AApplication->OnTouch
					(
						application_subject_touch,
						AArray_Create
						(
							EventTouchPoint, 1,
							{
								AGLConvert_ToGLX(AMotionEvent_getX(event, indexUp)),
								AGLConvert_ToGLY(AMotionEvent_getY(event, indexUp)),
								AMotionEvent_getPointerId(event, indexUp),
								event_touch_up,
							}
						)
					);

				} 	break;



				case AMOTION_EVENT_ACTION_MOVE:
				{
					int count = AMotionEvent_getPointerCount(event);
					EventTouchPoint points[count];

					for (int i = 0; i < count; i++)
					{
						points[i].x    = AGLConvert_ToGLX(AMotionEvent_getX(event, i));
						points[i].y    = AGLConvert_ToGLY(AMotionEvent_getY(event, i));
						points[i].id   = AMotionEvent_getPointerId(event, i);
						points[i].type = event_touch_move;
					}

					AApplication->OnTouch(application_subject_touch, (Array[]) {points, count});
				} 	break;


				case AMOTION_EVENT_ACTION_CANCEL:
				{
					int count = AMotionEvent_getPointerCount(event);
					EventTouchPoint points[count];

					for (int i = 0; i < count; i++)
					{
						points[i].x    = AGLConvert_ToGLX(AMotionEvent_getX(event, i));
						points[i].y    = AGLConvert_ToGLY(AMotionEvent_getY(event, i));
						points[i].id   = AMotionEvent_getPointerId(event, i);
						points[i].type = event_touch_cancel;
					}

					AApplication->OnTouch(application_subject_touch, (Array[]) {points, count});

				} 	break;

				default:
					return 0;
			}

			return 1;
		}


		case AINPUT_EVENT_TYPE_KEY:
		{
		}

    }


    // default dispatching
    return 0;
}


/**
 * Process the sensor event
 *
static int OnSensorEvent(int fd, int events, void* data)
{
	ALog_D("OnSensorEvent events = %d", events);

	if (appData->accelerometerSensor)
	{
		ASensorEvent event;
		while (ASensorEventQueue_getEvents(appData->sensorEventQueue, &event, 1) > 0)
		{
			ALog_D
			(
				"accelerometer: x=%f y=%f z=%f",
				event.acceleration.x, event.acceleration.y,
				event.acceleration.z
			);
		}
	}

	return 1;
}
*/


static void OnCmd(AndroidApp* app, int32_t cmd)
{
    switch (cmd)
    {
    	case APP_CMD_INPUT_CHANGED:
    		ALog_D("APP_CMD_INPUT_CHANGED");
    		break;

        case APP_CMD_INIT_WINDOW:
        	ALog_D("APP_CMD_INIT_WINDOW");
            // The window is being shown, get it ready
        	ALog_A(app->window, "Android window init failed");
		    OnInit();
            break;

        case APP_CMD_TERM_WINDOW:
        	ALog_D("APP_CMD_TERM_WINDOW");
            // The window is being hidden or closed, clean it up.
            break;

        case APP_CMD_WINDOW_RESIZED:
        	ALog_D("APP_CMD_WINDOW_RESIZED");
        	OnResized();
        	break;

        case APP_CMD_WINDOW_REDRAW_NEEDED:
        	ALog_D("APP_CMD_WINDOW_REDRAW_NEEDED");
        	break;

        case APP_CMD_CONTENT_RECT_CHANGED:
        	ALog_D("APP_CMD_CONTENT_RECT_CHANGED");
        	break;

        case APP_CMD_GAINED_FOCUS:
        	ALog_D("APP_CMD_GAINED_FOCUS");

           // When our app gains focus, we start monitoring the accelerometer.
           /**
           if (appData->accelerometerSensor)
           {
                ASensorEventQueue_enableSensor
				(
					appState->sensorEventQueue,
					appState->accelerometerSensor
				);

                // We'd like to get 60 events per second
                ASensorEventQueue_SetEventRate
				(
					appState->sensorEventQueue,
					appState->accelerometerSensor, EVENT_RATE
				);
            }
            */

            appData->isRunning = true;
            break;

        case APP_CMD_LOST_FOCUS:
        	ALog_D("APP_CMD_LOST_FOCUS");

            // When our app loses focus, we stop monitorisng the accelerometer
            // This is to avoid consuming battery while not being used
        	/**
            if (appData->accelerometerSensor)
        	{
               ASensorEventQueue_disableSensor(appData->sensorEventQueue, appData->accelerometerSensor);
            }
            */

            // Also stop running.
            appData->isRunning = false;
            break;

        case APP_CMD_CONFIG_CHANGED:
        	ALog_D("APP_CMD_CONFIG_CHANGED");
        	break;

        case APP_CMD_LOW_MEMORY:
        	ALog_D("APP_CMD_LOW_MEMORY");
        	break;

        case APP_CMD_START:
        	// Application just start
        	ALog_D("APP_CMD_START");
        	break;

        case APP_CMD_RESUME:
        	// Called after application onStart, not visible yet
        	ALog_D("APP_CMD_RESUME");
        	break;

        case APP_CMD_SAVE_STATE:
        	// The system has asked us to save our current state
        	ALog_D("APP_CMD_SAVE_STATE");
            break;

        case APP_CMD_PAUSE:
        	// Called when application going into the background
        	ALog_D("APP_CMD_PAUSE");
        	AApplication->callbacks->OnPause();
        	break;

        case APP_CMD_STOP:
        	// Called after onPause, when application no longer visible
        	ALog_D("APP_CMD_STOP");
        	break;

        case APP_CMD_DESTROY:
        	// Called after onStop,  when application final before destroyed
        	ALog_D("APP_CMD_DESTROY");
        	OnDestroy();
        	break;
    }
}

/**
 * This is the main entry point of a native application that is using
 * android_native_app_glue.  It runs in its own thread, with its own
 * event loop for receiving input events and doing other things
 */
void android_main(AndroidApp* app)
{
	ALog_D("android_main");

	ApplicationMain();

    // make sure glue isn't stripped
    app_dummy();
    memset(appData, 0, sizeof(appData));

    app->onAppCmd     = OnCmd;
    app->onInputEvent = OnInputEvent;
    appData->app      = app;


    // Prepare to monitor accelerometer
//	appData->sensorManager        = ASensorManager_getInstance();
//	appData->accelerometerSensor  = ASensorManager_getDefaultSensor(appData->sensorManager, ASENSOR_TYPE_ACCELEROMETER);
//	appData->sensorEventQueue     = ASensorManager_createEventQueue(appData->sensorManager, app->looper, LOOPER_ID_USER, OnSensorEvent, appData);
    AndroidPollSource* source     = NULL;

    // loop waiting for stuff to do
    while (true)
    {
    	// Read all pending events
        // If not running, we will block forever waiting for events
        // If running, we loop until all events are read
        // then continue to draw the next frame of run
        while (ALooper_pollAll(appData->isRunning ? 0 : -1, NULL, NULL, (void**) &source) > -1)
        {

            // Process this event
            if (source)
            {
                source->process(app, source);
            }

            // Check if we are exiting
            if (app->destroyRequested != 0)
            {
                return;
            }
        }

        struct timespec now;
    	clock_gettime(CLOCK_MONOTONIC, &now);
    	float deltaTime = (now.tv_nsec - appData->last.tv_nsec) * 0.000000001 +
    				      (now.tv_sec  - appData->last.tv_sec);

		appData->last   = now;
		AApplication->Loop(deltaTime);

	    eglSwapBuffers(appData->display, appData->surface);
    }
}
  • 0
    点赞
  • 3
    评论
  • 4
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值