Java——本地方法(JNI)详解

JNI

JNI(Java Native Interface,Java本地接口),是Java平台中的一个强大特性。应用程序可以通过JNI把C/C++代码集成进Java程序中。

通过JNI,开发者在利用Java平台强大功能的同时,又不必放弃对原有代码的投资;因为JNI是Java平台定义的规范接口,当程序员向Java代码集成本地库时,只要在一个平台中解决了语言互操作问题,就可以把该解决方案比较容易的移植到其他Java平台中。

使用场景:

  • Java API可能不支某些平台相关的功能。比如,应用程序执行中要使用Java API不支持的文件类型,而如果使用跨进程操作方式,即繁琐又低效;
  • 避免进程间低效的数据拷贝操作;
  • 多进程的派生:耗时、耗资源(内存);
  • 用本地代码或汇编代码重写Java中低效方法。

1、Java调用C函数

将一个本地方法链接到Java程序中,遵循如下几步:

  1. 在Java类中声明一个本地方法
  2. 运行javah以获得包含该方法的C声明的头文件
  3. 用C实现该本地方法
  4. 将代码置于共享类库中
  5. 在Java程序中加载该类库

在这里插入图片描述

1.1、在Java类中声明一个本地方法

Java编程语言使用关键字native表示本地方法,而且很显然,还需要在类中放置一个方法。

关键字native提醒编译器该方法将在外部定义。当然,本地方法不包含任何Java编程语言编写的代码,而且方法头后面直接跟着一个表示终结的分号。因此,本地方法声明看上去和抽象方法声明类似。

package pers.zhang.jni;


public class HelloNative {

    public static native void greeting();
}

在这个特定示例中,本地方法也被声明为static。本地方法既可以是静态的也可以是非静态的,使用静态方法是因为我们此刻还不想处理参数传递。

实际上可以编译这个类,但是在程序中使用它时,虚拟机就会告诉你它不知道如何找到greeting方法,它会报告一个UnsatisfiedLinkError异常。

1.2、运行javah以获得包含该方法的C声明的头文件

为了实现本地代码,需要编写一个相应的C函数,你必须完全按照Java虚拟机预期的那样来命名这个函数。其规则是:

  1. 使用完整的Java方法名,比如:HelloNative.greeting。如果该类属于某个包,那么在前面添加包名,比如:pers.zhang.jni.HelloNative.greeting
  2. 用下划线(_)替换掉所有的点号(.),并加上Java_前缀,例如Java_pers_zhang_jni_HelloNative_greeting
  3. 如果类名含油非ASCII字母或数字,如:_$或是大于\u007F的Unicode字符,用_0xxxx来替代它们,xxxx是该字符的Unicode值的4个十六进制数序列。

注意:如果重载了本地方法,也就是说,用相同的名字提供了多个本地方法,那么必须在名称后附加两个下划线,后面再加上已编码的参数类型。例如,如果你有一个本地方法greeting和另一个本地方法greeting(int repeat),那么,第一个称为Java_HelloNative_greeting,第二个称为Java_HelloNative_greeting__I。

实际上,没人会手工完成这些操作。相反,你应该用-h标志运行javac,并提供头文件放置的目录:

javac -h . HelloNative.java

该命令会在当前目录创建一个名为pers_zhang_jni_HelloNative.h的头文件:

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

#ifndef _Included_pers_zhang_jni_HelloNative
#define _Included_pers_zhang_jni_HelloNative
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     pers_zhang_jni_HelloNative
 * Method:    greeting
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_pers_zhang_jni_HelloNative_greeting
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

这个文件包含了函数声明(宏JNIEXPORTJNICALL
是在头文件jni.h中定义的,它们为那些来自动态装载库的导出函数标明了依赖于编译器的说明符)。

1.3、用C实现该本地方法

现在,需要将函数原型从头文件中复制到源文件中,并且给出函数的实现代码。

实现文件pers_zhang_jni_HelloNative.c如下:

#include "pers_zhang_jni_HelloNative.h"
#include <stdio.h>

JNIEXPORT void JNICALL Java_pers_zhang_jni_HelloNative_greeting (JNIEnv * env, jclass cl)
{
    printf("Hello Native World!\n");
}

现在,请先忽略两个宏:JNIEXPORT和JNICALL。你会发现,该函数声明,接受两个参数,而对应的Java代码对该函数的声明没有参数。第一个参数是指向JNIEnv结构的指针;第二个参数,为HelloWorld对象自身,即this指针。

注意:也可以使用C++实现本地方法。然而,那样必须将实现本地方法的函数声明为 extern “C” (可以阻止C++编译器混编方法名)。例如:

extern "C"
JNIEXPORT void JNICALL Java_pers_zhang_jni_HelloNative_greeting(JNIEnv* env, jclass cl)
{
	cout << "Hello, Native World!" << end;
}

1.4、 将代码置于共享类库中

首先要知道的是,头文件中的jni.h位于$JAVA_HOME/include下,在jni.h中还引入了一个jni_md.h,该头文件包含jbyte、jint和jlong的依赖于机器的typedef。

jni_md.h的位置依赖于操作系统:

#mac
$JAVA_HOME/include/darwin
#linux
$JAVA_HOME/include/linux
#win
$JAVA_HOME/include/win32

以上两个头文件的位置,在编译时需要通过-I参数指定。

将本地C代码编译到一个动态装载库中,具体方法依赖于编译器(本文为mac):

# linxu下Gnu C编译器,编译后为.so
gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o HelloNative.so pers_zhang_jni_HelloNative.c

# windows下的微软编译器,编译后为.dll
cl -I $JAVA_HOME/include -I $JAVA_HOME/include/win32 -LD pers_zhang_jni_HelloNative.c -Fe HelloNative.dll

# mac下Gnu C编译器,编译后为.dylib
gcc -I $JAVA_HOME/include -I $JAVA_HOME/include/darwin -dynamiclib -o HelloNative.dylib pers_zhang_jni_HelloNative.c

# 也可以使用可从http:/ww.cygwin.com处免费获取的Cygwin编程环境。它包含了GNUC编译器和Windows下的UNIX风格编程的库。使用Cygwin时,用以下命令:
gcc -mno-cygwin -D __int64="long long" -I $JAVA_HOME/include -I $JAVA_HOME/include/win32 -shared -Wl,--add-stdcall-alias -o HelloNative.dll pers_zhang_jni_HelloNative.c 

注意:Windows版本的头文件jni_md.h含有如下类型声明:typedef _int64 jlong; 它是专门用于微软编译器的。如果使用的是GNU编译器,那么需要编辑这个文件,例如:

#ifdef __GNUC__
	typedef long long jlong;
#else
	typedef __int64 jlong;
#endif

//或者,和上面的示例那样,使用-D __int64="long long"进行编译

1.5、在Java程序中加载该类库

需要在程序中添加一个加载类库的调用,为了确保虚拟机在第一次使用该类之前就会装在这个库,需要使用静态初始化代码块。

方式一:使用System.load方法

public class HelloNativeTest {

    static {
        //使用绝对路径加载该类库
        System.load("/Users/acton_zhang/J2EE/MavenWorkSpace/java_safe_demo/HelloNative.dylib");
    }

    public static void main(String[] args) {
        HelloNative.greeting();
    }
}
Hello Native World!

方式二:使用System.loadLibrary方法

public class HelloNativeTest {

    static {
        //使用相对路径加载类库
        System.loadLibrary("HelloNative");
    }

    public static void main(String[] args) {
        HelloNative.greeting();
    }
}

需要注意的是,这个相对路径是java.library.path,也就是说虚拟机会去该路径下查找类库。查看默认情况下的java.library.path:

public static void main(String[] args) {
    System.out.println(System.getProperty("java.library.path"));
}
/Users/acton_zhang/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.

所以,共有两个方法让虚拟机找到类库:

  1. 将类库复制到默认的java.library.path下
  2. 执行java代码时,将类库所在的位置设置为java.library.path,例如:java -Djava.library.path=. HelloNativeTest

1.6、总结

1)、关于Load与LoadLibrary

这里 System.load 方法需要读取 .dylib 的绝对路径,如果使用 System.loadLibrary 方法则需要传相对路径,这里建议大家写绝对路径,肯定不会出错。

2)、java.lang.UnsatisfiedLinkError

同样的 .dylib 文件换到别的项目不好使了,这是因为 JNI 要求 package、.h、.cpp 这一系列文件都是对应的,例如前面是 pers_zhang_jni_HelloNative,那我们后续不管 Java 端、C++ 端还是调用的 main 函数,都需要与此对应,如果出现java.lang.UnsatisfiedLinkError错误,需要检查是不是哪里名字没对上。

3)、JNI初始化与销毁

一些本地代码的共享库必须先运行初始化代码。可以把初始化代码放到JNI_0nLoad方法中。类似地,如果提供该方法,当虚拟机关闭时,将会调用JNI_OnUnload方法。它们的原型是:

jint JNI_OnLoad(JavaVM* vm, void* reserved);
void JNI_OnUnload(JavaVM* vm, void* reserved);

JNI_OnLoad方法要返回它所需的虚拟机的最低版本,例如:JNI_VERSION_1_2

2、数值参数与返回值

当在C和Java之间传递数字时,应该知道它们彼此之间的对应类型。例如,C也有int和long的数据类型,但是它们的实现却是取决于平台的。在一些平台上,int类型是l6位的,在另外一些平台上是32位的。然而,在Java平台上int类型总是32位的整数。基于这个原因,Java本地接口定义了jint、jlong等类型。

2.1、数值类型对应

JavaC字节
booleanjbolean1
bytejbyte1
charjchar2
shortjshort2
intjint4
longjlong8
floatfloa4
doublejdouble8

在头文件jni.h和jni_md.h中这些类型被typedef语句声明为在目标平台上等价的类型:

//jni.h
typedef unsigned char   jboolean;
typedef unsigned short  jchar;
typedef short           jshort;
typedef float           jfloat;
typedef double          jdouble;

//jni_md.h
typedef int jint;
#ifdef _LP64 /* 64-bit */
typedef long jlong;
#else
typedef long long jlong;
#endif
typedef signed char jbyte;
#endif /* !_JAVASOFT_JNI_MD_H_ */

2.2、示例

使用本地方法打印给定域宽度和精度的浮点数。

Printf1.java:

