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

混合Java和C/C++代码

从Java调用C/C++函数很简单,需要几个步骤:
(1) 必须在Java代码里面声明native方法
(2) 需要实现JNI glue layer
(3) 需要创建Android make file
(4) 在C/C++实现native方法
(5) 需要编译native库
(6) 需要加载native库

It really is easy in its own twisted way. 我们将浏览每一步,最后,你将了解混合Java和C/C++代码的基本过程。稍后的章节里,我们将讨论更加错综复杂的Android make file,使你的代码更加优化。因为Android NDK存在Linux、Mac OS和Windows版本(使用Cygwin或者NDK版本7以上),尽管大概的操作差不多,具体的步骤可能会稍有不同。接下来的步骤假设已经创建了Android工程,期望添加native代码。

声明Native方法

第一步在Listing2-1给出,很繁琐。

Listing 2-1 在Fibonacci.java中声明native方法

public class Fibonacci {
    public static native long recursiveNative (int n); // 注意native关键字
}

Native方法只是使用native关键字简单的声明,在Java不提供实现。上面声明的方法是public的,native方法可以是public, protected, private或者package-private,就像其他的Java方法。通常,native方法不需要是static的,也不需要仅使用基本类型。从调用者的角度看,一个native方法和其他方法一样。一旦它被声明,你可以在Java代码里面去调用它,不会有编译问题。然而,如果你的应用程序运行并且调用Fibonacci.recursiveNative,程序将抛出UnsatisfiedLinkError并且crash。这是可以预见的,因为现在除了声明一个函数,你还没有做其他的,函数的实际实现还不存在。

一旦你的native方法声明了,你可以开始去写JNI glue layer。

实现JNI Glue Layer

Java通过JNI framework调用C/C++写的库里面的方法。开发平台的JDK可以帮助你创建JNI glue layer. 首先,需要一个头文件定义准备去实现的方法。不需要自己去写这个头文件,因为你可以并且有必要使用JDK的javah工具。

最终,简单的改变目录到应用目录,调用javah去创建需要的头文件。头文件创建在应用的jni目录。因为开始jni目录并不存在,在创建头文件前你需要去显式的建立它。假设你的工程保存在~/workspace/MyFibonaciApp,要执行的命令是:
>> cd ~/workspace/MyFibonacciApp
>> mkdir jni
>> javah –classpath bin –jni –d jni com.apress.proandroid.Fibnacci

NOTE:需要指定类的完整名字。如果javah返回“Class com.apress.proandroid.Fibonacci not found”的错误,保证已经通过-classpath指定了正确的名字,而且全名是正确的。-d选项指定了在哪里创建头文件。因为javah需要使用Fibonacci.class,在执行这个命令前保证你的应用已经编译过了。

现在在~/workspace/MyFibonacciApp/jni有了一个叫com_apress_proandroid_Fibonacci.h的文件,内容如Listing 2-2所示。不需要去直接修改这个文件。如果需要更新这个文件(比如,如果决定去在Java文件中重命名native方法或者添加一个新的),可以使用javah去创建它。

Listing 2-2 JNI头文件

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_apress_proandroid_Fibonacci */

#ifndef _Included_com_apress_proandroid_Fibonacci
#define _Included_com_apress_proandroid_Fibonacci

#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:    com_apress_proandroid_Fibonacci
 * Method:   recursiveNative
 * Signature: (I)J
 */
JNIEXPORT jlong JNICALL
Java_com_apress_proandroid_Fibonacci_recursiveNative(JNIEnv *, jclass, jint);

#ifdef __cplusplus
}
#endif

#endif

单独的C头文件不会有任何帮助。现在需要在将创建的文件com_apress_proandorid_Fibonacci.c里实现Java_com_apress_proandroid_Fibonacci_recursiveNative方法,如Listing 2-3所示。

Listing 2-3 JNI C源文件

#include "com_apress_proandroid_Fibonacci.h"

/*
 * Class:     com_apress_proandroid_Fibonacci
 * Method:    recursiveNative
 * Signature: (I)J
 */
jlong JNICALL
Java_com_apress_proandroid_Fibonacci_recursiveNative(JNIEnv *env, jclass clazz, jint n) {
    return 0;    // 现在只是一个stub,返回0
}

在JNI layer的所有方法有一些共同点:它们的第一个参数通常是JNIEnv*类型(指向JNIEnv对象)。JNIEnv对象是JNI环境本身,通过它去和VM交互。当方法被定义成static,第二个参数是jclass类型,如果是不是static的,第二个参数是jobject类型。

TIP:尝试用javah和-stubs选项去生成c文件(javah –classpath bin –stubs com_apress_proandorid_Fibonacci –d jni)。如果你使用老版本的JDK,它可能会成功,尽管你很可能会得到这样的错误信息:Error:JNI does not require stubs, please refer to the JNI documentation。

创建Makefile

你很确定使用NDK的GCC编译器可以把C++文件编译成一个库,NDK提供了一个工具,ndk-build,它也可以做这件事。为了知道要去做什么,ndk-build工具你创建的使用两个文件:
(1) Applicaton.mk(optional)
(2) Android.mk

需要在应用的JNI目录创建这两个文件(JNI头文件和源文件已经存在这里)。创建这两个文件时,作为灵感的源泉只需引用一个已经定义这些文件的现有项目。在NDK的实例目录下包含了使用native代码的应用程序,hello-jni是最简单的。因为Applicatoin.mk是可选的文件,在每个实例中都找不到它。你需要尽快开始使用简单的Applicaton.mk和Android.mk去创建工程,现在不需要考虑性能。尽管Application.mk是可选的,可以不需要它,Listing 2-4给出了这个文件的基本版本。

