精通安卓性能优化-第二章(五)

来源:互联网 发布:淘宝网旗舰店是正品吗 编辑:程序博客网 时间:2024/05/19 03:20

Strings

在Java和C/C++使用字符串可能导致性能问题,Java的String使用16位的Unicode字符(UTF-16),而许多C/C++函数简单的使用char*如代表字符串(即C/C++中的string大多数情况下是ASCII或者UTF-8)。怀旧的开发者甚至使用EBCDIC编码。即,Java字符串在JNI可以使用之前需要转换成C/C++字符串。在Listing 2-20给出一个简单的实例。

Listing 2-20 JNI Glue Layer和使用String的Java Native方法

// Java (in Myclass.java)public class MyClass {    public static native void doSomethingWithString (String s);}// JNI glue layer (in C file)void JNICALLJava_com_apress_proandroid_MyClass_doSomethingWithString(JNIEnv *env, jclass clazz, jstring s) {    const char* str = (*env)->GetStringUTFChars(env, s, NULL);    if (str != NULL) {        // 在这里对str字符串做处理                // 记得释放string避免内存泄露        (*env)->ReleaseStringUTFChars(env, s, str);    }}

JNI为字符串提供了多个方法,他们都以同样的方式工作:
(1) Java String必须转换成C/C++字符串
(2) C/C++字符串必须被释放。

表格2-6给出了JNI提供的不同的字符串get/release方法,带有简单的介绍。

Table 2-6 JNI Get/Release的字符串方法


因为内存分配从来不会被释放,你应该在代码中任何可能的地方使用GetStringRegion和GetStringUTFRegion。这样做,你可以:
(1) 避免可能的内存分配
(2) 在预分配的buffer里面复制部分String(很可能在stack)
(3) 避免需要释放字符串,避免忘记释放字符串

NOTE:另外的String方法参考JNI在线文档和NDK的jni.h头文件。

访问字段或者方法

你可以在JNI glue layer访问Java对象或者类的成员变量或者方法,然而,这不像访问C++中的对象或者类那么简单。Java对象或者类的成员变量或者方法通过id访问。访问一个变量或者调用一个方法,需要:
(1) 获取成员变量或者方法的id
(2) 通过一个JNI函数去set/get这个field,或者调用方法

Listing 2-21给出一个实例。

Listing 2-21 从JNI Glue Layer修改字段或者调用方法

// Java (in MyClass.java)public class MyClass {    static {        System.loadLibrary("mylib");    }        public static int someInteger = 0;        public static native void sayHelloToJNI();        public static void helloFromJNI() {        Log.i("MyClass", "Greetings! someInteger=" + someInteger);    }}// JNI glue layer (in C file)void JNICALLJava_com_apress_proandroid_MyClass_sayHelloToJNI(JNIEnv *env, jclass clazz) {    // 得到someInteger字段和helloFromJNI方法的id    jfieldID someIntegerId = (*env)->GetStaticFieldID(env, clazz, "someInteger", "I");    jfieldID helloFromJNIId = (*env)->GetStaticMethodID(env, clazz, "helloFromJNI", "()V");        // 增加someInteger    jint value = (*env)->GetStaticIntField(env, clazz, someIntegerId);    (*env)->SetStaticIntField(env, clazz, value + 1);        // 调用helloFromJNI    (*env)->CallStaticVoidMethod(env, clazz, helloFromJNIId);}

为了性能的原因,你不希望每次需要访问字段或者调用方法的时候去获取成员变量或者方法的id。字段和方法的id当类被VM加载的时候被设置,在类被存在的整个过程中有效。如果类被VM卸载并且重新加载,新的id可能和老的不同。也就是说,一个有效的方法是当类被加载的时候获取id,即在静态初始化模块。如Listing 2-22所示。

Listing 2-22 仅获取Field/Method id一次