package pers.zhang.jni;

public class Printf1 {
    public static native int print(int width, int precision, double x);
}

生成头文件pers_zhang_jni_Printf1.h:

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

#ifndef _Included_pers_zhang_jni_Printf1
#define _Included_pers_zhang_jni_Printf1
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     pers_zhang_jni_Printf1
 * Method:    print
 * Signature: (IID)I
 */
JNIEXPORT jint JNICALL Java_pers_zhang_jni_Printf1_print
  (JNIEnv *, jclass, jint, jint, jdouble);

#ifdef __cplusplus
}
#endif
#endif

C实现文件pers_zhang_jni_Printf1.c:

#include "pers_zhang_jni_Printf1.h"
#include <stdio.h>

JNIEXPORT jint JNICALL Java_pers_zhang_jni_Printf1_print(JNIEnv * env, jclass jclass,
    jint width, jint precision, jdouble x)
{
    char fmt[30];
    jint ret;
    sprintf(fmt, "%%%d.%df", width, precision);
    ret = printf(fmt, x);
    fflush(stdout);
    return ret;
}

编译为动态库Printf1.dylib:

gcc -I $JAVA_HOME/include -I $JAVA_HOME/include/darwin -dynamiclib -o Printf1.dylib pers_zhang_jni_Printf1.c

测试文件Printf1Test.java:

public class Printf1Test {

    static {
        System.load("/Users/acton_zhang/J2EE/MavenWorkSpace/java_safe_demo/src/main/java/pers/zhang/jni/Printf1.dylib");
    }

    public static void main(String[] args) {
        int count = Printf1.print(8, 4, 3.14);
        count += Printf1.print(8, 4, count);
        System.out.println();
        for (int i = 0; i < count; i++) {
            System.out.print("-");
        }
        System.out.println();
    }
}

运行结果:

  3.1400  8.0000
----------------

3、字符串参数

字符串在这两种语言中很不一样,Java编程语言中的字符串是UTF-16编码点的序列,而C的字符串则是以null结尾的字节序列

JNI有两组操作字符串的函数:

  • 一组把Java字符串转换成modified UTF-8字节序列
  • 一组将它们转换成UTF-l6数值的数组,也就是说转换成jchar数组

注释:标准UTF-8编码和“modified UTF-8”编码的差别仅在于编码大于0xFFFF的增补字符。在标准UTF-8编码中,这些字符编码为4字节序列;然而,在modified UTF-8编码中,这些字符首先被编码为一对UTF-16编码的“替代品”,然后再对每个替代品用UTF-8编码,总共产生6字节编码。这有点笨拙,但这是个由历史原因造成的意外,编写Java虚拟机规范的时候Unicode还局限在16位。

如果C代码已经使用了Unicode,那么可以使用第二组转换函数。另一方面,如果字符串都仅限于使用ASCIⅡ字符,那么就可以使用“modified UTF-8”转换函数。

3.1、C代码访问Java字符串

带有字符串参数的本地方法实际上都要接受一个jstring类型的值,而带有字符串参数返回值的本地方法必须返回一个jstring类型的值。JNI函数将读入并构造出这些jstring对象。

例如,NewStringUTF函数会从包含ASCIⅡ字符的字符数组,或者是更一般的“modified UTF-8”编码的字节序列中,创建一个新的jstring对象。

JNI函数有一些奇怪的调用惯例。例如下面是NewStringUTF函数的一个调用:

JNIEXPORT jstring JNICALL Java_HelloNative_getGreeting(JNIEnv* env, jclass cl)
{
	jstring jstr;
	char greeting[] = "Hello, Native World\n";
	jstr = (*env)->NewStringUTF(env, greeting);
	return jstr;
}

有对JNI函数的调用都使用到了env指针,该指针是每一个本地方法的第一个参数。env指针是指向函数指针表的指针。所以,必须在每个JNI调用前面加上(*env)->,以便解析对函数指针的引用。而且,env是每个JNI函数的第一个参数。

在这里插入图片描述

注意:C++中对JNI函数的访问要简单一些。JNIEnv类的C++版本有有一个内联成员函数,它负责帮助我们查找函数指针。例如,可以这样调用NewStringUTF函数:
jstr = env->NewStringUTF(greeting);
注意:这里删除了该调用的参数列表里的JNIEnv指针。

NewStringUTF函数可以用来构造一个新的jstring,而读取现有jstring对象的内容,需要使用GetStringUTFChars函数。该函数返回指向描述字符串的“modified UTF-8”字符的const jbyte*指针。注意,具体的虚拟机可以为其内部的字符串表示方法自由地选择编码机制。所以,你可以得到实际的Java字符串的字符指针。因为Java字符串是不可变的,所以慎重处理cost就显得非常重要,不要试图将数据写到该字符数组中。另一方面,如果虚拟机使用UTF-16或UTF-32字符作为其内部字符串的表示,那么该函数会分配一个新的内存块来存储等价的“modified UTF-8”编码字符。

虚拟机必须知道你何时使用完字符串,这样它就能进行垃圾回收(垃圾回收器是在一个独立线程中运行的,它能够中断本地方法的执行)。基于这个原因,你必须调用ReleaseStringUTFChars函数。

另外,可以通过调用GetStringRegionGetStringUTFRegion方法来提供你自己的缓存,以存放字符串的字符。

最后GetStringUTFLength函数返回字符串的“modified UTF-8”编码所需的字符个数。

3.2、常用方法

  • jstring NewStringUTF(JNIEnv* env, const char bytes[]): 根据以全0字节结尾的modified UTF-8字节序列,返回一个新的Java字符串对象,或者当字符串无法构建时,返回NULL。
  • jsize GetStringUTFLength(JNIEnv* env, jstring string):返回进行UTF-8编码所需的字节个数(作为终止符的全0字节不计入内)。
  • const jbyte* GetStringUTFChars(JNIEnv* env, jstring string, jboolean* isCopy):返回指向字符串的modified UTF-8编码的指针,或者当不能构建字符数组时返回NULL。直到ReleaseStringUTFChars函数调用前,该指针一直有效。isCopy指向一个jboolean,如果进行了复制,则填入JNI_TRUE,否则填入JNI_FALSE
  • void ReleaseStringUTFChars(JNIEnv* env, jstring string, const jbyte bytes[]):通知虚拟机本地代码不再需要通过bytes(GetStringUTFChars返回的指针)访问Java字符串。
  • void GetStringRegion(JNIEnv* env, jstring string, jsize start, jsize length, jchar* buffer):将一个UTF-16双字节序列从字符串复制到用户提供的尺寸至少大于2*length的缓存中。
  • void GetStringUTFRegion(JNIEnv* env, jstring string, jsize start, jsize length, jbyte* buffer):将一个modified UTF-8字符序列从字符串复制到用户提供的缓存中。为了存放要复制的字节,该缓存必须足够长。最坏情况下,要复制3*length个字节。
  • jstring NewString(JNIEnv* env, const jchar chars[], jsize length):根据Unicode字符串返回一个新的Java字符串对象,或者在不能构建时返回NULL。
  • jsize GetStringLength(JNIEnv* env, jstring string):返回字符串中字符的个数。
  • const jchar* GetStringChars(JNIEnv* env, jstring string, jboolean* isCopy):返回指向字符串的Unicode编码的指针,或者当不能构建字符数组时返回NULL。直到ReleaseStringChars函数调用前,该指针一直有效。isCopy要么为NULL;要么在进行了复制时,指向用JNI_TRUE填充的jboolean,否则指向用JNI_FALSE填充的jboolean。
  • void ReleaseStringChars(JNIEnv* env, jstring string, const jchar chars[]):通知虚拟机本地代码不再需要通过chars(GetStringChars返回的指针)访问Java字符串。

3.3、示例

编写一个调用C函数springf的类Printf2.java

package pers.zhang.jni;

public class Printf2 {
    public static native String sprint(String format, double x);
}

生成头文件pers_zhang_jni_Printf2.h:

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

#ifndef _Included_pers_zhang_jni_Printf2
#define _Included_pers_zhang_jni_Printf2
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     pers_zhang_jni_Printf2
 * Method:    sprint
 * Signature: (Ljava/lang/String;D)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_pers_zhang_jni_Printf2_sprint
  (JNIEnv *, jclass, jstring, jdouble);

#ifdef __cplusplus
}
#endif
#endif

C实现文件pers_zhang_jni_Printf2.c:

#include "pers_zhang_jni_Printf2.h"
#include <string.h>
#include <stdlib.h>
#include <float.h>

char* find_format(const char format[])
{
    char* p;
    char* q;

    p = strchr(format, '%');
    while(p != NULL && *(p + 1) == '%')
        p = strchr(p + 2, '%');
    if(p == NULL)
        return NULL;
    p++;
    q = strchr(p, '%');
    while(q != NULL && *(q + 1) == '%')
        q = strchr(q + 2, '%');
    if(q != NULL)
        return NULL;
    q = p + strspn(p, " -0+#");
    q += strspn(q, "0123456789");
    if(*q == '.')
    {
        q++;
        q += strspn(q, "0123456789");
    }
    if(strchr("eEfFgG", *q) == NULL)
        return NULL;
    return p;
}


JNIEXPORT jstring JNICALL Java_pers_zhang_jni_Printf2_sprint(JNIEnv * env, jclass cl, jstring format, jdouble x)
{
    const char* cformat;
    char* fmt;
    jstring ret;
    
    cformat = (*env)->GetStringUTFChars(env, format, NULL);
    fmt = find_format(cformat);
    if(fmt == NULL)
        ret = format;
    else 
    {
        char* cret;
        int width = atoi(fmt);
        if(width == 0)
            width = DBL_DIG + 10;
        cret = (char*)malloc(strlen(cformat) + width);
        sprintf(cret, cformat, x);
        ret = (*env)->NewStringUTF(env, cret);
        free(cret);
    }
    (*env)->ReleaseStringUTFChars(env, format, cformat);
    return ret;
}

在本函数中,我们选择简化错误处理。如果打印浮点数的格式代码不是%w.pc形式的(其中c是e、E、f、g或G中的一个),那么我们将不对数字进行格式化。

编译为动态库Printf2.dylib:

gcc -I $JAVA_HOME/include -I $JAVA_HOME/include/darwin -dynamiclib -o Printf2.dylib pers_zhang_jni_Printf2.c