Listing 2-4 指定一个ABI的基本Application.mk

APP_ABI := armeabi-v7a

Application.mk指出仅需要创建library的一个版本,这个版本以Cortex系列处理器为目标。如果没有Applicaton.mk,一个以armeabi ABI(ARMv5)为目标的库将被创建,等同于创建一个如Listing 2-5所示的Application.mk文件。

Listing 2-5 指定armeabi为唯一ABI的Application.mk

APP_ABI := armeabi

使用Android.mk的最简单形式有一些啰嗦,因为最终把它编译成库的工具有默认的规定。Listing 2-6给出的Android.mk的基本版本。

Listing 2-6 Android.mk的基本版本

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := fibonacci
LOCAL_SRC_FILES := com_apress_proandroid_Fibonacci.c
include $(BUILD_SHARED_LIBRARY)

这个文件必须从local path的定义开始,即包含Android.mk的位置。Android NDK提供了可以在makefile中用的几个宏,这里我们使用到了my-dir宏,返回最后包含makefile的路径。在我们的例子中,最后包含的makefile是在~/workspace/MyFibonacciApp/jni/,因此LOCAL_PATH将被设置为~/workspace/MyFibonacciApp。

第二行只是简单的清除所有的LOCAL_XXX变量,除了LOCAL_PATH。如果忘掉了这一行,变量定义可能不正确。如果希望应用从一开始就处于可预见的状态,在定义模块前不能忘掉在Android.mk中包含这一行。


LOCAL_MODULE定义了模块的名字,用来生成库名。例如,如果LOCAL_MODULE设置为fibonacci,库将是libfibonacci.so。LOCAL_SRC_FILES列出了将被编译的所有文件,在这个例子中只有com_apress_proandroid_Fibonacci.c(JNI glue layer),因为我们还没有实现实际的函数。每当你新创建一个文件,记得把它添加到LOCAL_SRC_FILES,不然它不会被编译到库里。

最终,当所有的变量被定义,需要包含实际建立库的规则的文件。在这个例子中,我们期望去建立一个shared library,因此我们包含了$(BUILD_SHARED_LIBRARY)。
虽然这看起来有些费解,首先你只需要关心定义LOCAL_MODULE和LOCAL_SRC_FILES,因为该文件的其余部分多数是模板。
关于这些makefile的更多信息,参考本章的Application.mk和Android.mk部分。

实现Native函数

既然已经定义了makefile,我们需要创建fibonacci.c完成C代码的实现,如Listing 2-7所示,并从glue layer调用新实现的功能,如Listing 2-8所示。因为在fibonacci.c中实现的函数在被调用之前需要声明,因此需要创建一个新的头文件,如Listing 2-9。同样需要将fibonacci.c在Android.mk添加到编译文件中。

Listing 2-7 在fibonacci.c中实现新函数

#include "fibonacci.h"

uint64_t recursive (unsigned int n)
{
    if (n > 1) return recursive(n-2) + recursive(n-1);
    return n;
}

Listing 2-8 在Glue Layer调用函数

#include "com_apress_proandroid_Fibonacci.h"
#include "fibonacci.h"

/*
 * Class:        com_apress_proandroid_Fibonaccci
 * Method:       recursiveNative
 * Signature:    (I)J
 */
jlong JNICALL
Java_com_apress_proandroid_Fibonacci_recursiveNative(JNIEnv *env, jclass clazz, jint n)
{
    return recursive(n);
}

Listing 2-9 fibonacci.h头文件

#ifndef _FIBONACCI_H_
#define _FIBONACCI_H_

#include <stdint.h>

extern uint64_t recursive(unsigned int n);

#endif

NOTE:保证在C/C++代码中使用了正确的类型,比如jlong是64位的。使用明确定义好的类型,比如uint64_t或者int32_t当有size问题的时候。

有些人可能会说使用多个文件添加了没必要的复杂性,所有的东西都可以在glue layer实现,即在一个文件而不是3个或者4个文件(Fibonacci.h, Fibonacci.c, com_apress_proandroid_Fibonacci.c, 甚至com_apress_proandroid_Fibonacci.h)。尽管这在技术上是可行的,如Listing 2-10所示,但是并不推荐。这样做glue layer和原生函数的实现会紧密结合,使得代码不可以在一个非Java的应用重用。比如,你可能希望在IOS应用重用同样的头和C/C++文件。保持glue layer JNI专用,而且是仅仅JNI专用。

你可能尝试去移除JNI头文件的包含,请保留它,因为它保证你的功能和在Java层定义的一致(假设你记得每当在Java里面有变化的时候用javah工具去创建这个头文件)。

Listing 2-10 所有的三个文件组合成一个

#include "com_apress_proandroid_Fibonacci.h"
#include <stdint.h>

static uint64_t recursive (unsigned int n) 
{
    if (n > 1) return recursive(n-2) + recursive(n-1);
    return n;
}

/*
 * Class:     com_apress_proandroid_Fibonacci
 * Method:    recursiveNative
 * Signature: (I)J
 */
 jlong JNICALL
 Java_com_apress_proandroid_Fibonacci_recursiveNative(JNIEnv *env, jclass clazz, jint n)
 {
     return recursive(n);
 }



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值