// Java (in MyClass.java)public class MyClass {    static {        System.loadLibrary("mylib");        getIds(); // 仅当类加载的时候获取一次id    }        public static int someInteger = 0;        public static native void sayHelloToJNI();        public static void helloFromJNI() {        Log.i("MyClass", "Greetings! someInteger=" + someInteger);    }        private static native void getIds();}// JNI glue layer (in C file)static jfieldID someIntegerId;static jfieldID helloFromJNIId;void JNICALLJava_com_apress_proandroid_MyClass_sayHelloToJNI(JNIEnv *env, jclass clazz) {    // 不需要在这里获取id        // 增加someInteger    jint value = (*env)->GetStaticIntField(env, clazz, someIntegerId);    (*env)->SetStaticIntField(env, clazz, value + 1);        // 调用helloFromJNI    (*env)->CallStaticVoidMethod(env, clazz, helloFromJNIId);}void JNICALLJava_com_apress_proandroid_MyClass_getIds(JNIEnv *env, jclass clazz) {    // 获取someInteger字段和helloFromJNI方法的id    someIntegerId = (*env)->GetStaticFieldID(env, clazz, "someInteger", "I");    helloFromJNIId = (*env)->GetStaticMethodID(env, clazz, "helloFromJNI", "()V");}

JNI定义了许多的函数可以用来访问成员变量和调用方法。比如,访问一个整型变量和一个布尔型变量是两个操作,由两个不同的函数完成。类似的,定义了不同的函数去调用静态方法和非静态方法。

NOTE:参考JNI在线文档和NDK的jni.h头文件查看你可以使用的完整的函数列表。

Android定义了自己的函数和数据结构去访问大多数native代码使用到的类。比如,在android/bitmap.h(在NDK release 4b中引入)中定义的API允许访问bitmap对象的pixel buffer:
(1) AndroidBitmap_getInfo
(2) AndroidBitmap_lockPixels
(3) AndroidBitmap_unlockPixels


NDK 5引入了许多新的API,应用开发者可以在native代码中使用去访问部分java framework,而不需要依赖于JNI特性(比如, JNIEnv, jclass, jobject)。

Native Activity

现在我们已经看到了怎样在一个应用中混合使用Java和C/C++代码。Android 2.3更进一步定义了NativeActivity类,允许你用C/C++实现整个应用但是并不强制你去做,因为你仍然需要通过JNI访问整个的Android Java Framework。

NOTE:应用不需要所有的activity都使用NativeActivity。比如,你可以写一个两个activity的应用:一个NativeActivity,一个ListActivity。

如果你选择读原文件或者头文件代替更多的正式文档,你是在享受。事实上,大多数在头文件中提到的文档,你可以在NDK的platforms/android-9/arch-arm/usr/include/android路径下找到。Table 2-7列出了这些头文件。

Table 2-7 Native Activities使用的头文件



创建一个native activity非常简单。第一步是定义应用的manifest文件,让Android知道你要使用一个native activity。对每一个native activiy,你需要在应用的AndroidManifest.xml中指定两个事情:
(1) 需要实例化的类
(2) 需要加载的库和它的入口

第一项和其他非native的activity没什么不同。当activity创建后,Android需要去实例化正确的类,这是为什么android:name在<activity>标签里的原因。在大多数情况下,不需要你的应用去继承NativeActivity类,所以你需要使用android.app.NativeActivity类作为实例化的类。没有什么阻止你实例化一个你创建的类继承自NativeActivity。

第二项用来让Android知道哪个库包含你的native代码,这样可以在activity创建的时候自动
的加载。这条给安卓的metadata信息以键值对的方式:name需要设置为android.app.lib_name,value指定library的名字不要带有lib前缀或者.so扩展名。可选的,你同样可以以键值对的方式指定库的entry point,name设置为android.app.func_name,value设置为函数名。默认情况下,函数名字被设置为ANativeActivity_onCreate。

Listing 2-23给出了manifest文件的一个示例。最小的SDK version被设置为9,因为NativeActivity在Android 2.3被引入,activity的native代码在libmyapp.so中。

Listing 2-23 Native应用的AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.apress.proandroid"    android:versionCode="1"    android:versionName="1.0">        <uses-sdk android:minSdkVersion="9" />        <application android:icon="@drawable/icon"                 android:label="@string/app_name"                 android:hasCode="false">        <activity android:name="android.app.NativeActivity"                  android:lable="@string/app_name">            <meta-data android:name="android.app.lib_name"                       android:value="myapp" />            <meta-data android:name="android.app.func_name"                       android:value="ANativeActivity_onCreate" />            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>    </application></manifest>

NOTE: 可选的,如果你的应用不含有任何的Java代码,你可以在<application>标签中设置android:hasCode为false。

