`

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

分享到:
评论

相关推荐

    Android和ReactNative混合开发Demo

    在移动应用开发领域,Android和React Native的混合开发已经成为一种常见的技术栈选择,它结合了原生应用的优势和Web开发的便利性。本教程通过"Android和React Native混合开发Demo",将详细介绍如何在Android应用中...

    Android Native Exception

    ### Android Native Exception详解 #### 一、概览 在Android系统中,应用程序主要基于Java运行时环境进行开发,但也有不少部分是...此外,掌握Native Exception的处理流程也是提高应用稳定性和性能的重要手段之一。

    Android Native Goodies PRO 1.3.0

    在移动应用开发领域,Android以其开源性和丰富的生态系统占据了一席之地。"Android Native Goodies PRO 1.3.0"是一款专为Android开发者设计的工具集,它包含了多种实用功能,旨在提升用户在Android设备上的体验,...

    Unity和安卓交互插件Unity调Android Native Goodies PRO

    由于Unity和Android环境的不同,可能会出现兼容性问题或者运行时错误。学会使用日志输出、调试工具以及Unity的异常处理机制是必要的。 6. 性能优化:由于涉及到跨进程通信,性能可能成为关注点。合理设计API接口,...

    DPF Android Native.Components v2.8.1

    DPF Android Native.Components v2.8.1是这个库的一个重要版本,它包含了一系列关键组件,提升了Android应用的开发体验。 首先,我们关注到"DPF.Android.JSpinner.pas",这代表了JSpinner组件。JSpinner是Android ...

    Node.js-Androidnative层代码内存泄漏问题调试利器

    总的来说,掌握有效的内存泄漏调试方法对于提高Android应用的性能和稳定性至关重要。通过利用Node.js和LeakTracer这样的工具,开发者可以在Native层代码的内存管理上达到更高的精度,避免因内存泄漏导致的问题,从而...

    android native+h5 demo

    在Android开发中,"android native+h5"是一个常见的技术结合,它涉及到原生Android应用与Web(HTML5)内容的交互。本项目“android native+h5 demo”提供了一个实例,展示了如何在Android应用中集成H5页面并实现两者...

    Android Native TCP C语言实现Server创建和Client连接

    在Android平台上,有时候我们需要在原生层(Native)进行TCP通信,这通常涉及到C...理解并掌握这些知识,对于开发高效、安全的Android应用至关重要。在实践过程中,需要不断调试和优化,以适应各种网络环境和性能需求。

    Android驱动开发权威指南.pdf 有书签,很清楚

    Android驱动开发是Android系统开发的重要组成部分,涉及到硬件与软件的交互,对系统的性能、稳定性和效率有着直接影响。本书首先会引导读者理解Android系统的架构,特别是硬件抽象层(HAL)和Linux内核之间的关系,...

    swift-ReactNative开发嘎嘎商城客户端

    我们可以看到“swift-ReactNative开发嘎嘎商城客户端”项目涵盖了移动应用开发的多个核心领域,包括跨平台开发、原生与JavaScript的融合、状态管理、网络通信和UI设计等,这些都是现代移动应用开发中的重要技术栈。

    android native与html中js交互

    在Android开发中,有时我们需要在原生代码(Native)与HTML中的JavaScript之间进行交互,以实现混合式应用的开发。这种交互方式可以让开发者利用Web技术的便利性与原生平台的强大功能,达到最佳的用户体验。本文将...

    android_app_NativeActivity.rar_NativeActivity_android

    标题中的“android_app_NativeActivity.rar_NativeActivity_android”表明我们正在探讨与Android应用开发相关的主题,特别是...理解和掌握NativeActivity的使用,对于进行高性能、低延迟的Android应用开发至关重要。

    ReactNative跨平台开发android和ios

    ReactNative是一种由Facebook开发的开源框架,它允许开发者使用JavaScript和React库来构建原生的Android和iOS应用程序。ReactNative的核心理念是“Learn once, write anywhere”,即学习一次,到处编写,这使得...

    王家林的Android软硬整合设计与框架揭秘: HAL&Framework; &Native; Service &App;&Browser;架构设计与实战开发

    4. Native Service与Binder:通过Camera服务的实例,学员将学习如何创建和管理Native Service,并理解Binder作为Android系统间通信的关键角色,以及其在系统架构中的重要地位。 5. App开发:课程不仅覆盖Android...

    Android-androidjs与native交互相关内容

    在Android应用开发中,JavaScript(通常通过HTML5)与原生代码(Native)的交互是构建混合应用程序的关键技术。这种交互使得开发者可以利用Web技术来实现界面,同时利用Android原生功能,提升用户体验和性能。本文将...

    DPF.Android.Native.Components.v2.8.6XE7控件及修正实例.rar

    总之,《DPF.Android.Native.Components.v2.8.6XE7控件及修正实例》是Delphi开发者在构建Android应用时的重要参考资料,通过深入理解和实践,开发者能够提升Android开发技能,打造出高质量的原生应用。

    ReactNative开发的食谱应用程序

    在这个“React Native开发的食谱应用程序”项目中,我们可以看到如何利用React Native构建一个功能丰富的移动端应用。 1. **React基础**:React Native基于React,这是一个用于构建用户界面的JavaScript库。React...

    Android游戏开发大全.rar

    在Android平台上进行游戏开发是一项富有挑战性和创新性的任务,它涉及到多个技术和工具的融合。"Android游戏开发大全"这个资源可能包含了一份全面的指南,帮助开发者深入理解和掌握Android游戏开发的核心概念和技术...

    《Android系统开发与实践》 PDF

    《Android系统开发与实践》这本书深入探讨了Android操作系统的核心技术和实际应用开发,是广大Android开发者和爱好者提升技术能力的重要参考资料。书中的内容涵盖了从系统架构到应用层开发的多个方面,旨在帮助读者...

Global site tag (gtag.js) - Google Analytics