ndk开发

来源:互联网 发布:资料员用的软件 编辑:程序博客网 时间:2024/06/13 12:44

NDk的用处:1,核心代码保护,写入由于apk的Java层代码很容易被反编译,而C/C++库反汇难度较大。

NDK开发常用于-驱动开发、无线热点共享、数学运算、实时渲染的游戏、音视频处理、文件压缩、人脸识别、图片处理等。 



1,首先是so文件的生产

      创建Native方法

public class NdkJniUtils {    static {        System.loadLibrary("native-lib");    }    //获取C中隐藏的AppKey    public native String getStringFromNative(); //native方法}
 2,build->make project生产class文件

3,使用android studio自带的Terminal进入cd app/src/main    然后执行javah -d jni -classpath {sdk位置}\platforms\android-22\android.jar;..\..\build\intermediates\classes\debug {类的package.类名}   示例如下:

javah -d jni -classpath D:\android_studio_new\sdk\platforms\android-22\android.jar;..\..\build\intermediates\classes\debug com.xcm91.relation.ndkapp.NdkJniUtils

在ndk会自动生成一个.h文件

4,创建.c文件引用.h文件

#include "com_xcm91_relation_ndkapp_NdkJniUtils.h"JNIEXPORT jstring JNICALL Java_com_xcm91_relation_ndkapp_NdkJniUtils_getStringFromNative(JNIEnv *env, jclass obj){    char buf[] = "HEllO JNI";    return (*env)->NewStringUTF(env,buf);}
5,build->rebuile project