加载这个应用将会导致一个Runtime错误,因为libmyapp.so还不存在。结果,下一步是去编译这个丢失的库。通过NDK的ndk-build工具完成。

编译丢失的库

你需要定义Application.mk文件和Android.mk文件。当使用native activity,Android.mk有不同,如Listing 2-24所示。你同样需要包含应用的实际实现一个文件,myapp.c(在Listing 2-25)。

Listing 2-24 Native应用的Android.mk

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := myappLOCAL_SRC_FILES := myapp.cLOCAL_LDLIBS := -llog -landroidLOCAL_STATIC_LIBRARIES := android_native_app_glueinclude $(BUILD_SHARED_LIBRARY)$(call import-module, android/native_app_glue)

这个Android.mk和前面我们使用的不同在:
(1) 连接的shared libraries不同
(2) 连接的static library不同

因为你的native activity使用Android native应用API,你需要添加-landroid到LOCAL_LDLIBS。依赖于你要使用什么,你需要去连接更多的库。比如,-llog用来连接logging库,允许你使用logcat调试机制。


Android NDK提供了更简单的方式去创建一个native应用,在NDK的native_app_glue模块实现。使用这个模块,你不仅需要把它添加到LOCAL_STATIC_LIBRARIES,同样需要在你的工程中import,通过import-module功能宏,如Listing 2-24最后一行那样。

NOTE:native_app_glue模块在NDK实现,源代码的位置在android-ndk-r7/sources/android/native_app_glue目录下。你可以自由的修改实现并且使用你自己修改的版本编译你的应用,因为库是静态链接的。

Listing 2-25给出了应用的一个示例,在myapp.c中实现,监听如下的事件:
(1) Application events (类似于Java的onStart方法)
(2) Input events(key, motion)
(3) Accelerometer
(4) Gyroscope(callback-based)

这个应用除了建立sensors和告诉你如何处理sersor事件没有做任何其他的事情。在这个特殊的例子中,sersor的值通过调用_android_log_print显示。使用这个应用作为你自己需要的骨架。


Listing 2-25 myapp.c的实现

