美文网首页NDK
OpenGL-ES 3.0学习指南(四)——NativeActi

OpenGL-ES 3.0学习指南(四)——NativeActi

作者: 798fe2ac8685 | 来源:发表于2016-11-21 12:13 被阅读505次

标签(空格分隔): OpenGL-ES

版本:1
作者:陈小默
声明:禁止商业,禁止转载

发布于作业部落简书


上一篇:OpenGL-ES 3.0学习指南(三)——JNI操作Bitmap


[toc]


六、NativeActivity

通常情况下,使用Java编写的Android程序就已经能够满足绝大部分应用场景。不过在上一节我们可以看出,在大量数据处理上来说,Java等语言的效率还是不如更贴近底层的C/C++的。于是Android为对性能要求较高的应用,比如游戏等,保留了使用Native方法的Activity。

6.1 native_activity.h

native_activity.h是整个NativeActivity的核心文件,该文件定义了Activity的生命周期回调以及事件队列等基本要素。

6.1.1 生命周期回调

typedef struct ANativeActivityCallbacks {
    //第一部分
    void (*onStart)(ANativeActivity* activity);
    void (*onResume)(ANativeActivity* activity);
    void* (*onSaveInstanceState)(ANativeActivity* activity, size_t* outSize);
    void (*onPause)(ANativeActivity* activity);
    void (*onStop)(ANativeActivity* activity);
    void (*onDestroy)(ANativeActivity* activity);
    /**
     * 窗口改变时回调。对于游戏应用相当有用,当应用失去焦点时,应该暂停游戏。
     */
    void (*onWindowFocusChanged)(ANativeActivity* activity, int hasFocus);
    void (*onContentRectChanged)(ANativeActivity* activity, const ARect* rect);
    void (*onConfigurationChanged)(ANativeActivity* activity);
    
    //第二部分
    /**
     * 可绘制的窗口被创建,可以通过ANativeWindow对象的缓冲区绘制图案。
     */
    void (*onNativeWindowCreated)(ANativeActivity* activity, ANativeWindow* window);
    /**
     * 窗口尺寸改变。在不可分屏的设备上无意义。
     */
    void (*onNativeWindowResized)(ANativeActivity* activity, ANativeWindow* window);
    /**
     * 窗口重绘。
     */
    void (*onNativeWindowRedrawNeeded)(ANativeActivity* activity, ANativeWindow* window);
    /**
     * 绘制窗口被销毁时回调。
     */
    void (*onNativeWindowDestroyed)(ANativeActivity* activity, ANativeWindow* window);
    
    //第三部分
    /**
     * 输入事件队列被创建时回调。
     */
    void (*onInputQueueCreated)(ANativeActivity* activity, AInputQueue* queue);
    /**
     * 输入事件队列被销毁时回调。
     */
    void (*onInputQueueDestroyed)(ANativeActivity* activity, AInputQueue* queue);

    //第四部分
    void (*onLowMemory)(ANativeActivity* activity);
} ANativeActivityCallbacks;

上面贴出了完整的NativeActivity的回调方法结构体声明,其中主要包含了四个部分:第一部分与普通Activity相同,不再介绍。

第二部分
这一部分方法是绘制图像相关的生命周期方法。ANativeWindow是一个窗口缓冲区对象,后面会进行详细介绍,我们可以使用这个对象进行绘图操作。

第三部分
该部分是事件相关生命周期,在此期间我们可以对获取到的事件进行处理。注意:该方法在主线程中运行,如果我们需要处理事件队列,必须开启一个子线程。

第四部分
第四部分只包含一个回调,就是低内存警告。这是考虑到了NativeActivity的应用场景而设立的。比如在低内存场景下,游戏应用必须能够做出相应的反应。

6.1.2 NativeActivity对象

/**
 * 该结构体定义了android.app.NativeActivity.对象。该对象由Android框架创建并启动。
 */
typedef struct ANativeActivity {
    /**
     * 声明周期回调方法结构体指针。注意,我们不能修改callbacks的指向,因为这是
     * 由框架所指定的,但我们可以向其中赋值。
     */
    struct ANativeActivityCallbacks* callbacks;

    /**
     * Java虚拟机对象。
     */
    JavaVM* vm;

    /**
     * JNI上下文对象,只能在主线程中被访问。
     */
    JNIEnv* env;

    /**
     * 这就是NativeActivity对象句柄。
     *
     * 注意: 这是一个错误的变量名称(我就想知道这货被扣了多少钱的工资)。 该变量本来应该被命名为
     * 'activity' 而不是 'clazz'。
     */
    jobject clazz;

    /**
     * 应用内数据目录路径名。
     */
    const char* internalDataPath;
    
    /**
     * 外部设备存储目录路径名。
     */
    const char* externalDataPath;
    
    /**
     * SDK版本。
     */
    int32_t sdkVersion;
    
    /**
     * 这是一个用户专属变量,不受框架控制,用户可以将一些数据存储到此处,方便在其他地方使用。
     */
    void* instance;

    /**
     * Asset Manager对象。用户可以通过此对象访问应用的assets文件。
     */
    AAssetManager* assetManager;

    /**
     * 指向应用内OBB文件的目录。如果应用没有OBB文件,此路径可能不存在。
     */
    const char* obbPath;
} ANativeActivity;