测试文件Printf2Test.java:

public class Printf2Test {

    static {
        System.load("/Users/acton_zhang/J2EE/MavenWorkSpace/java_safe_demo/src/main/java/pers/zhang/jni/Printf2.dylib");
    }

    public static void main(String[] args) {
        double price = 44.95;
        double tax = 7.75;
        double amountDue = price * (1 + tax / 100);

        String s = Printf2.sprint("Amount due = %8.2f", amountDue);
        System.out.println(s);
    }
}

输出:

Amount due =    48.43

4、访问域

4.1、访问实例域

案例为调用实例方法,Employee.java:

package pers.zhang.jni;


public class Employee {

    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
    
    //本地方法
    public native void raiseSalary(double byPercent);
    
    public void print() {
        System.out.println(name + " " + salary);
    }
}

生成头文件pers_zhang_jni_Employee.h:

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

#ifndef _Included_pers_zhang_jni_Employee
#define _Included_pers_zhang_jni_Employee
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     pers_zhang_jni_Employee
 * Method:    raiseSalary
 * Signature: (D)V
 */
JNIEXPORT void JNICALL Java_pers_zhang_jni_Employee_raiseSalary
  (JNIEnv *, jobject, jdouble);

#ifdef __cplusplus
}
#endif
#endif

注意,方法的第二个参数不再是jclass类型而是jobject类型。实际上,它和this引用等价。静态方法得到的是类的引用,而非静态方法得到的是对隐式的this参数对象的引用。

现在,我们访问隐式参数的salary域。在Javal.0中“原生的”Java到C的绑定中,这很简单,程序员可以直接访问对象数据域。然而,直接访问要求虚拟机暴露它们的内部数据布局。基于这个原因,JNI要求程序员通过调用特殊的JNI函数来获取和设置数据的值。

在例子里,要使用GetdoubleFieldSetDoubleField函数,因为salary是double类型的。对于其他类型,可以使用的函数有:GetIntField/SetIntFieldGetObjectField/SetObjectField等等。其通用语法是:

x = (*env)->GetXxxField(env, this_obj, fieldID);
(*env)->SetXxxField(env, this_obj, fieldID, x);

这里,fieldID是一个特殊类型jfieldID的值,jfieldID标识结构中的一个域,而Xxx代表Java数据类型(Object、Boolean、Byte或其他)。为了获得fieldID,必须先获得一个表示类的值,有两种方法可以实现此目的。GetObjectClass函数可以返回任意对象的类。例如:

jclass class_Employee = (*env)->GetObjectClass(env, this_obj);

FindClass函数可以以字符串形式来指定类名(要以/代替句号作为包名之间的分隔符)。

jclass class_String = (*env)->FindClass(env, "java/lang/String");

之后,可以使用GetFieldID函数来获得fieldID。必须提供域的名字、它的签名以及它的类型的编码。例如,下面是从salary域得到fieldID的代码:

jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, "salary", "D");

C实现文件pers_zhang_jni_Employee.c:

#include "pers_zhang_jni_Employee.h"

JNIEXPORT void JNICALL Java_pers_zhang_jni_Employee_raiseSalary(JNIEnv * env, jobject this_obj, jdouble byPercent)
{
    //获得类
    jclass class_Employee = (*env)->GetObjectClass(env, this_obj);
    //获得fieldID
    jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, "salary", "D");
    //获得字段值
    jdouble salary = (*env)->GetDoubleField(env, this_obj, id_salary);
    //计算
    salary *= 1 + byPercent / 100;
    //设置到字段
    (*env)->SetDoubleField(env, this_obj, id_salary, salary);
}

警告:类引用只在本地方法返回之前有效。因此,不能在代码中缓存GetObjectClss的返回值。不要将类引用保存下来以供以后的方法调用重复使用。必须在每次执行本地方法时都调用GetObjectClass。如果无法忍受这一点,必须调用NewGlobalRef来锁定该引用:

static jclass class_X = 0;
static jfieldID id_a;
...
if(class_X == 0)
{
	jclass cx = (*env)->GetObjectClass(env, obj);
	class_X = (*env)->NewGlobalRef(env, cx);
	id_a = (*env)->GetFieldID(env, cls, "a","...");
}

//现在,嗯可以在后面的调用中使用类引用和域ID了,当结束对类的使用时,务必调用:
(*env)->DeleteGlobalRef(env, class_X);

编译为动态库Employee.dylib:

gcc -I $JAVA_HOME/include -I $JAVA_HOME/include/darwin -dynamiclib -o Employee.dylib pers_zhang_jni_Employee.c

测试文件EmployeeTest.java:

public class EmployeeTest {
    static {
        System.load("/Users/acton_zhang/J2EE/MavenWorkSpace/java_safe_demo/src/main/java/pers/zhang/jni/Employee.dylib");
    }

    public static void main(String[] args) {
        Employee[] staff = new Employee[3];
        staff[0] = new Employee("Harry Hacker", 35000);
        staff[1] = new Employee("Carl Cracker", 75000);
        staff[2] = new Employee("Tony Tester", 38000);

        for (Employee e : staff) {
            e.raiseSalary(5);
        }
        for (Employee e : staff) {
            e.print();
        }
    }
}

输出:

Harry Hacker 36750.0
Carl Cracker 78750.0
Tony Tester 39900.0

4.2、访问静态域

访问静态域和访问非静态域类似,要使用GetStaticFieldIDGetStaticXxxField/SetStaticXxxField函数。它们几乎与非静态的情形一样,只有两个区别:

  1. 由于没有对象,所以必须使用FindClass代替GetObjectClass来获得类引用。
  2. 访问域时,要提供类而非实例对象。

示例:得到System.out的引用

//获得类
jclass class_System = (*env)->FindClass(env, "java/lang/System");
//获得fieldID
jfieldID id_out = (*env)->GetStaticFieldID(env, class_Sytem, "out", "Ljava/io/PrintStream;");
//获得引用
jobject obj_out = (*env)->GetStaticObjectField(env, class_System, id_out);

4.3、常用方法

  • jfieldID GetFieldID(JNIEnv *env, jclass cl, const char name[], const char fieldSignature[]):返回类中的一个域的标识符。
  • Xxx GetXxxField(JNIEnv *env, jobject obj, jfieldID id):返回域的值。域类型Xxx是Object、Boolean、Byte、Char、Short、Int、Long、Float或Double之一。
  • void SetXxxField(JNIEnv *env, jobject obj, jfieldID id, Xxx value):把某个域设置为一个新值。域类型Xxx是Object、Boolean、Byte、Char、Short、Int、Long、Float或Double之一。
  • jfieldID GetStaticFieldID(JNIEnv *env, jclass cl, const char name[], const char fieldSignature[]):返回某类型的一个静态域的标识符。
  • Xxx GetStaticXxxField(JNIEnv *env, jclass cl, jfieldID id):返回某静态域的值。域类型Xxx是Object、Boolean、Byte、Char、Short、Int、Long、Float或Double之一。
  • void SetStaticXxxField(JNIEnv *env, jclass cl, jfieldID id, Xxx value):把某个静态域设置为一个新值。域类型Xxx是Object、Boolean、Byte、Char、Short、Int、Long、Float或Double之一。

5、编码签名

数据类型描述符如下表:

类型描述符
byteB
charC
doubleD
floatF
intI
longJ
shortS
booleanZ
voidV
L+classname+;
数组[
方法签名(args)return

要建立一个方法的完整签名,需要把括号内的参数类型都列出来,然后列出返回值类型。例如,一个接收两个整形参数,一个整形数组参数并返回一个整数的方法签名为:

(II[I)I

Employee类的构造器签名为:

(Ljava/lang/String;D)V

提示:可以使用带有选项-s的javap命令来从类文件中产生方法签名。

例如:

javap -s -private Employee

可以得到以下显示所有域和方法的描述/签名:

Compiled from "Employee.java"
public class pers.zhang.jni.Employee {
  private java.lang.String name;
    descriptor: Ljava/lang/String;
  private double salary;
    descriptor: D
  public pers.zhang.jni.Employee(java.lang.String, double);
    descriptor: (Ljava/lang/String;D)V

  public native void raiseSalary(double);
    descriptor: (D)V

  public void print();
    descriptor: ()V
}

6、C调用Java方法

6.1、实例方法

示例:使用装饰器模式增强Printf类,给它增加一个与C函数fprintf类似的方法。也就是说,它能够在任意PrintWriter对象上打印一个字符串。

package pers.zhang.jni;


import java.io.PrintWriter;

public class Printf3 {
    public static native void fprint(PrintWriter out, String format, double x);
}

生成头文件pers_zhang_jni_Printf3.h:

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

#ifndef _Included_pers_zhang_jni_Printf3
#define _Included_pers_zhang_jni_Printf3
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     pers_zhang_jni_Printf3
 * Method:    fprint
 * Signature: (Ljava/io/PrintWriter;Ljava/lang/String;D)V
 */
JNIEXPORT void JNICALL Java_pers_zhang_jni_Printf3_fprint
  (JNIEnv *, jclass, jobject, jstring, jdouble);

#ifdef __cplusplus
}
#endif
#endif

C实现文件pers_zhang_jni_Printf3:

首先要把打印的字符串组装成一个String对象str,然后从实现本地方法的C函数中调用PrintWriter类的print方法。

使用如下函数掉哟个,可以从C中调用任何Java方法:

(*env)->CallXxxMethod(env, implicit parameter, methodID, explicit parameters)

根据方法的返回类型,用Void、Int、Object等来替换Xxx。就像需要一个fieldID来访问某个对象的一个域一样,还需要一个方法的ID来调用方法。可以通过调用JNI函数GetMethodID,并且提供该类、方法的名字和方法签名来获得方法ID。

在我们的例子中,我们想要获得Printwriter类的print方法的ID。PrintWriter类有几个名为pit的重载方法。基于这个原因,还必须提供一个方法签名,描述想要使用的特定函数的参数和返回值。例如,我们想要使用void print(java.lang.String)方法,对应的签名为(Ljava//Lang/String;)V"

完整的实现代码:

#include "pers_zhang_jni_Printf3.h"
#include <string.h>
#include <stdlib.h>
#include <float.h>

char* find_format(const char format[])
{
    char* p;
    char* q;

    p = strchr(format, '%');
    while(p != NULL && *(p + 1) == '%')
        p = strchr(p + 2, '%');
    if(p == NULL)
        return NULL;
    p++;
    q = strchr(p, '%');
    while(q != NULL && *(q + 1) == '%')
        q = strchr(q + 2, '%');
    if(q != NULL)
        return NULL;
    q = p + strspn(p, " -0+#");
    q += strspn(q, "0123456789");
    if(*q == '.')
    {
        q++;
        q += strspn(q, "0123456789");
    }
    if(strchr("eEfFgG", *q) == NULL)
        return NULL;
    return p;
}


JNIEXPORT void JNICALL Java_pers_zhang_jni_Printf3_fprint(JNIEnv * env, jclass cl, jobject out, jstring format, jdouble x)
{
    const char* cformat;
    char* fmt;
    jstring str;
    jclass class_PrintWriter;
    jmethodID id_print;
    
    cformat = (*env)->GetStringUTFChars(env, format, NULL);
    fmt = find_format(cformat);
    if(fmt == NULL)
        str = format;
    else 
    {
        char* cstr;
        int width = atoi(fmt);
        if(width == 0)
            width = DBL_DIG + 10;
        cstr = (char*)malloc(strlen(cformat) + width);
        sprintf(cstr, cformat, x);
        str = (*env)->NewStringUTF(env, cstr);
        free(cstr);
    }
    //获取类
    class_PrintWriter = (*env)->GetObjectClass(env, out);
    //获得methodID
    id_print = (*env)->GetMethodID(env, class_PrintWriter, "print", "(Ljava/lang/String;)V");
    //调用方法
    (*env)->CallVoidMethod(env, out, id_print, str);
}

数值型的方法ID和域ID在概念上和反射API中的Method和Field对象类似。可以使用以下函数在两者间进行转换:

jobject ToRelectedMethod(JNIEnv* env, jclass class, jmethodID methodID);

methodID FromReflectedMethod(JNIEnv* env, jobject method);

jobject ToReflectedField(JNIEnv* env, jclass class, jfieldID fieldID);

fieldID FromReflectedField(JNIEnv* env, jobject field);

编译为动态库Printf3.dylib:

gcc -I $JAVA_HOME/include -I $JAVA_HOME/include/darwin -dynamiclib -o Printf3.dylib pers_zhang_jni_Printf3.c

测试文件Printf3Test.java:

public class Printf3Test {

    static {
        System.load("/Users/acton_zhang/J2EE/MavenWorkSpace/java_safe_demo/src/main/java/pers/zhang/jni/Printf3.dylib");
    }

    public static void main(String[] args) {
        double price = 44.95;
        double tax = 7.75;
        double amountDue = price * (1 + tax / 100);
        PrintWriter out = new PrintWriter(System.out);
        Printf3.fprint(out, "Amount due = %8.2f\n", amountDue);
        out.flush();
    }
}

输出:

Amount due =    48.43

6.2、静态方法

从本地方法调用静态方法与调用非静态方法类似。两者的差别是:

  1. 要用GetStaticMethodIDCallStaticXxxMethod函数。
  2. 当调用方法时,要提供类对象,而不是隐式的参数对象。

示例,从C代码中调用以下静态方法:

System.getProperty("java.class.path");
//首先找到要用的类,因为没有System类的对象可供使用,所以使用FindClass而非GetObjectClass
jclass class_System = (*env)->FindClass(env, "java/lang/System");
//获得静态getProperty方法的ID
jmethodID id_getProeprty = (*env)->GetStaticMethodID(env, class_Sstem, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
//调用
jobject obj_ret = (*env)->CallStaticObjectMethod(env, class_System, id_getProperty, (*env)->NewStringUTF(env, "java.class.path"));
//返回值是jobject类型的,如果想要把它当做字符串操作,必须把它转型为jstring
jstring str_ret = (jstring)obj_ret;

C++注释:在C中,jstring和jclass类型和数组类型一样,都是与jObject等价的类型。因此,在C语言中,前面例子中的转型并不是严格必需的。但是在C++中,这些类型被定义为指向拥有正确继承层次关系的“哑类”的指针。例
如,将一个jstring不经过转型便赋给jobject在C++中是合法的,但是将jobject赋给jstring必须先转型。

6.3、构造器

本地方法可以通过调用构造器来创建新的Java对象。可以调用NewObject函数来调用构造器。

jobject obj_new = (*env)->NewObject(env, class, methodID, construction parameters);1

可以通过指定方法名为"<init>",并指定构造器(返回值为void)的编码签名,从GetMethodID函数中获取该调用必需的方法ID。

例如,下面是本地方法创建FileOutputStream对象的情形:

const char[] fileName = "...";

jstring str_fileName = (*env)->NewStringUTF(env, fileName);

jclass class_FileOutputStream = (*env)->FindClass(env, "java/io/FileOutputStram");

jmethodID id_FileOutputStream = (*env)->GetMethodID(env, class_FileOutputStream, "<init>", "(Ljava/lang/String;)V");

jobject obj_stream = (*env)->NewObject(env, class_FileOutputStream, id_FileOutputStream, str_fileName);

6.4、另一种方法调用

有若干种JNI函数的变体都可以从本地代码调用Java方法。它们没有我们已经讨论过的那些函数那么重要,但偶尔也会很有用。

CallNonvirtualXxxMethod函数接收一个隐式参数、一个方法ID、一个类对象(必须对应于隐式参数的超类)和一个显式参数。这个函数将调用指定的类中的指定版本的方法,而不使用常规的动态调度机制。

所有调用函数都有后缀"A"和"V"的版本,用于接收数组中或va_list中的显式参数(就像在C头文件stdarg.h中所定义的那样)。

6.5、常用方法

  • jmethodID GetMethodID(JNIEnv *env, jclass cl, const char name[], const char methodSignature[]):返回某类中某个方法的标识符。
  • Xxx CallXxxMethod(JNIEnv *env, jobect obj, jmethoID id, args)
  • Xxx CallXxxMethodA(JNIEnv *env, jobect obj, jmethoID id, jvalue args[])
  • Xxx CallXxxMethodV(JNIEnv *env, jobect obj, jmethoID id, va_list args):调用一个方法。返回类型Xxx是Object、Boolean、Byte、Char、Short、Int、LongFloat或Double之一。第一个函数有可变数量参数,只要把方法参数附加到方法ID之后即可。第二个函数接受jvalue数组中的方法参数。第三个函数接收C头文件stdarg.h中定义的va_list中的方法参数。

javalue是一个联合体,定义如下:

typedef union jvalue
{
	jboolean z;
	jbyte b;
	jchar c;
	jshort s;
	jint i;
	jlong j;
	jfloat f;
	jdouble d;
	jobject l;
} javlue;
  • Xxx CallNonvirtualXxxMethod(JNIEnv *env, jobject obj, jclass cl, jmethodID id, args)
  • Xxx CallNonvirtualXxxMethod(JNIEnv *env, jobject obj, jclass cl, jmethodID id, javlue args[])
  • Xxx CallNonvirtualXxxMethod(JNIEnv *env, jobject obj, jclass cl, jmethodID id, va_list args):调用一个方法,并绕过动态调度。返回类型Xxx是Object、Boolean、Byte、Char、Short、Int、LongFloat或Double之一。第一个函数有可变数量参数,只要把方法参数附加到方法ID之后即可。第二个函数接受jvalue数组中的方法参数。第三个函数接收C头文件stdarg.h中定义的va_list中的方法参数。
  • jmethodID GetStaticMethodID(JNIEnv *env, jclass cl, const char name[], const char methodSignatrue[]):返回类的某个静态方法的标识符。
  • Xxx CallStaticXxxMethod(JNIEnv *env, jclass cl, jmethodID id, args)
  • Xxx CallStaticXxxMethod(JNIEnv *env, jclass cl, jmethodID id, jvalue args[])
  • Xxx CallStaticXxxMethod(JNIEnv *env, jclass cl, jmethodID id, va_list args):调用一个静态方法。返回类型Xxx是Object、Boolean、Byte、Char、Short、Int、LongFloat或Double之一。第一个函数有可变数量参数,只要把方法参数附加到方法ID之后即可。第二个函数接受jvalue数组中的方法参数。第三个函数接收C头文件stdarg.h中定义的va_list中的方法参数。
  • jobject NewObject(JNIEnv *env, jclass cl, jmethodID id, args)
  • jobject NewObject(JNIEnv *env, jclass cl, jmethodID id, jvalue args[])
  • jobject NewObject(JNIEnv *env, jclass cl, jmethodID id, va_list args):调用构造器。函数ID从钓鱼函数名为<init>和返回类型为void的GetMethodID获取。第一个函数有可变数量参数,只要把方法参数附加到方法ID之后即可。第二个函数接受jvalue数组中的方法参数。第三个函数接收C头文件stdarg.h中定义的va_list中的方法参数。

7、访问数组元素

7.1、Java数组类型和C数组类型之间的对应关系

Java数组类型C数组类型
boolean[]jbooleanArray
byte[]jbyteArray
char[]jcharArray
int[]jintArray
short[]jshortArray
long[]jlongArray
float[]jfloatArray
double[]jdoubleArray
Object[]jobjectArray

C++:在C中,所有这些数组类型实际上都是jobject的同义类型。然而,在C++中它们被安排在如图下所示的继承层次结构中。jarray类型表示一个泛型数组。

在这里插入图片描述

7.2、常用方法

  • jsize GetArrayLength(JNIEnv *env, jarray array):返回数组中的元素个数。
  • jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index):返回数组元素的值。
  • void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value):将数组元素设为新值。
  • Xxx* GetXxxArrayElements(JNIEnv env, jarray array, jboolean isCopy):产生一个指向JAVA数组元素的C指针。域类型Xxx是Boolean、Byte、Char、Short、It、Long、Float或Double之一。指针不再使用时,该指针必须传递给ReleaseXxxArrayElements。iscopy可能是NULL,或者在进行复制时,指向用JNI_TRUE填充的jboolean;否则,指向用JNI_FALSE填充的jboolean。
  • void ReleaseXxxArrayElements(JNIEnv *env, jarray array, Xxx elems[], jint mode):通知虚拟机通过GetXxxArrayElements获得的一个指针已经不再需要了。Mode是0(更新数组元素后释放elems缓存)、JNI_COMMIT(更新数组元素后不释放elems缓存)或JNI_ABORT(不更新数组元素便释放elems缓存)之一。
  • void GetXxxArrayRegion(JNIEnv *env, jarray array, jint start, jint length, Xxx elems[]):将Java数组的元素复制到C数组中。域类型Xxx是Boolean、Byte、Char、Short、It、Long、Float或Double之一。
  • void SetXxxArrayRegion(JNIEnv *env, jarray array, jint length, Xxx elems[]);将C数组的元素复制到Java数组找那个。域类型Xxx是Boolean、Byte、Char、Short、It、Long、Float或Double之一。

7.3、示例

C语言实现逆序数组。

package pers.zhang.jni;


public class ArrayTest {
    static {
        System.load("/Users/acton_zhang/J2EE/MavenWorkSpace/java_safe_demo/src/main/java/pers/zhang/jni/ArrayTest.dylib");
    }

    public static void main(String[] args) {
        double[] arr = new double[]{1.0, 2.0, 3.0, 4.0, 5.0};
        reverse(arr);
        for (double num : arr) {
            System.out.print(num + "\t");
        }
    }
    
    public static native void reverse(double[] arr);
}

生成头文件pers_zhang_jni_ArrayTest.h:

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

#ifndef _Included_pers_zhang_jni_ArrayTest
#define _Included_pers_zhang_jni_ArrayTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     pers_zhang_jni_ArrayTest
 * Method:    reverse
 * Signature: ([D)V
 */
JNIEXPORT void JNICALL Java_pers_zhang_jni_ArrayTest_reverse
  (JNIEnv *, jclass, jdoubleArray);

#ifdef __cplusplus
}
#endif
#endif

实现文件pers_zhang_jni_ArrayTest.c:

#include "pers_zhang_jni_ArrayTest.h"

JNIEXPORT void JNICALL Java_pers_zhang_jni_ArrayTest_reverse(JNIEnv * env, jclass cl, jdoubleArray double_array)
{
    //获取数组长度
    jsize length = (*env)->GetArrayLength(env, double_array);
    //获取指向java数组的指针
    double* arr = (*env)->GetDoubleArrayElements(env, double_array, NULL);
    //逆序
    for (int i = 0; i < (length - 1) / 2; i++)
    {
        int temp = arr[i];
        arr[i] = arr[length - 1 - i];
        arr[length - 1 - i] = temp;
    }
    //更新数组之后再释放指针
    (*env)->ReleaseDoubleArrayElements(env, double_array, arr, 0);
}

编译为动态库ArrayTest.dylib:

gcc -I $JAVA_HOME/include -I $JAVA_HOME/include/darwin -dynamiclib -o ArrayTest.dylib pers_zhang_jni_ArrayTest.c

测试后输出:

6.0	5.0	3.0	4.0	2.0	1.0	

8、错误处理

8.1、概述

在Java编程语言中,使用本地方法对于程序来说是要冒很大的安全风险的。C的运行期系统对数组越界错误、不良指针造成的间接错误等不提供任何防护。所以,对于本地方法的程序员来说,处理所有的出错条件以保持Java平台的完整性显得格外重要。尤其是,当本地方法诊断出一个它无法解决的问题时,那么它应该将此问题报告给Jva虚拟机。然后,在这种情况下,很自然地会抛出一个异常。然而,C语言没有异常,必须调用ThrowThrowNew函数来创建一个新的异常对象。当本地方法退出时,Java虚拟机就会抛出该异常。

要使用Throw函数,需要调用NewObject来创建一个Throwable子类的对象。例如,下面分配一个EOFException对象,然后将它抛出:

jclass class_EOFException = (*env)->FindClass(env, "java/io/EOFException");
jmethodID id_EOFException = (*env)->GetMethodID(env, class_EOFException,  "<init>", "()V");
jthrowable obj_exc = (*env)->NewObject(env, class_EOFException, id_EOFException);
(*env)->Throw(env, obj_exc);

通常会调用ThrowNew会更加方便,因为只需要提供一个类和一个"modified UTF-8"字节序列,该函数就会构建一耳光异常对象:

(*env)->ThrowNew(env, (*env)->FindClass(env, "java/io/EOFExceptino"), "Unexpected end of file");

Throw和ThrowNew都只是发布异常,它们不会中断本地方法的控制流。只有当该方法返回时,Java虚拟机才会抛出异常。所以,每一个对Throw和ThrowNew的调用语句之后总是紧跟着return语句。

通常,本地代码不需要考虑捕获Java异常。但是,当本地方法调用Java方法时,该方法可能会抛出异常。而且,一些JNI函数也会抛出异常。

例如,如果索引越界,SetObjectArrayElement方法会抛出一个ArrayIndexOut0fBoundsException异常,如果所存储的对象的类不是数组元素类的子类,该方法会抛出一个ArrayStoreException异常。在这类情况下,本地方法应
该调用·ExceptionOccurred·方法来确认是否有异常抛出。如果没有任何异常等待处理,则下面的调用:

jthrowable obj_exc = (*env)->ExceptionOccurred(env);

将返回NULL。否则,返回一个当前异常对象的引用。如果只要检查是否有异常抛出,而不需要获得异常对象的引用,那么应使用:

jboolean occurred = (*env)->ExceptionCheck(env);

通常,有异常出现时,本地方法应该直接返回。那样,虚拟机就会将该异常传送给Java代码。但是,本地方法也可以分析异常对象,确定它是否能够处理该异常。如果能够处理,那么必须调用下面的函数来关闭该异常:

(*env)->ExceptionClear(env);

8.2、常用方法

  • jint Throw(JNIEnv *env, jthrowable obj): 准备一个在本地代码退出时抛出的异常。成功时返回0,失败时返回一个负值。
  • jint ThrowNew(JNIEnv *env, jclass cl, const char msg[]): 准备一个在本地代码退出时抛出的类型为cl的异常。成功时返回0,失败时返回一个负值。msg是表示异常对象的String构造参数的"modified UTF-8"字节序列。
  • jthrowable ExcepitonOccurred(JNIEnv *env): 如果有异常挂起,则返回该异常对象,否则返回NULL。
  • jboolean ExceptionCheck(JNIEnv *env): 如果有异常挂起,则返回true。
  • void ExceptionClear(JNIenv *env): 清除挂起的异常。

8.3、示例

在例子中,实现了fprint本地方法,这是基于该方法适合编写为本地方法的假设而实现的。下面是我们抛出的异常:

  • 如果格式字符串是NULL,则抛出NullPointerException异常。
  • 如果格式字符串不含适合打印double所需的%说明符,则抛出IllegalArgumentException异常。
  • 如果调用malloc失败,则抛出OutOfMemoryError异常。

最后,为了说明本地方法调用Java方法时,怎样检查异常,我们将一个字符串发送给数据流,一次一个字符,并且在每次调用Java方法后调用ExceptionOccurred。注意,在调用Printwriter.print出现异常时,本地方法并不会立即终止执行,它会首先释放cstr缓存。当本地方法返回时,虚拟机再次抛出异常。

Printf4.Java文件:

package pers.zhang.jni;

import java.io.PrintWriter;


public class Printf4 {
    
    public static native void fprint(PrintWriter ps, String format, double x);
}

生成头文件pers_zhang_jni_Printf4.h:

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

#ifndef _Included_pers_zhang_jni_Printf4
#define _Included_pers_zhang_jni_Printf4
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     pers_zhang_jni_Printf4
 * Method:    fprint
 * Signature: (Ljava/io/PrintWriter;Ljava/lang/String;D)V
 */
JNIEXPORT void JNICALL Java_pers_zhang_jni_Printf4_fprint
  (JNIEnv *, jclass, jobject, jstring, jdouble);

#ifdef __cplusplus
}
#endif
#endif