#include <android_native_app_glue.h>#include <android/sensor.h>#include <android/log.h>#define TAG "myapp"typedef struct {    // 加速计    const Asensor* accelerometer_sensor;    AsensorEventQueue* accelerometer_event_queue;    // 陀螺仪    const Asensor* gyroscope_sensor;    ASensorEventQueue* gyroscope_event_queue;} my_user_data_t;static int32_t on_key_event (struct android_app* app, AInputEvent* event){    // 使用AKeyEvent_xxx API    return 0; // 或者1,如果已经处理了这个事件}static int32_t on_motion_event(struct android_app* app, AInputEvent* event) {    // 使用AMotionEvent_xxx API    return 0; // 或者1,如果已经处理了这个事件}// 简单的检测事件类型,调用合适的函数static int32_t on_input_event(struct android_app* app, AInputEvent* event){    int32_t type = AInputEvent_getType(event);    int32_t handled = 0;        switch(type) {        case AINPUT_EVENT_TYPE_KEY:            handled = on_key_event(app, event);            break;                case AINPUT_EVENT_TYPE_MOTION:            handled = on_motion_event(app, event);            break;    }        return handled;}// 一些函数还没有实现static void on_input_changed (struct android_app* app) {}static void on_init_window (struct android_app* app) {}static void on_term_window (struct android_app* app) {}static void on_window_resized (struct android_app* app) {}static void on_window_redraw_needed (struct android_app* app) {}static void on_content_rect_changed (struct android_app* app) {}// 在这里开启传感器static void on_gained_focus (struct android_app* app){    my_user_data_t* user_data = app->userData;        if (user_data->accelerometer_sensor != NULL) {        ASensorEventQueue_enableSensor(user_data->accelerometer_event_queue, user_data->accelerometer_sensor);        ASensorEventQueue_setEventRate(user_data->accelerometer_event_queue, user_data->accelerometer_sersor, 1000000L/60);    }        if (user_data->gyroscope_sensor != NULL) {        ASensorEventQueue_enableSensor(user_data->gyroscope_event_queue, user_data->gyroscope_sensor);        ASensorEventQueue_setEventRate(user_data->gyroscope_event_queue, user_data->gyroscope_sensor, 1000000L/60);    }}// 当失去焦点的时候关闭传感器static void on_lost_focus (struct android_app* app){    my_user_data_t* user_data = app->userData;        if (user_data->accelerometer_sensor != NULL) {        ASensorEventQueue_disableSensor(user_data->accelerometer_event_queue, user_data->accelerometer_sensor);    }        if (user_data->gyroscope_sensor != NULL) {        ASensorEventQueue_disableSensor(user_data->gyroscope_event_queue, user_data->gyroscope_sensor);    }}// 在这里实现更多的函数static void on_config_changed (struct android_app* app) {}static void on_low_memory (struct android_app* app) {}static void on_start (struct android_app* app) {}static void on_resume (struct android_app* app) {}static void on_save_state (struct android_app* app) {}static void on_pause (struct android_app* app) {}static void on_stop (struct android_app* app) {}static void on_destroy (struct android_app* app) {}// 简单的检测指令并调用正确的函数static void on_app_command (struct android_app* app, int32_t cmd) {    switch (cmd) {        case APP_CMD_INPUT_CHANGED:            on_input_changed(app);            break;        case APP_CMD_INIT_WINDOW:            on_init_window(app);            break;        case APP_CMD_TERM_WINDOW:            on_term_window(app);            break;        case APP_CMD_WINDOW_RESIZED:            on_window_resized(app);            break;        case APP_CMD_WINDOW_REDRAW_NEEDED:            on_window_redraw_needed(app);            break;        case APP_CMD_CONTENT_RECT_CHANGED:            on_content_rect_changed(app);            break;        case APP_CMD_GAINED_FOCUS:            on_gained_focus(app);            break;        case APP_CMD_LOST_FOCUS:            on_lost_focus(app);            break;        case APP_CMD_CONFIG_CHANGED:            on_config_changed(app);            break;        case APP_CMD_LOW_MEMORY:            on_low_memory(app);            break;        case APP_CMD_START:            on_start(app);            break;        case APP_CMD_RESUME:            on_resume(app);            break;        case APP_CMD_SAVE_STATE:            on_save_state(app);            break;        case APP_CMD_PAUSE:            on_pause(app);            break;        case APP_CMD_STOP:            on_stop(app);            break;        case APP_CMD_DESTROY:            on_destroy(app);            break;    }}// 使用定义的looper id#define LOOPER_ID_USER_ACCELEROMETER (LOOPER_ID_USER + 0)#define LOOPER_ID_USER_GYROSCOPE     (LOOPER_ID_USER + 1);// 将可以一次最多获取8个事件#define NB_SENSOR_EVENTS 8static int gyroscope_callback (int fd, int events, void* data) {    // 在这里log所有事情不是好的想法,因为可能会比你期望的更多    __android_log_write(ANDROID_LOG_INFO, TAG, "gyroscope_callback");    return 1;}static void list_all_sensors (ASensorManager* sm) {    ASensorList list;    int i, n;    n = ASensorManager_getSensorList(sm, & list);        for (i=0; i<n; i++) {        const ASensor* sensor = list[i];        const char* name = ASensor_getName(sensor);        const char* vendor = ASensor_getVendor(sensor);        int type = ASensor_getType(sensor);        int min_delay = ASensor_getMinDelay(sensor);        float resolution = ASensor_getResolution(sensor);                __android_log_print(ANDROID_LOG_INFO, TAG, "%s (%s) %d %d %f", name, vendor, type, min_delay, resolution);    }}// 入口点void android_main (struct android_app* app){    my_user_data_t user_data;    ASensorManager* sm = ASensorManager_getInstance();        app_dummy(); // 不要忘了这个调用        // 简单的列出设备上的传感器    list_all_sensors(sm);        state->userData = & user_data;    state->onAppCmd = on_app_command;    state->onInputEvent = on_input_event;        // 加速计    user_data.accelerometersensor = ASensorManager_getDefaultSensor(sm, ASENSOR_TYPE_ACCELEROMETER);    user_data.accelerometer_event_queue = ASensorManager_createEventQueue(sm, state->looper, LOOPER_ID_USER_ACCELEROMETER, NULL, NULL);        // 陀螺仪 (callback-based)    user_data.gyroscope_sensor = ASensorManager_getDefaultSensor(sm, ASENSOR_TYPE_GYROSCOPE);    user_data.gyroscope_event_queue = ASensorManager_createEventQueue(sm, state->looper, LOOPER_ID_USER_GYROSCOPE, gyroscope_callback, NULL);        while (1) {        int ident;        int events;        struct android_poll_source* source;                while ((ident = ALooper_pollAll(-1, NULL, &events, (void**)&source)) >= 0) {            // "standard"事件优先            if ((ident == LOOPER_ID_MAIN) || (ident == LOOPER_ID_INPUT)) {                // source should not be NULL but we check anyway                if (source != NULL) {                    // 这将调用on_app_command或者on_input_event                    source->process(source->app, source);                }            }                        // 加速计事件            if (ident == LOOPER_ID_USER_ACCELEROMETER) {                ASensorEvent sensor_events[NB_SENSOR_EVENTS];                int i, n;                while (( n = ASensorEventQueue_getEvents(user_data.accelerometer_event_queue, sersor_events, NB_SENSOR_EVENTS)) > 0) {                    for (i=0; i<n; i++) {                        ASensorVector* vector = &sensor_events[i].vector;                        __android_log_print(ANDROID_LOG_INFO, TAG, "%d accelerometer x=%f y=%f z=%f", i, vector->x, vector->y, vector->z);                    }                }                           }                        // 在这里处理其他事件                        // 不要忘记检测是否到了返回的时间            if (state->destroyRequested != 0) {                ASensorManager_destroyEventQueue(sm, user_data.accelerometer_event_queue);                ASensorManager_destroyEventQueue(sm, user_data.gyroscope_event_queue);                return;            }        }                // 所有的事情处理完之后在这里做渲染    }}