6.1.3 NativeActivity入口函数

typedef void ANativeActivity_createFunc(ANativeActivity* activity,
        void* savedState, size_t savedStateSize);

extern ANativeActivity_createFunc ANativeActivity_onCreate;

native_activity.h定义了ANativeActivity_createFunc函数,这个函数是NativeActivity的入口函数,同时也是生命周期中的onCreate方法。ANativeActivity_onCreate声明了入口函数的名称,我们在写入口函数时,其名称必须是ANativeActivity_onCreate。

6.1.4 其他NativeActivity方法

/**
 * 与普通Activity中finish()方法相同,当调用此函数时,将会销毁此activity,注意,这个
 * 函数可以在任何线程被调用。
 */
void ANativeActivity_finish(ANativeActivity* activity);

/**
 * 更改当前Activity中窗口的图像格式,目前可选 WINDOW_FORMAT_RGBA_8888、WINDOW_FORMAT_RGBX_8888、
 * WINDOW_FORMAT_RGB_565 ,具体参数请查阅<native_window.h>。此函数可以在任意线程被调用。
 */
void ANativeActivity_setWindowFormat(ANativeActivity* activity, int32_t format);

/**
 * 添加或移除当前Activity的窗口标记,比如AWINDOW_FLAG_FULLSCREEN等,具体参数请参考<window.h>
 * 此函数可以在任何线程被调用。
 */
void ANativeActivity_setWindowFlags(ANativeActivity* activity,
        uint32_t addFlags, uint32_t removeFlags);

enum {
    /**
     * 显示软键盘,被打开的键盘可以自动隐藏。
     */
    ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001,
    /**
     * 显示软键盘,被打开的键盘不能自动隐藏,除非调用相应的隐藏方法。
     */
    ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002,
};

/**
 * 在当前Activity上打开软键盘。  该函数可以在任何线程被调用。
 */
void ANativeActivity_showSoftInput(ANativeActivity* activity, uint32_t flags);

enum {
    /**
     * 通知表示可以隐藏软键盘。
     */
    ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001,
    /**
     * 强制隐藏软键盘。
     */
    ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002,
};

/**
 * 隐藏当前Activity上打开的软键盘。该函数可以在任何线程被调用。
 */
void ANativeActivity_hideSoftInput(ANativeActivity* activity, uint32_t flags);

6.2 实现NativeActivity

在Google的官方示例中,其使用了一个胶水层<android_native_app_glue.h>,在这一层里对NativeActivity进行了封装,然而,这里的封装结果并没有使得NativeActivity的结果清晰,反而使人摸不着头脑。所以我们抛弃这个胶水层,直接使用native_activity.h实现。

6.2.1 定义头文件

创建一个native-activity.h头文件,在其中声明生命周期以及其他必要的函数。

#ifndef NDK_NATIVE_ACTIVITY_H
#define NDK_NATIVE_ACTIVITY_H

#include <android/native_activity.h>

/*
 * 绑定声明周期函数到activity
 */
void bindLifeCycle(ANativeActivity *activity);

/*
 * NativeActivity的入口函数
 */
void ANativeActivity_onCreate(ANativeActivity *activity, void *savedState, size_t savedStateSize);

/*
 * 处理事件队列的线程函数。
 */
void *looper(void *args);

void onStart(ANativeActivity *activity);
void onResume(ANativeActivity *activity);

void *onSaveInstanceState(ANativeActivity *activity, size_t *outSize);

void onPause(ANativeActivity *activity);

void onStop(ANativeActivity *activity);

void onDestroy(ANativeActivity *activity);

void onWindowFocusChanged(ANativeActivity *activity, int hasFocus);

void onNativeWindowCreated(ANativeActivity *activity, ANativeWindow *window);

void onNativeWindowDestroyed(ANativeActivity *activity, ANativeWindow *window);

void onInputQueueCreated(ANativeActivity *activity, AInputQueue *queue);

void onInputQueueDestroyed(ANativeActivity *activity, AInputQueue *queue);

void onConfigurationChanged(ANativeActivity *activity);

void onLowMemory(ANativeActivity *activity);

#endif //NDK_NATIVE_ACTIVITY_H

6.2.2 定义生命周期函数