C实现文件pers_zhang_jni_Printf4.c:

#include "pers_zhang_jni_Printf4.h"
#include <string.h>
#include <stdlib.h>
#include <float.h>

char* find_format(const char format[])
{
    char* p;
    char* q;

    p = strchr(format, '%');
    while(p != NULL && *(p + 1) == '%')
        p = strchr(p + 2, '%');
    if(p == NULL)
        return NULL;
    p++;
    q = strchr(p, '%');
    while(q != NULL && *(q + 1) == '%')
        q = strchr(q + 2, '%');
    if(q != NULL)
        return NULL;
    q = p + strspn(p, " -0+#");
    q += strspn(q, "0123456789");
    if(*q == '.')
    {
        q++;
        q += strspn(q, "0123456789");
    }
    if(strchr("eEfFgG", *q) == NULL)
        return NULL;
    return p;
}

JNIEXPORT void JNICALL Java_pers_zhang_jni_Printf4_fprint(JNIEnv * env, jclass cl, jobject out, jstring format, jdouble x)
{
    const char* cformat;
    char* fmt;
    jclass class_PrintWriter;
    jmethodID id_print;
    char* cstr;
    int width;
    int i;
    
    //输入的参数为空,抛出异常
    if(format == NULL)
    {
        (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/NullPointerException"), "Printf4.fprint:format is null");
        return;
    }
    
    cformat = (*env)->GetStringUTFChars(env, format, NULL);
    fmt = find_format(cformat);
    
    //格式化字符串为不符合标准,抛出异常
    if(fmt == NULL)
    {
        (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/IllegalArgumentException"), "Printf4.fprint:format is invalid");
        return;
    }
    
    width = atoi(fmt);
    if(width == 0)
        width = DBL_DIG + 10;
    cstr = (char*)malloc(strlen(cformat) + width);
    
    if(cstr == NULL)
    {
        (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/OutOfMemoryError"), "Printf4.fprint:malloc failed");
        return;
    }
    
    sprintf(cstr, cformat, x);
    (*env)->ReleaseStringUTFChars(env, format, cformat);
    
    class_PrintWriter = (*env)->GetObjectClass(env, out);
    id_print = (*env)->GetMethodID(env, class_PrintWriter, "print", "(C)V");
    for(i = 0; cstr[i] != 0 && !(*env)->ExceptionOccurred(env); i++)
    {
        (*env)->CallVoidMethod(env, out, id_print, cstr[i]);
    }
    free(cstr);
}

编译为动态库Printf4.dylib:

gcc -I $JAVA_HOME/include -I $JAVA_HOME/include/darwin -dynamiclib -o Printf4.dylib pers_zhang_jni_Printf4.c

测试文件Printf4Test.java:

public class Printf4Test {

    static {
        System.load("/Users/acton_zhang/J2EE/MavenWorkSpace/java_safe_demo/src/main/java/pers/zhang/jni/Printf4.dylib");
    }

    public static void main(String[] args) {
        double price = 44.95;
        double tax = 7.75;
        double amountDue = price * (1 + tax / 100);
        PrintWriter out = new PrintWriter(System.out);

        //%%8.2f 格式不正确,抛出异常
        Printf4.fprint(out,"Amount due = %%8.2f\n", amountDue);
        out.flush();
    }
}

输出:

Exception in thread "main" java.lang.IllegalArgumentException: Printf4.fprint:format is invalid
	at pers.zhang.jni.Printf4.fprint(Native Method)
	at pers.zhang.jni.Printf4Test.main(Printf4Test.java:22)

9、使用调用API(invocation API)

之前都是Java程序调用C代码,假设在相反的情况下,有一个C或者C++的程序,并且想要调用一些Java代码。调用API(invocation API)能够把Java虚拟机嵌入到C或者C++程序中。

下面是初始化虚拟机所需的基本代码:

JavaVMOption options[1];
JavaVMInitArgs vm_ags;
JavaVM *jvm;
JNIEnv *env;

options[0].optionString = "-Djava.class.path=.";
memset(&vm_args, 0, sizeof(vm_args));
vm_args.version = JNI_VERSION_1_2;
vm_args.nOptions = 1;
vm_args.options = options;

JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

JNI_CreateJavaVM的调用将创建虚拟机,并且使指针jvm指向虚拟机,使指针env指向执行环境。

可以给虚拟机提供任意数目的选项,这只需增加选项数组的大小和vm args.nOptions的值。例如,

options[i].optionString = "-Djava.compiler=NONE";

可以钝化即时编译器。

一旦设置完虚拟机,就可以如前面的那样调用Java方法了。只要按常规方法使用env指针即可。

只有在调用API中的其他函数时,才需要jm指针。目前,只有四个这样的函数。最重要的一个是终止虚拟机的函数:

(*jvm)->DestroyJavaVM(jvm);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
书名:《Android底层开发技术实战详解——内核、移植和驱动》(电子工业出版社.王振丽)。本书从底层原理开始讲起,结合真实的案例向读者详细介绍了android内核、移植和驱动开发的整个流程。全书分为19章,依次讲解驱动移植的必要性,何为hal层深入分析,goldfish、msm、map内核和驱动解析,显示系统、输入系统、振动器系统、音频系统、视频输出系统的驱动,openmax多媒体、多媒体插件框架,传感器、照相机、wi-fi、蓝牙、gps和电话系统等。在每一章中,重点介绍了与Android驱动开发相关的底层知识,并对Android源码进行了剖析。 本书适合Android研发人员及Android爱好者学习,也可以作为相关培训学校和大专院校相关专业的教学用书。 全书压缩打包成3部分,这是第3部分。 目录: 第1章 Android底层开发基础 1 1.1 什么是驱动 1 1.1.1 驱动程序的魅力 1 1.1.2 电脑中的驱动 2 1.1.3 手机中的驱动程序 2 1.2 开源还是不开源的问题 3 1.2.1 雾里看花的开源 3 1.2.2 从为什么选择java谈为什么不开源驱动程序 3 1.2.3 对驱动开发者来说是一把双刃剑 4 1.3 Android和Linux 4 1.3.1 Linux简介 5 1.3.2 Android和Linux的关系 5 1.4 简析Linux内核 8 1.4.1 内核的体系结构 8 1.4.2 和Android密切相关的Linux内核知识 10 1.5 分析Linux内核源代码很有必要 14 1.5.1 源代码目录结构 14 1.5.2 浏览源代码的工具 16 1.5.3 为什么用汇编语言编写内核代码 17 1.5.4 Linux内核的显著特性 18 1.5.5 学习Linux内核的方法 26 第2章 分析Android源代码 31 2.1 搭建Linux开发环境和工具 31 2.1.1 搭建Linux开发环境 31 2.1.2 设置环境变量 32 2.1.3 安装编译工具 32 2.2 获取Android源代码 33 2.3 分析并编译Android源代码 35 2.3.1 Android源代码的结构 35 2.3.2 编译Android源代码 40 2.3.3 运行Android源代码 42 2.3.4 实践演练——演示编译Android程序的两种方法 43 2.4 编译Android kernel 47 2.4.1 获取goldfish内核代码 47 2.4.2 获取msm内核代码 50 2.4.3 获取omap内核代码 50 2.4.4 编译Android的Linux内核 50 2.5 运行模拟器 52 2.5.1 Linux环境下运行模拟器的方法 53 2.5.2 模拟器辅助工具——adb 54 第3章 驱动需要移植 57 3.1 驱动开发需要做的工作 57 3.2 Android移植 59 3.2.1 移植的任务 60 3.2.2 移植的内容 60 3.2.3 驱动开发的任务 61 3.3 Android对Linux的改造 61 3.3.1 Android对Linux内核文件的改动 62 3.3.2 为Android构建 Linux的操作系统 63 3.4 内核空间和用户空间接口是一个媒介 64 3.4.1 内核空间和用户空间的相互作用 64 3.4.2 系统和硬件之间的交互 64 3.4.3 使用relay实现内核到用户空间的数据传输 66 3.5 三类驱动程序 70 3.5.1 字符设备驱动程序 70 3.5.2 块设备驱动程序 79 3.5.3 网络设备驱动程序 82 第4章 hal层深入分析 84 4.1 认识hal层 84 4.1.1 hal层的发展 84 4.1.2 过去和现在的区别 86 4.2 分析hal层源代码 86 4.2.1 分析hal moudle 86 4.2.2 分析mokoid工程 89 4.3 总结hal层的使用方法 98 4.4 传感器在hal层的表现 101 4.4.1 hal层的sensor代码 102 4.4.2 总结sensor编程的流程 104 4.4.3 分析sensor源代码看Android api 与硬件平台的衔接 104 4.5 移植总结 116 4.5.1 移植各个Android部件的方式 116 4.5.2 移植技巧之一——不得不说的辅助工作 117 第5章 goldfish下的驱动解析 125 5.1 staging驱动 125 5.1.1 staging驱动概述 125 5.1.2 binder驱动程序 126 5.1.3 logger驱动程序 135 5.1.4 lowmemorykiller组件 136 5.1.5 timed output驱动程序 137 5.1.6 timed gpio驱动程序 139 5.1.7 ram console驱动程序 139 5.2 wakelock和early_suspend 140 5.2.1 wakelock和early_suspend的原理 140 5.2.2 Android休眠 141 5.2.3 Android唤醒 144 5.3 ashmem驱动程序 145 5.4 pmem驱动程序 148 5.5 alarm驱动程序 149 5.5.1 alarm简析 149 5.5.2 alarm驱动程序的实现 150 5.6 usb gadget驱动程序151 5.7 Android paranoid驱动程序153 5.8 goldfish设备驱动154 5.8.1 framebuffer驱动155 5.8.2 键盘驱动159 5.8.3 实时时钟驱动程序160 5.8.4 tty终端驱动程序161 5.8.5 nandflash驱动程序162 5.8.6 mmc驱动程序162 5.8.7 电池驱动程序162 第6章 msm内核和驱动解析164 6.1 msm基础164 6.1.1 常见msm处理器产品164 6.1.2 snapdragon内核介绍165 6.2 移植msm内核简介166 6.3 移植msm168 6.3.1 makefile文件168 6.3.2 驱动和组件170 6.3.3 设备驱动172 6.3.4 高通特有的组件174 第7章 omap内核和驱动解析177 7.1 omap基础177 7.1.1 omap简析177 7.1.2 常见omap处理器产品177 7.1.3 开发平台178 7.2 omap内核178 7.3 移植omap体系结构180 7.3.1 移植omap平台180 7.3.2 移植omap处理器183 7.4 移植Android专用驱动和组件188 7.5 omap的设备驱动190 第8章 显示系统驱动应用195 8.1 显示系统介绍195 8.1.1 Android的版本195 8.1.2 不同版本的显示系统195 8.2 移植和调试前的准备196 8.2.1 framebuffer驱动程序196 8.2.2 硬件抽象层198 8.3 实现显示系统的驱动程序210 8.3.1 goldfish中的framebuffer驱动程序210 8.3.2 使用gralloc模块的驱动程序214 8.4 msm高通处理器中的显示驱动实现224 8.4.1 msm中的framebuffer驱动程序225 8.4.2 msm中的gralloc驱动程序227 8.5 omap处理器中的显示驱动实现235 第9章 输入系统驱动应用239 9.1 输入系统介绍239 9.1.1 Android输入系统结构元素介绍239 9.1.2 移植Android输入系统时的工作240 9.2 input(输入)驱动241 9.3 模拟器的输入驱动256 9.4 msm高通处理器中的输入驱动实现257 9.4.1 触摸屏驱动257 9.4.2 按键和轨迹球驱动264 9.5 omap处理器平台中的输入驱动实现266 9.5.1 触摸屏驱动267 9.5.2 键盘驱动267 第10章 振动器系统驱动269 10.1 振动器系统结构269 10.1.1 硬件抽象层271 10.1.2 jni框架部分272 10.2 开始移植273 10.2.1 移植振动器驱动程序273 10.2.2 实现硬件抽象层274 10.3 在msm平台实现振动器驱动275 第11章 音频系统驱动279 11.1 音频系统结构279 11.2 分析音频系统的层次280 11.2.1 层次说明280 11.2.2 media库中的audio框架281 11.2.3 本地代码284 11.2.4 jni代码288 11.2.5 java代码289 11.3 移植audio系统的必备技术289 11.3.1 移植audio系统所要做的工作289 11.3.2 分析硬件抽象层290 11.3.3 分析audioflinger中的audio硬件抽象层的实现291 11.4 真正实现audio硬件抽象层298 11.5 msm平台实现audio驱动系统298 11.5.1 实现audio驱动程序298 11.5.2 实现硬件抽象层299 11.6 oss平台实现audio驱动系统304 11.6.1 oss驱动程序介绍304 11.6.2 mixer305 11.7 alsa平台实现audio系统312 11.7.1 注册音频设备和音频驱动312 11.7.2 在Android中使用alsa声卡313 11.7.3 在omap平台移植Android的alsa声卡驱动322 第12章 视频输出系统驱动326 12.1 视频输出系统结构326 12.2 需要移植的部分328 12.3 分析硬件抽象层328 12.3.1 overlay系统硬件抽象层的接口328 12.3.2 实现overlay系统的硬件抽象层331 12.3.3 实现接口332 12.4 实现overlay硬件抽象层333 12.5 在omap平台实现overlay系统335 12.5.1 实现输出视频驱动程序335 12.5.2 实现overlay硬件抽象层337 12.6 系统层调用overlay hal的架构342 12.6.1 调用overlay hal的架构的流程342 12.6.2 s3c6410 Android overlay的测试代码346 第13章 openmax多媒体框架349 13.1 openmax基本层次结构349 13.2 分析openmax框架构成350 13.2.1 openmax总体层次结构350 13.2.2 openmax il层的结构351 13.2.3 Android中的openmax354 13.3 实现openmax il层接口354 13.3.1 openmax il层的接口354 13.3.2 在openmax il层中需要做什么361 13.3.3 研究Android中的openmax适配层361 13.4 在omap平台实现openmax il363 13.4.1 实现文件364 13.4.2 分析ti openmax il的核心365 13.4.3 实现ti openmax il组件实例368 第14章 多媒体插件框架373 14.1 Android多媒体插件373 14.2 需要移植的内容374 14.3 opencore引擎375 14.3.1 opencore层次结构375 14.3.2 opencore代码结构376 14.3.3 opencore编译结构377 14.3.4 opencore oscl381 14.3.5 实现opencore中的openmax部分383 14.3.6 opencore的扩展398 14.4 stagefright引擎404 14.4.1 stagefright代码结构404 14.4.2 stagefright实现openmax接口405 14.4.3 video buffer传输流程409 第15章 传感器系统415 15.1 传感器系统的结构415 15.2 需要移植的内容417 15.2.1 移植驱动程序417 15.2.2 移植硬件抽象层418 15.2.3 实现上层部分419 15.3 在模拟器中实现传感器424 第16章 照相机系统430 16.1 camera系统的结构430 16.2 需要移植的内容433 16.3 移植和调试433 16.3.1 v4l2驱动程序433 16.3.2 硬件抽象层441 16.4 实现camera系统的硬件抽象层446 16.4.1 java程序部分446 16.4.2 camera的java本地调用部分447 16.4.3 camera的本地库libui.so448 16.4.4 camera服务libcameraservice.so449 16.5 msm平台实现camera系统454 16.6 omap平台实现camera系统457 第17章 wi-fi系统、蓝牙系统和gps系统459 17.1 wi-fi系统459 17.1.1 wi-fi系统的结构459 17.1.2 需要移植的内容461 17.1.3 移植和调试461 17.1.4 omap平台实现wi-fi469 17.1.5 配置wi-fi的流程471 17.1.6 具体演练——在Android下实现ethernet473 17.2 蓝牙系统475 17.2.1 蓝牙系统的结构475 17.2.2 需要移植的内容477 17.2.3 具体移植478 17.2.4 msm平台的蓝牙驱动480 17.3 定位系统482 17.3.1 定位系统的结构483 17.3.2 需要移植的内容484 17.3.3 移植和调试484 第18章 电话系统498 18.1 电话系统基础498 18.1.1 电话系统简介498 18.1.2 电话系统结构500 18.2 需要移植的内容501 18.3 移植和调试502 18.3.1 驱动程序502 18.3.2 ril接口504 18.4 电话系统实现流程分析507 18.4.1 初始启动流程507 18.4.2 request流程509 18.4.3 response流程512 第19章 其他系统514 19.1 alarm警报器系统514 19.1.1 alarm系统的结构514 19.1.2 需要移植的内容515 19.1.3 移植和调试516 19.1.4 模拟器环境的具体实现518 19.1.5 msm平台实现alarm518 19.2 lights光系统519 19.2.1 lights光系统的结构520 19.2.2 需要移植的内容521 19.2.3 移植和调试521 19.2.4 msm平台实现光系统523 19.3 battery电池系统524 19.3.1 battery系统的结构524 19.3.2 需要移植的内容526 19.3.3 移植和调试526 19.3.4 在模拟器中实现电池系统529
 本书内容   本书的各个章节及其组织方式如下所示。   第1章“Android系统概述”,概述Android系统方面的内容,包括智能手机平台的发展历程、Android系统的特点、Android的3种开发工作,以及Android的2种不同的开发方式。   第2章“Android系统开发综述”,介绍Android系统开发的综述性内容,包括工具使用、获得代码、编译系统、仿真器运行、SDK使用等。   第3章“Android的Linux内核与驱动程序”,介绍Android内核的特点、Android中使用的专用驱动程序、Android系统可能使用的标准设备驱动。   第4章“Android的底层库和程序”,介绍Android系统的基础程序,以本地程序为主。   第5章“Android的Java虚拟机和Java环境”,这是介于本地和Java层之间的相关内容,主要介绍Android的Java虚拟机Dalvik的基本概念、Android Java程序的环境、JNI的使用方法,以及Java框架的启动流程等。   第6章“Android的GUI系统”,包括Android GUI系统架构、底层的pixelflinger和libui库、Surface系统、Skia和2D图形系统、Android的OpenGL和3D图形系统等内容。   第7章“Android的Audio系统”,主要是音频的输入输出环节,音频系统从驱动程序、本地框架到Java框架都具有内容。   第8章“Android的Video输入输出系统”,介绍Android的Video输入输出系统,包括Overlay系统和Camera系统两个部分,前者只有本地的内容,后者各个层次均有内容。   第9章“Android的多媒体系统”,介绍Android的多媒体系统的核心部分,包括Android中多媒体系统的业务、结构、多媒体系统的核心框架、OpenCore系统结构和使用等内容。   第10章“Android的电话部分”,介绍Android系统在电话方面的功能,Android是智能手机系统,电话是其核心部分。   第11章“Android的连接部分”,主要包括WiFi、蓝牙及定位系统等,这些Android的连接部分也具有从驱动层到Java应用层的结构。   第12章“Android的传感器系统”,传感器系统涉及Android的各个层次,具有完整的结构,相比其他系统,传感器系统的各个层次都比较简单。   第6~12章分模块介绍Android的几个核心系统,主要是本地框架和Java框架方面的内容,兼顾应用程序和驱动层,这是本书的重点。   第13章“Android应用程序概述及框架”,介绍Android应用程序层的基本概念和应用程序框架,这部分内容是Android自下而上的第4个层次,可以基于源代码或者SDK开发,它们之间的差别非常小。   第14章“Android应用程序的主要方面”,介绍Android应用程序层开发的各个方面,基本上是横向内容,包括应用程序的基本控制、各种UI元素的使用、图形API使用3个方面的内容。   第15章“Android应用程序的设计思想”,本章的内容是基于通用的应用程序和GUI程序的通用设计思想,结合Android系统自身的特点,提出一些值得注意的问题和设计方法。   本书读者   本书适应广大的读者群,力求在Android的系统移植、应用程序开发、系统框架改进方面给读者全面的支持。不同的读者在学习本书时,应该使用不同的方法。   Android初级开发者:在本书指引下阅读代码,搭建系统开发环境,对于Android应用程序的开发者,重点关注后3章的内容。   Android中、高级开发者:通过本书的引导,学习系统架构,关注开发要点,并尽量使用手机系统的通用设计思想、软件工程思想、系统工程思想来指导Android系统学习。   嵌入式Linux系统学习者:将Android作为一个集Linux核心和应用层程序于一体的系统进行学习,并可以利用Android的仿真环境运行和调试程序。   读者在学习本书的过程中,应尽量对照本书的框图和手头的Android源代码,这样可以达到事半功倍的效果。本书在重点代码中加上大量的注释,帮助读者阅读,对于非重点的代码,不占用本书的篇幅,读者可以参考开放的源代码。可以采用顺序读和重点读相结合的方式学习本书,顺序读关注系统框架,重点读关注开发要点。   本书作者   本书在编写过程中提炼和综合Android早期开发者的经验、中国大陆Androidin社区的开发成果,以及各位专家的经验和技术,这是本书出版的知识源泉。本书主要由Androidin社区的两名核心技术专家韩超和梁泉领衔规划和编著,韩超统稿。总部设在南加州、专注于Android平台并提供其移动应用开发及解决方案的迈奔无线(mAPPn Inc.)也投入技术和人力参与了本书的工作。   参与本书编写的还有于仕林、张宇、张超等人,赵家维、黄亮、沈桢、徐威特、杨钰等参与了审校工作。   由于时间仓促,可能依然存在一些错误和问题,请读者见谅,欢迎读者讨论和指点。 编辑本段 目 录   第1章 Android系统概述 1   1.1 基础知识 1   1.1.1 Android开发系统的由来 1   1.1.2 移动电话系统开发模式 2   1.1.3 未来移动电话的功能及Android的优势 4   1.2 Android的开发工作 6   1.2.1 Android移植开发 6   1.2.2 Android应用开发 8   1.2.3 Android系统开发 9   1.3 Android的SDK与源代码 10   1.3.1 基于SDK的Android开发 10   1.3.2 基于源代码SDK Android开发 11   第2章 Android系统开发综述 13   2.1 Android的系统架构 13   2.1.1 软件结构 13   2.1.2 Android的工具 16   2.2 Android源代码的开发环境 18   2.2.1 Android源代码的获取和提交 18   2.2.2 Android源代码结构 21   2.2.3 编译 24   2.2.4 系统的运行 25   2.3 Android SDK的开发环境 32   2.3.1 SDK的结构 32   2.3.2 Windows环境SDK开发 33   2.3.3 Linux环境SDK开发 42   第3章 Android的Linux内核与驱动程序 44   3.1 Linux核心与驱动 44   3.2 Android专用驱动 45   3.2.1 Ashmem 45   3.2.2 Binder 45   3.2.3 Logger 46   3.3 Android使用的设备驱动 46   3.3.1 Framebuffer显示驱动 46   3.3.2 Event输入设备驱动 48   3.3.3 v4l2摄像头——视频驱动 50   3.3.4 OSS音频驱动 53   3.3.5 ALSA音频驱动 54   3.3.6 MTD驱动 56   3.3.7 蓝牙驱动 57   3.3.8 Wlan驱动 58   第4章 Android的底层库和程序 60   4.1 底层库和程序的结构 60   4.1.1 本地实现的基本结构 60   4.1.2 增加本地程序和库的方法 61   4.2 标准C/C++库bionic 64   4.3 C语言工具库libcutils 65   4.4 init可执行程序 66   4.5 Shell工具 72   4.6 C++工具库libutils 75   4.6.1 libutils的基本内容 75   4.6.2 Binder 76   4.6.3 libutils中的其他内容 82   4.7 Android的系统进程 85   4.7.1 servicemanager 85   4.7.2 zygote 87   第5章 Android的Java虚拟机和Java环境 88   5.1 Dalvik虚拟机和核心库 88   5.1.1 dex工具库和虚拟机的实现 89   5.1.2 核心库 90   5.1.3 nativehelper库 91   5.2 Android的Java程序环境 91   5.2.1 Java类的层次结构 91   5.2.2 Android Java类的代码 92   5.2.3 Android系统API 92   5.3 JNI的使用 96   5.3.1 JNI的架构和实现方式 97   5.3.2 JNI的实现方式 97   5.3.3 在应用程序中使用JNI 99   5.4 系统服务的Java部分 101   5.4.1 Binder 102   5.4.2 ServiceManager 103   5.4.3 系统进程 103   第6章 Android的GUI系统 106   6.1 Android GUI系统综述 106   6.2 pixelflinger和libui库 108   6.2.1 pixelflinger 108   6.2.2 libui 108   6.2.3 输出/输入与硬件的接口 109   6.3 Surface系统 113   6.3.1 Surface系统本地接口 113   6.3.2 SurfaceFlinger本地代码 115   6.3.3 Surface的JavaJNI代码 119   6.4 Skia和2D图形系统 121   6.4.1 Skia底层库 121   6.4.2 Android图形系统的JNI接口 124   6.4.3 Android的图形包(graphics) 125   6.5 Android的OpenGL系统与3D图形系统 125   6.5.1 OpenGL的本地代码 125   6.5.2 OpenGL的JNI代码 130   6.5.3 OpenGL的Java类 130   第7章 Android的Audio系统 132   7.1 Audio系统综述 132   7.2 Audio系统和上层接口 134   7.2.1 Audio系统的各个层次 134   7.2.2 media库中的Audio框架部分 135   7.2.3 AudioFlinger本地代码 138   7.2.4 Audio系统的JNI代码 140   7.2.5 Audio系统的Java代码 142   7.3 Audio的硬件抽象层 142   7.3.1 Audio硬件抽象层的接口定义 142   7.3.2 AudioFlinger中自带Audio硬件抽象层实现 144   7.3.3 Audio硬件抽象层的真正实现 150   第8章 Android的Video输入输出系统 151   8.1 Video输入输出系统综述 151   8.1.1 Android的Overlay系统结构 152   8.1.2 Android的Camera系统结构 153   8.2 Overlay系统 155   8.2.1 Overlay系统的框架部分定义 156   8.2.2 SurfaceFlinger系统的Overlay部分 158   8.3 Overlay的硬件抽象层 161   8.3.1 Overlay系统硬件抽象层的接口 161   8.3.2 Overlay系统硬件实现框架 164   8.3.3 Overlay系统硬件实现的注意事项 166   8.4 Camera系统与上层接口 169   8.4.1 Camera本地代码框架 169   8.4.2 CameraService 176   8.4.3 Camera的JNI代码 179   8.4.4 Camera的Java代码 182   8.5 Camera的硬件抽象层 182   8.5.1 Camera硬件抽象层的接口定义 182   8.5.2 Camera硬件抽象层的桩实现 184   8.5.3 Camera硬件抽象层的硬件实现 188   第9章 Android的多媒体系统 190   9.1 Android多媒体系统的结构和业务 190   9.1.1 多媒体系统的宏观结构 190   9.1.2 多媒体的各种业务 192   9.2 多媒体系统的各个层次 199   9.2.1 libmedia的框架部分 199   9.2.2 多媒体服务 208   9.2.3 多媒体部分的JNI代码 213   9.2.4 多媒体部分的Java框架代码 215   9.2.5 android.widget.VideoView类 216   9.3 多媒体实现的核心部分OpenCore 216   9.3.1 OpenCore概述 216   9.3.2 OpenCore的层次结构 217   9.3.3 OpenCore的OSCL部分 219   9.3.4 OpenCore的文件格式和编解码部分 221   9.3.5 OpenCore 的Node 222   9.3.6 OpenCore 的功能扩展 223   9.3.7 OpenCore的 Player 226   9.3.8 OpenCore 的Author 236   第10章 Android的电话部分 243   10.1 Android电话部分综述 243   10.2 Modem驱动 243   10.3 本地的RIL代码 245   10.3.1 简介 245   10.3.2 RILD守护进程 246   10.3.3 libril库 247   10.3.4 RIL的实现库Reference RIL 247   10.3.5 Request(请求)流程 248   10.3.6 Response(响应)流程 249   10.3.7 RIL的移植工作 251   10.4 Java框架及应用 251   10.4.1 基本架构 252   10.4.2 呼叫 255   10.4.3 短信 256   10.4.4 数据连接 257   10.4.5 其他框架部分及其他应用 258   第11章 Android的连接部分 259   11.1 WiFi部分 259   11.1.1 WiFi基本架构 259   11.1.2 WiFi本地实现 260   11.1.3 WiFi的JavaJNI 263   11.1.4 Settings中的WiFi设置 265   11.1.5 WiFi工作流程实例 265   11.2 蓝牙部分 267   11.2.1 蓝牙基本架构 268   11.2.2 蓝牙用户空间库bluez 269   11.2.3 bluez适配层 272   11.2.4 蓝牙的JNIJava部分 272   11.3 GPS和定位部分 280   11.3.1 定位系统基本架构 281   11.3.2 定位系统驱动层 281   11.3.3 GPS本地实现 282   11.3.4 GPS JNI实现 283   11.3.5 定位系统Java实现 284   第12章 Android的传感器系统 286   12.1 传感器系统综述 286   12.2 传感器系统层次结构 288   12.2.1 传感器系统的各个层次 288   12.2.2 传感器系统的JNI 288   12.2.3 传感器系统的Java代码 290   12.3 传感器系统的硬件抽象层 291   12.3.1 传感器系统硬件抽象层的接口定义 291   12.3.2 传感器系统硬件抽象层的示例实现 293   12.3.3 传感器系统硬件抽象层的实现要点 296   12.4 Sensor的使用 296
目录: 结构如下,非常详细 第1章 android系统概述 1 1.1 基础知识 1 1.1.1 android开发系统的由来 1 1.1.2 移动电话系统开发模式 2 1.1.3 未来移动电话的功能及android的优势 4 1.2 android的开发工作 6 1.2.1 android移植开发 6 1.2.2 android应用开发 8 1.2.3 android系统开发 9 1.3 android的sdk与源代码 10 1.3.1 基于sdk的android开发 10 1.3.2 基于源代码sdk android开发 11 第2章 android系统开发综述 13 2.1 android的系统架构 13 2.1.1 软件结构 13 2.1.2 android的工具 16 2.2 android源代码的开发环境 18 2.2.1 android源代码的获取和提交 18 2.2.2 android源代码结构 21 2.2.3 编译 24 .2.2.4 系统的运行 25 2.3 android sdk的开发环境 32 2.3.1 sdk的结构 32 2.3.2 windows环境sdk开发 33 2.3.3 linux环境sdk开发 42 第3章 android的linux内核与驱动程序 44 3.1 linux核心与驱动 44 3.2 android专用驱动 45 3.2.1 ashmem 45 3.2.2 binder 45 3.2.3 logger 46 3.3 android使用的设备驱动 46 3.3.1 framebuffer显示驱动 46 3.3.2 event输入设备驱动 48 3.3.3 v4l2摄像头——视频驱动 50 3.3.4 oss音频驱动 53 3.3.5 alsa音频驱动 54 3.3.6 mtd驱动 56 3.3.7 蓝牙驱动 57 3.3.8 wlan驱动 58 第4章 android的底层库和程序 60 4.1 底层库和程序的结构 60 4.1.1 本地实现的基本结构 60 4.1.2 增加本地程序和库的方法 61 4.2 标准c/c++库bionic 64 4.3 c语言工具库libcutils 65 4.4 init可执行程序 66 4.5 shell工具 72 4.6 c++工具库libutils 75 4.6.1 libutils的基本内容 75 4.6.2 binder 76 4.6.3 libutils中的其他内容 82 4.7 android的系统进程 85 4.7.1 servicemanager 85 4.7.2 zygote 87 第5章 android的java虚拟机和java环境 88 5.1 dalvik虚拟机和核心库 88 5.1.1 dex工具库和虚拟机的实现 89 5.1.2 核心库 90 5.1.3 nativehelper库 91 5.2 android的java程序环境 91 5.2.1 java类的层次结构 91 5.2.2 android java类的代码 92 5.2.3 android系统api 92 5.3 jni的使用 96 5.3.1 jni的架构和实现方式 97 5.3.2 jni的实现方式 97 5.3.3 在应用程序中使用jni 99 5.4 系统服务的java部分 101 5.4.1 binder 102 5.4.2 servicemanager 103 5.4.3 系统进程 103 第6章 android的gui系统 106 6.1 android gui系统综述 106 6.2 pixelflinger和libui库 108 6.2.1 pixelflinger 108 6.2.2 libui 108 6.2.3 输出/输入与硬件的接口 109 6.3 surface系统 113 6.3.1 surface系统本地接口 113 6.3.2 surfaceflinger本地代码 115 6.3.3 surface的javajni代码 119 6.4 skia和2d图形系统 121 6.4.1 skia底层库 121 6.4.2 android图形系统的jni接口 124 6.4.3 android的图形包(graphics) 125 6.5 android的opengl系统与3d图形系统 125 6.5.1 opengl的本地代码 125 6.5.2 opengl的jni代码 130 6.5.3 opengl的java类 130 第7章 android的audio系统 132 7.1 audio系统综述 132 7.2 audio系统和上层接口 134 7.2.1 audio系统的各个层次 134 7.2.2 media库

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值