替代方式

创建native应用的另一种方式是去实现native版本的onCreate(),在这里你不仅初始化你的应用,同样定义所有的其他callbacks(即等同于onStart, onResume, 。。。。)。这是native_app_glue模块实现用来简化你的开发。同样的,native_app_glue模块保证这些事件在一个单独的线程处理,允许你的应用保持响应。定义你自己的onCreate实现,不需要连接native_app_glue库,需要实现ANativeActivity_onCreate而不是android_main。如Listing 2-26所示。

Listing 2-26 ANativeActivity_onCreate的实现

#include <android/native_activity.h>void ANativeActivity_onCreate (ANativeActivity* activity, void* savedState, size_t savedStateSize){    // 在这里设置所有的回调    activity->callbacks->onStart = my_on_start;    activity->callback->onResume = my_on_resume;    ...        // 设置activity->instance为实例特有的数据    activity->instance = my_own_instance; // 和userData相似        // 这里没有事件循环,简单的返回,NativeActivity接着调用你定义的回调}

这看起来是简单的,在你开始监听其他事件和在屏幕上画东西的时候将会变得复杂(比如sensors)。

TIP:如果你使用native_app_glue模块,不要在你的manifest文件中修改库的入口点名字,因为它实现了ANativeActivity_onCreate。

新的NativeActivity类本身不会提升性能。它只是简化了创建native应用开发的机制。实际上,你可以在老的安卓版本自己的应用上实现同样的机制用来写native应用。尽管你的应用可以全部用C/C++写,它仍然运行在Dalvik虚拟机上,它依然依赖于NativeActivity Java类。


总结

我们已经看到如何使用native代码可以提升性能。尽管精心打造的原生代码很少导致性能下降,性能不是你使用NDK的唯一原因。如下是使用NDK的好的原因:
(1) 你希望去复用存在的代码而不需要用Java去重写
(2) 你希望代码在其他不支持Java的平台上使用
(3) 你希望在不支持JIT编译器的老的安卓设备上使用(Android 2.1或者更早),这种情况下原生代码是提供一个好的用户体验的唯一方式
(4) 在应用中使用原生代码使得用户体验更好,即使安卓设备存在JIT编译器


前两个原因很重要,实际上你可能在某些情况下牺牲性能(确保用户体验不受影响,或者在一个可接受的阈值内)。像许多开发者一样,你的资源是有限的,你希望你的应用能够尽量多的人使用。把自己局限于一个单一的平台将无法最大化您的投资。

0 0