接下来我们需要创建一个native-activity.cpp源文件,然后定义声明中方法,并对声明周期方法进行绑定。声明周期的定义如下,在其中使用LOG打印函数名。

#define LOG_TAG "native-activity"

#include "JniUtil.h"
#include "native-activity.h"

...

void
onStart(ANativeActivity *activity) {
    ALOGE("onStart");
}

...

接下来我们定义绑定声明周期的函数:

void
bindLifeCycle(ANativeActivity *activity) {
    activity->callbacks->onStart = onStart;
    activity->callbacks->onResume = onResume;
    activity->callbacks->onSaveInstanceState = onSaveInstanceState;
    activity->callbacks->onPause = onPause;
    activity->callbacks->onStop = onStop;
    activity->callbacks->onDestroy = onDestroy;
    activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
    activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
    activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
    activity->callbacks->onInputQueueCreated = onInputQueueCreated;
    activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
    activity->callbacks->onConfigurationChanged = onConfigurationChanged;
    activity->callbacks->onLowMemory = onLowMemory;
}

接下来实现Activity的入口函数,并在其中调用bindLifeCycle函数

void
ANativeActivity_onCreate(ANativeActivity *activity, void *savedState, size_t savedStateSize) {
    ALOGE("onCreate");
    bindLifeCycle(activity);
}

6.2.3 实现事件处理线程

通过上面的步骤,这个NativeActivity其实已经可以正常运行了。但是,如果我们需要能够正常接收事件的话,就需要开启一个线程。于是我们需要实现looper函数。

static bool isLoop = false;
static pthread_t loopID;

void *
looper(void *args) {
    ANativeActivity *activity = (ANativeActivity *) args;
    AInputQueue *queue = (AInputQueue *) activity->instance;
    AInputEvent *event = NULL;
    while (isLoop) {
        if (!AInputQueue_hasEvents(queue))//判断队列中是否有未处理事件
            continue;
        AInputQueue_getEvent(queue, &event);//从队列中获取一个事件
        switch (AInputEvent_getType(event)) {//判断事件类型
            case AINPUT_EVENT_TYPE_MOTION: {//触摸事件类型
                switch (AMotionEvent_getAction(event)) {
                    case AMOTION_EVENT_ACTION_DOWN:{//触摸按下事件
                        float x = AMotionEvent_getX(event, 0);//获得x坐标
                        float y = AMotionEvent_getY(event, 0);//获得y坐标
                        ALOGE("X:%f,Y:%f", x, y);//输出坐标
                        break;
                    }
                    case AMOTION_EVENT_ACTION_UP:{//触摸抬起事件
                        break;
                    }
                }
                break;
            }
            case AINPUT_EVENT_TYPE_KEY: {//按键事件类型
                switch (AKeyEvent_getAction(event)) {
                    case AKEY_EVENT_ACTION_DOWN: {//按键按下事件
                        switch (AKeyEvent_getKeyCode(event)) {
                            case AKEYCODE_BACK: {//返回键
                                ANativeActivity_finish(activity);//退出Activity
                                break;
                            }
                        }
                        break;
                    }
                    case AKEY_EVENT_ACTION_UP: {//按键抬起事件
                        break;
                    }
                }
            }
        }
        AInputQueue_finishEvent(queue, event, 1);//将事件从事件队列中移除
    }
    return args;
}

接下来我们只用在事件队列创建完成后开启线程,然后在事件队列销毁前退出线程循环即可,如果对线程不太了解的,可以参考C++多线程编程(二)——线程操作

void
onInputQueueCreated(ANativeActivity *activity, AInputQueue *queue) {
    ALOGE("onInputQueueCreated");
    isLoop = true;
    activity->instance = (void *) queue;
    pthread_create(&loopID, NULL, looper, activity);
}

void
onInputQueueDestroyed(ANativeActivity *activity, AInputQueue *queue) {
    ALOGE("onInputQueueDestroyed");
    isLoop = false;
}

6.2.4 注册NativeActivity

在配置完CMakeList之后,我们需要修改应用的清单配置文件

        <activity
            android:name="android.app.NativeActivity"
            android:configChanges="orientation|keyboardHidden"
            android:label="@string/app_name">
            <meta-data
                android:name="android.app.lib_name"
                android:value="ndk-lib" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

这里activity的名称是固定的android.app.NativeActivitymeta-data以key-value形式描述该Activity参数,比如name="android.app.lib_name"value="ndk-lib"指明了这个NativeActivity所在的本地库的库名为ndk-lib,另外,该Activity必须被指定为MainActivity且需要自动启动。


下一篇:OpenGL-ES 3.0学习指南(五)——EGL基础

相关文章

网友评论

    本文标题:OpenGL-ES 3.0学习指南(四)——NativeActi

    本文链接:https://www.haomeiwen.com/subject/telupttx.html