   在app\build\intermediates\ndk\debug\lib\中生产so文件







3.1 System.loadLibrary

java在java.lang.System包提供了两个静态方法用于加载共享库(一种含原生语言实现的可供android程序调用的库),分别是load,loadLibrary两个方法,由于我们在程序启动的时候就需要加载共享库,因此放在静态代码块中加载。这两个方法的参数是共享库名称。注意共享库为了跨平台使用,它的文件名称会包含一些前缀,而共享库文件的后缀是so。比如我们加载cppUtils共享库,其实他的全名是:libcppUtils.so。


3.2 native标签

native标签用于告诉java编辑器,它的方法是由原生语言实现的,因此不需要去实现它。native方法用分号结束。即如果你希望一个方法用原生语言实现,那么你就给它声明为native方法。

 


3.3 生成原生语言头文件

由于原生语言头文件需要根据字节码文件来进行分析,所以,在生成头文件之前,我们必须对项目进行build。之后打开我们项目的bin/classes的文件夹,笔者的文件夹如下图:

 

\

 

接着我们就要针对CppUtils.class进行分析生成头文件,在我们对编写原生语言头文件的时候,最好借助工具生成,而不是手写,这样出错的概率才会更低,否则很容易发生jni桥无法将java函数与原生方法联系起来的错误。生成头文件的方法,就是使用命令行工具。比如笔者这里就是,先进入自己项目要分析的java文件的目录下,然后生成头文件。生成头文件的命令如下:

javah -classpath bin/classes com.example.jnibolg.CppUtils

 

完整的操作过程看下图:

 

\

 

 

然后回到eclipse中,刷新下我们的项目,我们会发现多了一个以h结尾的文件,这个就是机器生成的头文件。

 

\

 

 

关于原声函数的实现以及祥光头文件,我们需要放在jni文件夹中,因此,我们接下来需要在项目中建立jni文件夹,并将相关文件放进去。

 

\

 

 

这样我们的原生文件就生成了。

 

 

3.4 分析头文件

接下来我们需要分析头文件的内容,有助于帮助我们了解整个实现过程。首先我们看头文件的代码:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jnibolg_CppUtils */
 
#ifndef _Included_com_example_jnibolg_CppUtils
#define _Included_com_example_jnibolg_CppUtils
#ifdef __cplusplus
extern"C"{
#endif
/*
 * Class:     com_example_jnibolg_CppUtils
 * Method:    getStringFromCPP
 * Signature: ()Ljava/lang/String;
 */<preclass="cpp"name="code">JNIEXPORT jstring JNICALL Java_com_example_jnibolg_CppUtils_getStringFromCPP
  (JNIEnv *, jobject);</pre>
<br>
#ifdef __cplusplus}#endif#endif
<preclass="brush:java;"></pre>
<p> </p>
首先我们可以看到jni.h头文件被包含了,这个头文件包含了jni机制为了实现从java对象到原生语言的映射的规则。因此,我们一切java调用原生函数,或者原声函数调用java,都必须通过它来实现。</jni.h>

 

其次我们关注这个函数声明:

 

 

?
1
2
JNIEXPORT jstring JNICALL Java_com_example_jnibolg_CppUtils_getStringFromCPP
  (JNIEnv *, jobject);

这个函数声明说明了,它实现的是jnibolg包下的CppUtils类的getStringFromCPP方法,返回的是jstring类型,这是一个jni类型,映射到java的string类型。这里不详细解析jni类型映射,以免变得复杂,主要以实现一个例子了解整个流程为主。

 

JNIEnv 是一个指针,指向jni对象。通过它,就可以调用jni.h头文件包含的所有函数。即,它就是一个指向jni对象的指针。

 

jobject表示当前函数所实现方法所属的java对象,这里指的是CppUtils的一个实例。

 

 

有了头文件,那么接下来,我们需要用到NDK了,这就需要获取NDK的支持。

 

 

 

3.6 分析mk文件的内容

 

我们打开Android.mk文件,内容如下:

 

?
1
2
3
4
5
6
7
8
LOCAL_PATH := $(call my-dir)
 
include $(CLEAR_VARS)
 
LOCAL_MODULE    := cppUtils
LOCAL_SRC_FILES := cppUtils.cpp
 
include $(BUILD_SHARED_LIBRARY)

 

LOCAL_PATH,这是一个用于定位源文件的宏,在Android.mk文件中,它必须是第一个变量。

include $(CLEAR_VARS),作用是清除命名冲突,因为Android构建系统在单次执行中会构建多个文件和模块,为了避免LOCAL_模式的变量名冲突,必须包含这条指令。

LOCAL_MODULE,指的是生成的共享库的名称,为了适应不同的架构,生成的共享库会含有lib前缀。

LOCAL_SRC_FILES,指的是生成共享库的源文件,多个源文件之间用空格隔开。

include $(BUILD_SHARED_LIBRARY)指令表示生成一个共享库。

 

关于共享库的生成,可以有更复杂的组织,比如多个共享库依赖某个静态库.....这里不详细讲解,只为了让大家理解整个流程。我们要知道的就是,基本的生成流程都是按照上面这几条指令顺序来的。


3.7实现原生代码

 

我们打开cppUtils.cpp文件,会发现是空的。如果包含了jni.h头文件指令,我们把他删掉,因为我们即将要实现的头文件已经包含了jni,h头文件,所以我们的源文件无需再次包含。

 

接着我们将头文件需要实现的函数声明复制过来,为了避免出错,强烈建议复制过来。然后修改成下面这个样子。

 

?
1
2
3
4
5
6
7
#include"com_example_jniblog_CppUtils.h"
 
JNIEXPORT jstring JNICALL Java_com_example_jniblog_CppUtils_getStringFromCpp
  (JNIEnv * env, jobject jthis)
{
   returnenv->NewStringUTF("来自C++");
}

 

3.8调用native函数

做完了上面这些,就可以函数调用了。调用和正常的java调用没有差别的。比如这里就是:

 

?
1
2
3
4
5
6
7
8
9
10
privateTextView text;
    CppUtils cppUtils = newCppUtils();
 
    @Override
    protectedvoid onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        text = (TextView) findViewById(R.id.text);
        text.setText("从C++获取字符串:"+ cppUtils.getStringFromCpp());
    }