`

android的native开发的重要性

 
阅读更多

1=====概要(android的native开发的重要性,那些方面会用到native开发;
  提高某些关键模块的效率;
  使用某个以C/C++等Native语言编写的程序库;
  硬件操作时候,在与别的公司/团队合作的时候,如果别人提供的是c/c++实现的功能;
  由于Native模块的使用,Java代码会丧失其原有的跨平台性和类型安全等特性;
  Java代码与Native代码运行于同一个进程空间内;对于跨进程甚至跨宿主环境的Java与Native间通信的需求,可以考虑采用socket、Web Service等IPC通信机制来实现;

2=====,提供源码与提供第三方库(动态库/静态库)
  A,B,C,三种mk方式

3=====,全源码编译和ndk编译,写mk文件喝写makefile文件;
   在Android中,有两种方式可以调用JNI,一种是Google release的专门针对
Android Native开发的工具包,叫做NDK。去Android网站上下载该工具包后,就可以通过阅读里面的文档来
setup一个新的包含Native代码的工程,创建自己的Android.mk文件,编译等等;另一种是完整的源码编译环
境 ,也就是通过git从官方网站获取完全的Android源代码平台。这个平台中提供有基于make的编译系统。
更多细节请参考这里。不管选择以上两种方法的哪一个,都必须编写自己的Android.mk文件,有关该文件

接下来本应介绍test_getPrintStr。但在此之前,简单介绍Android.mk,也就是编译NDK所需要的
Makefile,从而完成JNI信息链的讲解。Android.mk可以基于模版修改,里面重要的变量包括:
a. LOCAL_C_INCLUDES:包含的头文件。这里需要包含JNI的头文件。
b. LOCAL_SRC_FILES: 包含的源文件。
c. LOCAL_MODULE:当前模块的名称,也就是第一步中我们提到的LIBNAME。注意这个
需要加上lib前缀,但不需要加.so后缀,也就是说应该是libLIBNAME。
d. LOCAL_SHARED_LIBRARIES:当前模块需要依赖的共享库。
e. LOCAL_PRELINK_MODULE:该模块是否被启动就加载。该项设置依具体程序的特性而
定。

4=====,jni方式 c/c++,文件后缀策略;
  load与非load方式

private native String getPrintStr();


    try {System.loadLibrary("LIBNAME" }
catch (UnsatisfiedLinkError ule)
 {Log.e(TAG, "Could not load native library");}
注意JNI会自动补全lib和so给LIBNAME,你只需要提供LIBNAME给loadLibrary就行了。在最后执行的时候,
JNI会先找到这个动态库,然后找里面的OnLoad函数,具体注册流程由OnLoad函数接管


如果把上面的getPrintString函数申明比作原型,那么本地代码中的具体函数定义就应该和该原型匹
配,JNI才能知道具体在哪里执行代码。具体来说,应该有一个对应的Native函数,有和Java中定义
的函数同样的参数列表以及返回值。另外,还需要有某种机制让JNI将两者相互映射,方便参数和
返回值的传递。在老版的JNI中,这是通过丑陋的命名匹配实现的,比如说在Java中定义的函数名
是getPrintStr, 该函数属于package java.come.android.xxx,那么中对应Native代码中的函数名就应该是
Java_com_android_xxx_getPrintStr。这样给开发人员带来了很多不便。可以用javah命令来生成对应
Java code中定义函数的Native code版本header文件,从中得知传统的匹配方法是如何做的。具体过
程如下:
a. 通过SDK的方式编译Java代码。
b. 找到Eclipse的工程目录,进入bin目录下。这里是编译出的java文件所对应的class文件所
在。
c.
假设包括Native函数调用的java文件属于com.android.xxx package,名字叫test.java,那么在
bin下执行javah -jni com.android.xxx.test
执行完后,可以看到一个新生成的header文件,名字为com_android_xxx_test.h。打开后会发现已经
有一个函数申明,函数名为java_com_android_xxx_test_getPrintStr。这个名字就包括了该函数所对应
Java版本所在的包,文件以及名称。这就是JNI传统的确定名字的方法。
值得注意的是,header文件中不仅包含了基于函数名的映射信息,还包含了另一个重要信息,就是
signature。一个函数的signature是一个字符串,描述了这个函数的参数和返回值。其中"()" 中的字符
表示参数,后面的则代表返回值。例如"()V" 就表示void Func(); "(II)V" 表示 void Func(int, int); 数组
则以"["开始,用两个字符表示。
具体的每一个字符的对应关系如下:
字符 Java类型 C类型
V void void
I jint int
Z jboolean boolean
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S
jshort
short
上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾,中间是用"/" 隔开的
包及类名。而其对应的C函数名的参数则为jobject。 一个例外是String类,其对应的类为jstring。举
例:
Ljava/lang/String; String jstring
Ljava/net/Socket; Socket jobject
如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。例如 "(Ljava/lang/String;Landroid/os/
FileUtils$FileStatus;)Z"
这个signature非常重要,是下面要介绍的新版命名匹配方法的关键点之一。所以,即使传统的命名
匹配已经不再使用,javah这一步操作还是必须的,因为可以从中得到Java代码中需要Native执行的
函数的签名,以供后面使用。
3. 在新版(版本号大于1.4)的JNI中,Android提供了另一个机制来解决命名匹配问题,那就是
JNI_OnLoad。正如前面所述,每一次JNI执行Native代码,都是通过调用JNI_OnLoad实现的。下面
的代码是针对本例的OnLoad代码:
/* Returns the JNI version on success, -1 on failure.
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed");
goto bail;
}
assert(env != NULL);
if (!register_Test(env)) {
LOGE("ERROR: Test native registration failed");
goto bail;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
bail:
return result;
}
仔细分析这个函数。首先,OnLoad通过GetEnv函数获取JNI的环境对象,然后通过register_Test来
注册Native函数。register_Test的实现如下:
int register_Test(JNIEnv *env) {
const char* const ClassPathName = "com/android/xxx/test";
return registerNativeMethods(env, ClassPathName, TestMethods,
sizeof(TestMethods) / sizeof(TestMethods[0]));
}
在这里,ClassPathName是Java类的全名,包括package的全名。只是用 “/” 代替 ”.” 。然后我们把
类名以及TestMethods这个参数一同送到registerNativeMethods这个函数中注册。这个函数是基于
JNI_OnLoad的命名匹配方式的重点。
在JNI中,代码编写者通过函数signature名和映射表的配合,来告诉JNI_OnLoad,你要找的函数在
Native代码中是如何定义的(signature),以及在哪定义的(映射表)。关于signature的生成和含
义,在上面已经介绍。而映射表,是Android使用的一种用于映射Java和C/C++函数的数组,这个数
组的类型是JNINativeMethod,定义为:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
其中,第一个变量是Java代码中的函数名称。第二个变量是该函数对应的Native signature。第三
个变量是该函数对应的Native函数的函数指针。例如,在上面register_Test的函数实现中,传给
registerNativeMethods的参数TestMethods就是映射表,定义如下:
static JNINativeMethod TestMethods[] = {
{"getPrintStr", "()Ljava/lang/String", (void*)test_getPrintStr}
};
其中getPrintStr是在Java代码中定义的函数的名称,()Ljava/lang/String是签名,因为该函数无参数
传入,并返回一个String。test_getPrintStr则是我们即将在Native code中定义的函数名称。该映射
表和前面定义的类名ClassPathName一起传入registerNativeMethods:
static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod*
Methods, int numMethods) {
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
LOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
LOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
return JNI_TRUE;
}
在这里,先load目标类,然后注册Native函数,然后返回状态。
可以看出,通过映射表方式,Java code中的函数名不须再和Native code中的函数名呆板对应。只需
要将函数注册进映射表中,Native code的函数编写就有了很大的灵活性。虽说和前一种传统的匹配
方法比,这种方式并没有效率上的改进,因为两者本质上都是从JNI load开始做函数映射。但是这
一种register的方法极大降低了两边的耦合性,所以实际使用中会受欢迎得多。比如说,由于映射表
是一个<名称,函数指针>对照表,在程序执行时,可多次调用registerNativeMethods()函数来更换本
地函数指针,而达到弹性抽换本地函数的目的。


//


Andoird 中使用了一种不同传统Java JNI的方式来定义其native的函数。其中很重要的区别是Andorid使用了一种Java 和 C 函数的映射表数组,并在其中描述了函数的参数和返回值。这个数组的类型是JNINativeMethod,定义如下:

typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;

第一个变量name是Java中函数的名字。

第二个变量signature,用字符串是描述了函数的参数和返回值

第三个变量fnPtr是函数指针,指向C函数。

其中比较难以理解的是第二个参数,例如

"()V"

"(II)V"

"(Ljava/lang/String;Ljava/lang/String;)V"

实际上这些字符是与函数的参数类型一一对应的。

"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();

"(II)V" 表示 void Func(int, int);

具体的每一个字符的对应关系如下

字符 Java类型 C类型

V      void            void
Z       jboolean     boolean
I        jint              int
J       jlong            long
D      jdouble       double
F      jfloat            float
B      jbyte            byte
C      jchar           char
S      jshort          short

数组则以"["开始,用两个字符表示

[I       jintArray      int[]
[F     jfloatArray    float[]
[B     jbyteArray    byte[]
[C    jcharArray    char[]
[S    jshortArray   short[]
[D    jdoubleArray double[]
[J     jlongArray     long[]
[Z    jbooleanArray boolean[]

上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾中间是用"/" 隔开的包及类名。而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring

Ljava/lang/String; String jstring
Ljava/net/Socket; Socket jobject

如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。

例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"







<5=====>,jni多中方法包装策略,static方法,普通方法,参数传递,global机制;
    用于最后测试的test_getPrintStr函数实现如下:
const jstring testStr = env->NewStringUTF("hello, world");
return testStr;


<6>,c调用java,java调用c;

JNI是一个双向的接口:开发者不仅可以通过JNI在Java代码中访问Native模块,还可以在 Native代码中嵌入一个JVM,并通过JNI访问运行于其中的Java模块。可见,JNI担任了一个桥梁的角色,它将JVM与Native模块联系起来,从而实现了Java代码与Native代码的互访;

<7>,多线程中的C调用java,以及很重要的jvm/dvm机制。剖析java和native的实现机制。


<8>,GNU makefile


<9>,架构,分层实现(c/c++ --> jni->java)。


<10>,java的多线程与native多线程的同步与互斥。


<11>,Android/Ndk对c/c++的支持,cpu架构不同时的。
在Android中,仅有以下类库是允许在JNI中使用的:
● libc (C library) headers
● libm (math library) headers
● JNI interface headers
● libz (Zlib compression) headers
● liblog (Android logging) header
● OpenGL ES 1.1 (3D graphics library) headers (since 1.6)
● A Minimal set of headers for C++ support




附:

关于测试时使用Log。调用JNI进行Native Code的开发有两种环境,完整源码环境以及NDK。两种
环境对应的Log输出方式也并不相同,差异则主要体现在需要包含的头文件中。如果是在完整源
码编译环境下,只要include <utils/Log.h>头文件(位于Android-src/system/core/include/cutils)
,就可以使用对应的LOGI、LOGD等方法了,当然LOG_TAG,LOG_NDEBUG等宏值需要自定义。
如果是在NDK环境下编译,则需要include <android/log.h>头文件(位于ndk/android-ndk-r4/
platforms/android-8/arch-arm/usr/include/android/),另外自己定义宏映射,例如:
#include <android/log.h>
#ifndef LOG_TAG
#define LOG_TAG "MY_LOG_TAG"
#endif
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)
另外,在Android.mk文件中对类库的应用在两种环境下也不相同。如果是NDK环境下,需要包括
LOCAL_LDLIBS := -llog
而在完整源码环境下,则需要包括
LOCAL_SHARED_LIBRARIES := libutils libcutils

分享到:
评论

相关推荐

    AndroidBase android 应用开发框架.zip

    开发工具在软件开发生命周期中扮演着至关重要的角色,它们旨在简化和加速从概念设计到产品部署的各个环节。以下是开发工具的主要作用: 代码编写与编辑: 提供集成开发环境(IDE),如Visual Studio、Eclipse、...

    【Android】 Android开发工具之Crash日志打印.zip

    开发工具在软件开发生命周期中扮演着至关重要的角色,它们旨在简化和加速从概念设计到产品部署的各个环节。以下是开发工具的主要作用: 代码编写与编辑: 提供集成开发环境(IDE),如Visual Studio、Eclipse、...

    Android应用开发基本框架。Volley二次封装及常见开发工具包。.zip

    开发工具在软件开发生命周期中扮演着至关重要的角色,它们旨在简化和加速从概念设计到产品部署的各个环节。以下是开发工具的主要作用: 代码编写与编辑: 提供集成开发环境(IDE),如Visual Studio、Eclipse、...

    极简化的Android App开发框架和App内调试工具.zip

    开发工具在软件开发生命周期中扮演着至关重要的角色,它们旨在简化和加速从概念设计到产品部署的各个环节。以下是开发工具的主要作用: 代码编写与编辑: 提供集成开发环境(IDE),如Visual Studio、Eclipse、...

    仅用于Android上开发关于文件上传、下载功能的开发工具包,采用了OkHttp作为http请求处理。.zip

    开发工具在软件开发生命周期中扮演着至关重要的角色,它们旨在简化和加速从概念设计到产品部署的各个环节。以下是开发工具的主要作用: 代码编写与编辑: 提供集成开发环境(IDE),如Visual Studio、Eclipse、...

    android项目 二次开发框架------------最优秀android开发框架的整合,下载下来直接进行二次开发. .zip

    开发工具在软件开发生命周期中扮演着至关重要的角色,它们旨在简化和加速从概念设计到产品部署的各个环节。以下是开发工具的主要作用: 代码编写与编辑: 提供集成开发环境(IDE),如Visual Studio、Eclipse、...

    工程硕士学位论文 基于Android+HTML5的移动Web项目高效开发探究

    1.2.4 Android移动Web项目开发的三种解决方案:Native, Web和Hybrid优缺陷分析 4 1.2.5国内外应用现状 6 1.2.6 研究现状总结 7 1.3研究目标与内容 7 1.3.1多窗口浏览器模式的实现机制 7 1.3.2跨域交互即缓存处理方法...

    深入理解Android 卷1.pdf

    针对性强,注重实际应用开发需求,书中所涵盖的知识点都是Android应用开发者和系统开发者需要重点掌握的。共10章,第1章介绍了阅读本书所需要做的准备工作,主要包括对Android系统架构和源码阅读方法的介绍;第2章...

    ThinkAndroid是简易的、遵循Apache2开源协议发布的Android开发框架.zip

    开发工具在软件开发生命周期中扮演着至关重要的角色,它们旨在简化和加速从概念设计到产品部署的各个环节。以下是开发工具的主要作用: 代码编写与编辑: 提供集成开发环境(IDE),如Visual Studio、Eclipse、...

    深入理解Android++卷1pdf电子书

    针对性强,注重实际应用开发需求,书中所涵盖的知识点都是Android应用开发者和系统开发者需要重点掌握的。 《深入理解Android(卷1)》共10章,第1章介绍了阅读本书所需要做的准备工作,主要包括对Android系统架构和...

    一款Android快速开发框(常用工具箱,控件,刷新,数据适配器).zip

    开发工具在软件开发生命周期中扮演着至关重要的角色,它们旨在简化和加速从概念设计到产品部署的各个环节。以下是开发工具的主要作用: 代码编写与编辑: 提供集成开发环境(IDE),如Visual Studio、Eclipse、...

    深入理解Android:卷I--详细书签版

     结合实际应用开发需求,以情景分析的方式有针对性地对Android的源代码进行了十分详尽的剖析,深刻揭示Android系统的工作原理  机锋网、51CTO、开源中国社区等专业技术网站一致鼎力推荐 内容简介  《深入理解...

    《深入理解Android:卷I》试读本

    针对性强,注重实际应用开发需求,书中所涵盖的知识点都是Android应用开发者和系统开发者需要重点掌握的。 全书共10章,第1章介绍了阅读本书所需要做的准备工作,主要包括对Android系统架构和源码阅读方法的介绍;...

    深入理解Android卷1

    针对性强,注重实际应用开发需求,书中所涵盖的知识点都是Android应用开发者和系统开发者需要重点掌握的。 全书共10章,第1章介绍了阅读本书所需要做的准备工作,主要包括对Android系统架构和源码阅读方法的介绍;第...

    Android library 工具类、日常开发整理.zip

    开发工具在软件开发生命周期中扮演着至关重要的角色,它们旨在简化和加速从概念设计到产品部署的各个环节。以下是开发工具的主要作用: 代码编写与编辑: 提供集成开发环境(IDE),如Visual Studio、Eclipse、...

    深入理解Android:卷2

    针对性强,注重实际应用开发需求,书中所涵盖的知识点都是Android应用开发者和系统开发者需要重点掌握的。 全书共10章,第1章介绍了阅读本书所需要做的准备工作,主要包括对Android系统架构和源码阅读方法的介绍;第...

    深入理解Android 卷I

    针对性强,注重实际应用开发需求,书中所涵盖的知识点都是android应用开发者和系统开发者需要重点掌握的。  全书共10章,第1章介绍了阅读本书所需要做的准备工作,主要包括对android系统架构和源码阅读方法的介绍;...

    Android开发常用工具类,让程序猿有更多时间看电影,撩妹纸。.zip

    开发工具在软件开发生命周期中扮演着至关重要的角色,它们旨在简化和加速从概念设计到产品部署的各个环节。以下是开发工具的主要作用: 代码编写与编辑: 提供集成开发环境(IDE),如Visual Studio、Eclipse、...

Global site tag (gtag.js) - Google Analytics