NDK项目源码地址 :

 

转载出处http://blog.csdn.net/shulianghan/article/details/18964835

.

开发环境介绍 :

eclipse : adt-bundle-windows-x86-20130917

sdk : 版本 2.3.3

ndk : android-ndk-r9c-windows-x86.zip

cygwin : 所需组件 binutils , gcc , gcc-mingw , gdb , make;

javah : jdk6.0自带工具

javap : jdk6.0自带工具

**JNI 总结 : **

Java 调用 C 流程 :

a. 定义 Native 方法 : 在 shuliang.han.ndkparameterpassing.DataProvider.java 类中定义 Native 方法 public native int add(int x, int y);

b. 生成方法签名 : 进入 AndroidProject/bin/classes 目录, 使用 **javah **shuliang.han.ndkparameterpassing.DataProvider 命令, 便生成了头文件, 该头文件引用了 jni.h, 以及定义好了 对应的 Native 方法, 生成 JNIEXPORT jint JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_add (JNIEnv *, jobject, jint, jint);

c. 编写 Android.mk 文件 :

LOCAL_PATH := <span class="katex math inline">(call my-dir)  
  
include</span>(CLEAR_VARS)  
  
LOCAL_MODULE    := hello-jni  
LOCAL_SRC_FILES := hello-jni.c  
  
include $(BUILD_SHARED_LIBRARY)

d. 生成 动态库 so 文件 : 进入 Android.mk 所在目录, 在该目录执行 ndk 下的 ndk-build 命令;

e. Java代码加载动态库 : 在 Java 代码中调用该类的类前面, 在类的一开始, 不在方法中, 加入 static{ System.loadLibrary(“hello”); } ;

一. JNI介绍

1. JNI引入

JNI概念 : Java本地接口,Java Native Interface, 它是一个协议, 该协议用来沟通Java代码和外部的本地C/C++代码, 通过该协议 Java代码可以调用外部的本地代码, 外部的C/C++ 代码可以调用Java代码;

C和Java的侧重 :

C语言 : C语言中最重要的是 函数 function;

Java语言 : Java中最重要的是 JVM, class类, 以及class中的方法;

C与Java如何交流 :

JNI规范 : C语言与Java语言交流需要一个适配器, 中间件, 即 JNI, JNI提供了一种规范;

C语言中调用Java方法 : 可以让我们在C代码中找到Java代码class中的方法, 并且调用该方法;

Java语言中调用C语言方法 : 同时也可以在Java代码中, 将一个C语言的方法映射到Java的某个方法上;

JNI桥梁作用 : JNI提供了一个桥梁, 打通了C语言和Java语言之间的障碍;

JNI中的一些概念 :

native : Java语言中修饰本地方法的修饰符, 被该修饰符修饰的方法没有方法体;

Native方法 : 在Java语言中被native关键字修饰的方法是Native方法;

JNI层 : Java声明Native方法的部分;

JNI函数 : JNIEnv提供的函数, 这些函数在jni.h中进行定义;

JNI方法 : Native方法对应的JNI层实现的 C/C++方法, 即在jni目录中实现的那些C语言代码;

2. Android中的应用程序框架

正常情况下的Android框架 : 最顶层Android的应用程序代码, 上层的应用层应用框架层 主要是Java代码, 中间有一层的Framework框架层代码是 C/C++代码, 通过Framework进行系统调用, 调用底层的库 和linux 内核;

使用JNI时的Android框架 : 绕过Framework提供的调用底层的代码, 直接调用自己写的C代码, 该代码最终会编译成为一个库, 这个库通过JNI提供的一个Stable的ABI 调用linux kernel;ABI是二进制程序接口 application binary interface.

JNI在Android中作用 : JNI可以调用本地代码库(即C/C++代码), 并通过 Dalvik虚拟机 与应用层应用框架层进行交互, Android中JNI代码主要位于应用层 和 应用框架层;

应用层 : 该层是由JNI开发, 主要使用标准JNI编程模型;

应用框架层 : 使用的是Android中自定义的一套JNI编程模型, 该自定义的JNI编程模型弥补了标准JNI编程模型的不足;

Android中JNI源码位置 : 在应用框架层中, 主要的JNI代码位于 framework/base目录下, 这些模块被编译成共享库之后放在 /system/lib 目录下;

NDK与JNI区别 :

NDK: NDK是Google开发的一套开发和编译工具集, 主要用于Android的JNI开发;

JNI : JNI是一套编程接口, 用来实现Java代码与本地的C/C++代码进行交互;

JNI编程步骤:

声明native方法 : 在Java代码中声明 native method()方法;

实现JNI的C/C++方法 : 在JNI层实现Java中声明的native方法, 这里使用javah工具生成带方法签名的头文件, 该JNI层的C/C++代码将被编译成动态库;

加载动态库 : 在Java代码中的静态代码块中加载JNI编译后的动态共享库;

.

3. JNI作用

JNI作用 :

扩展: JNI扩展了JVM能力, 驱动开发, 例如开发一个wifi驱动, 可以将手机设置为无限路由;

高效 : 本地代码效率高, 游戏渲染, 音频视频处理等方面使用JNI调用本地代码, C语言可以灵活操作内存;

复用 : 在文件压缩算法 7zip开源代码库, 机器视觉 openCV开放算法库 等方面可以复用C平台上的代码, 不必在开发一套完整的Java体系, 避免重复发明轮子;

特殊 : 产品的核心技术一般也采用JNI开发, 不易破解;

Java语言执行流程 :

编译字节码 : Java编译器编译 .java源文件, 获得.class 字节码文件;

装载类库 : 使用类装载器装载平台上的Java类库, 并进行字节码验证;

Java虚拟机 : 将字节码加入到JVM中, Java解释器 和 即时编译器 同时处理字节码文件, 将处理后的结果放入运行时系统;

调用JVM所在平台类库 : JVM处理字节码后, 转换成相应平台的操作, 调用本平台底层类库进行相关处理;

Java一次编译到处执行 : JVM在不同的操作系统都有实现, Java可以一次编译到处运行, 字节码文件一旦编译好了, 可以放在任何平台的虚拟机上运行;

.

二. NDK详解

1. 交叉编译库文件

C代码执行 : C代码被编译成库文件之后, 才能执行, 库文件分为动态库静态库 两种;

动态库 : unix环境下**.so 后缀的是动态库, windows环境下.dll 后缀**的是动态库; 动态库可以依赖静态库加载一些可执行的C代码;

静态库 :.a 后缀是静态库的扩展名;

库文件来源 : C代码 进行 编译 链接操作之后, 才会生成库文件, 不同类型的CPU 操作系统 生成的库文件是不一样;

CPU分类 : arm结构, 嵌入式设备处理器; x86结构, pc 服务器处理器; 不同的CPU指令集不同;

交叉编译 :windows x86编译出来的库文件可以在arm平台运行的代码;

交叉编译工具链 : Google提供的 NDK 就是交叉编译工具链, 可以在linux环境下编译出在arn平台下执行的二进制库文件;

NDK作用 : 是Google提供了交叉编译工具链, 能够在linux平台编译出在arm平台下执行的二进制库文件;

NDK版本介绍 : android-ndk-windows 是在windows系统中的cygwin使用的, android-ndk-linux 是在linux下使用的;

2. 部署NDK开发环境

(1) 下载Cygwin安装器

下载地址 : http://cygwin.com/setup-x86.exe , 这是下载器, 可以使用该下载器在线安装, 也可以将cygwin下载到本地之后, 在进行安装;

安装器使用 : Cygwin的下载, 在线安装, 卸载 等操作都有由该安装器进行;

本地文件安装 : 选择安装文件所在的目录, 然后选择所要安装的安装包;

在线安装 : 选择在线安装即可, 然后选择需要的安装包;

卸载 : windows上使用其它软件例如360, 控制面板中是无法卸载Cygwin的, 只能通过安装器来卸载;

(2) 安装Cygin

双击安装器 setup-x86.exe 下一步 :

选择安装方式 :

在线安装 : 直接下载, 然后安装;

下载安装文件 : 将安装文件下载下来, 可以随时安装, 注意安装文件也需要安装器来进行安装;

从本地文件安装 : 即使用下载的安装文件进行安装;

选择Cygwin安装位置 :

选择下载好安装文件位置 : 之前我下了一个完全版的Cygwin, 包括了所有的Cygwin组件, 全部加起来有5.23G, 下载速度很快, 使用网易的镜像, 基本可以全速下载;

选择需要安装Cygwin组件 : 这里我们只需要以下组件 : binutils , gcc , gcc-mingw , gdb , make , 不用下全部的组件;

之后点击下一步等待完成安装即可;

.

安装完之后, 打开bash命令窗口, 可以设置下显示的字体, 使用 make -version 查看是否安装成功 :

(3) Cygwin目录介绍

以下是Cygwin安装目录的情况 : 该安装目录就是所模拟的linux 的根目录;

对应的linux目录 : 这两个目录进行对比发现, 两个目录是一样的, Cygwin的安装目录就是 linux根目录;

cygdrive目录 : 该目录是Cygwin模拟出来的windows目录结构, 进入该目录后, 会发现windows的盘符目录, 通过该目录可以访问windows中的文件;

(4) 下载NDK工具

windows版本NDK:android-ndk-r9c-windows-x86.zip (32位),android-ndk-r9c-windows-x86_64.zip (64位) 该版本是用在windows上的Cygwin下, 不能直接在windows上直接运行;

linux版本NDK :android-ndk-r9c-linux-x86.tar.bz2(32位) , android-ndk-r9c-linux-x86_64.tar.bz2 (64位) , 该版本直接在linux下执行即可;

在这里下载windows版本的NDK, 运行在Cygwin上;

(4) NDK环境介绍

NDK工具的文件结构 :

ndk-build脚本 : NDK build 脚本是 gun-make 的简单封装, gun-make 是编译C语言代码的工具, 该脚本执行的前提是linux环境下必须安装 make 程序;

**NDK安装在Cygwin中** : 将NDK压缩文件拷贝到Cygwin的根目录中, 解压 : android-ndk-r9c 目录就是NDK目录;





**执行以下NDK目录下的 ndk-build 命令** : ./ndk-build ;





**执行结果** :
Android NDK: Could not find application project directory !
Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.
/android-ndk-r9c/build/core/build-local.mk:148: *** Android NDK: Aborting    。 停止。
![](http://img.blog.csdn.net/20140130194048078)</div> 



  &nbsp;



# 三. 开发第一个NDK程序

## 1. 开发NDK程序流程

#### a. **创建Android工程**:



  首选创建一个Android工程, 在这个工程中进行JNI开发;



#### b. **声明native方法** :



  注意方法名使用 native 修饰, 没有方法体 和 参数, eg : public native String helloFromJNI();



#### c. **创建C文件** :



  在工程根目录下创建 jni 目录, 然后创建一个c语言源文件, 在文件中引入 include <jni.h> , C语言方法声明格式 jstring Java_shuliang.han.ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env) , jstring 是 Java语言中的String类型, 方法名格式为 : Java_完整包名类名_方法名();





  &#8212; **JNIEnv参数** : 代表的是Java环境, 通过这个环境可以调用Java里面的方法;





  &#8212; **jobject参数** : 调用C语言方法的对象, thiz对象表示当前的对象, 即调用JNI方法所在的类;



#### d. **编写Android.mk文件** :



  如何写 查看文档, NDK根目录下有一个 documentation.html 文档, 点击该html文件就可以查看文档, 查看 Android.mk File 文档, 下面是该文档给出的 Android.mk示例 :



```

LOCAL_PATH := (call my-dir)

include(CLEAR_VARS)

LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)

    
    

      &#8212; **LOCAL_PATH** : 代表mk文件所在的目录;
    

    
    

      &#8212; **include $(CLEAR_VARS)** : 编译工具函数, 通过该函数可以进行一些初始化操作;
    

    
    

      &#8212; **LOCAL_MODULE** : 编译后的 .so 后缀文件叫什么名字;
    

    
    

      &#8212; **LOCAL_SRC_FILES**: 指定编译的源文件名称;
    

    
    

      &#8212; **include $(BUILD_SHARED_LIBRARY)** : 告诉编译器需要生成动态库;
    

    
    #### e. **NDK编译生成动态库** :
    
    

      进入 cygdrive 找到windows目录下对应的文件, 编译完成之后, 会**自动生成so文件并放在libs目录下**, 之后就可以在Java中调用C语言方法了;
    

    
    #### f. **Java中加载动态库** :
    
    

      在Java类中的静态代码块中使用System.LoadLibrary()方法加载编译好的 .so 动态库;
    

    
    

      **NDK平台版本** : NDK脚本随着 android-sdk 版本不同, 执行的脚本也是不同的, 不同平台会引用不同的头文件, 编译的时候一定注意 sdk 与 ndk 版本要一致;
    

    
    

      **so文件在内存中位置** : apk文件安装到手机上之后, .so动态库文件存在在 data/安装目录/libs 目录下;
    

    
    ## 2. 开发实例
    
    <div>
    </div>
    
    <div>
      **按照上面的步骤进行开发**
    </div>
    
    <div>
    </div>
    
    <div>
    </div>
    
    ### (1) 创建Android工程
    
    <div>
    </div>
    
    <div>
      **Android工程版本** : 创建一个Android工程,**minSdk 为 7 即 android-2.1**, 编译使用的**sdk为 10 即 android-2.3.3** ;
    </div>
    
    <div>
      ```
    &lt;uses-sdk
        android:minSdkVersion="7"
        android:targetSdkVersion="10" /&gt;
</div>

<div>
  **NDK编译原则** : 编译NDK动态库是**按照最小版本进行编译**, 选择编译的平台的时候, 会选择 NDK 7 平台进行编译;
</div>

<div>
</div>

<div>
  ![](http://img.blog.csdn.net/20140130221252312)      ![](http://img.blog.csdn.net/20140130221301250)
</div>

<div>
</div>

### (2) 声明native方法

<div>
</div>

<div>
  **声明native方法, 注意该方法没有方法体 和 参数, 如下** :
</div>

<div>
</div>

<div>
  ```
/*
 * 声明一个native方法
 * 这个方法在Java中是没有实现的, 没有方法体
 * 该方法需要使用C语言编写
 */
public native String helloFromJNI();
      
      

        .
      

      
      

        **作者** : **万境绝尘 **
      

      
      

        **转载请注明出处** : [**http://blog.csdn.net/shulianghan/article/details/18964835**](http://blog.csdn.net/shulianghan/article/details/18964835)
      

      
      

        .
      

    </div>
    
    ### (3) 创建C文件
    
    <div>
    </div>
    
    <div>
      **引入头文件**: 首先要包含头文件 jni.h, 该**头文件位置**定义在 android-ndk-r9c\platforms\android-5\arch-arm\usr\include目录下的 jni.h, 下面是该头文件中定义的一些方法, 包括本项目中使用的 NewString 方法;
    </div>
    
    <div>
      ```
jstring     (*NewString)(JNIEnv*, const jchar*, jsize);
jsize       (*GetStringLength)(JNIEnv*, jstring);
const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);
void        (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);
jstring     (*NewStringUTF)(JNIEnv*, const char*);
jsize       (*GetStringUTFLength)(JNIEnv*, jstring);
</div>

<div>
</div>

<div>
  **调用Java类型** : C中调用Java中的String类型为 jstring;
</div>

<div>
</div>

<div>
  **C语言方法名规则** : Java_完整包名类名_方法名(JNIEnv *env, jobject thiz), 注意完整的类名包名中包名的点要用 _ 代替;
</div>

<div>
</div>

<div>
  **参数介绍** : C语言方法中有两个重要的参数, JNIEnv *env, jobject thiz ;
</div>

<div>
  &#8212; **JNIEnv参数** : 该参数代表Java环境, 通过这个环境可以调用Java中的方法;
</div>

<div>
  &#8212; **jobject参数** : 该参数代表调用jni方法的类, 在这里就是MainActivity;
</div>

<div>
</div>

<div>
  **调用jni.h中的NewStringUTF方法** : 该方法的作用是在C语言中创建一个Java语言中的String类型对象, jni.h中是这样定义的 jstring (*NewStringUTF)(JNIEnv*, const char*), JNIEnv 结构体中包含了 NewStringUTF 函数指针, 通过 JNIEnv 就可以调用这个方法;
</div>

<div>
</div>

<div>
  **C语言文件源码** :
</div>

<div>
  ```

#include <jni.h>

/*

  • 方法名称规定 : Java_完整包名类名_方法名()
  • JNIEnv 指针
  • 参数介绍 :
  • env : 代表Java环境, 通过这个环境可以调用Java中的方法
  • thiz : 代表调用JNI方法的对象, 即MainActivity对象 */ jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv env, jobject thiz) { /
    • 调用 android-ndk-r9c\platforms\android-8\arch-arm\usr\include 中jni.h中的方法
    • jni.h 中定义的方法 jstring (NewStringUTF)(JNIEnv, const char*); */ return (*env)->NewStringUTF(env, “hello world jni”); }
      
      <div>
      </div>
      
      <div>
      </div>
      
      ### (4) 编写Android.mk文件
    </div>
    
    <div>
    </div>
    
    <div>
    </div>
    
    <div>
      **查询NDK文档** : NDK的文档在NDK工具根目录下, 点击 documentation.html 文件, 就可以在浏览器中打开NDK文档;
    </div>
    
    <div>
    </div>
    
    <div>
      **上面的开发流程中详细的介绍了Android.mk 五个参数的详细用处, 这里直接给出源码** :
    </div>
    
    <div>
      ```
LOCAL_PATH := <span class="katex math inline">(call my-dir)

include</span>(CLEAR_VARS)

LOCAL_MODULE    := hello
LOCAL_SRC_FILES := hello.c

include $(BUILD_SHARED_LIBRARY)
  ### (5) 编译NDK动态库
</div>

<div>
</div>

<div>
</div>

<div>
  **进入Cygwin相应目录** : 从Cygwin中的cygdrive 中进入windows的工程jni目录 ;
</div>

<div>
  ![](http://img.blog.csdn.net/20140130223434359)

</div>

<div>
  **编译hello.c文件** : 注意Android.mk文件 与 hello.c 文件在同一目录中;
</div>

<div>
  ![](http://img.blog.csdn.net/20140130224601328)
</div>

<div>
</div>

<div>
  **编译完成后的情况** : 编译完之后 会成成一个obj文件, 在obj文件中会生成 libhello.so, 系统会**自动将该 so后缀文件放在libs目录下**;
</div>

<div>
  ![](http://img.blog.csdn.net/20140130224720812)
</div>

<div>
</div>

<div>
</div>

<div>
</div>

### (6) Java中加载动态库

<div>
</div>

<div>
  **静态代码块中加载** : Java中在静态代码块中加载库文件, 调用 System.loadLibrary(&#8220;hello&#8221;) 方法,**注意 libs中的库文件名称为 libhello.so**,**我们加载的时候 将 lib 去掉**, 只取hello 作为动态库名称, 这是规定的;
</div>

<div>
  ```
//静态代码块加载C语言库文件
static{
	System.loadLibrary("hello");
}
      
      <div>
      </div>
      
      <div>
      </div>
      
      ### (7) 其它源码
    </div>
    
    <div>
    </div>
    
    <div>
      **MainActivity源码** :
    </div>
    
    <div>
      ```
package shuliang.han.ndkhelloworld;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {

	//静态代码块加载C语言库文件
	static{
		System.loadLibrary("hello");
	}
	
	/*
	 * 声明一个native方法
	 * 这个方法在Java中是没有实现的, 没有方法体
	 * 该方法需要使用C语言编写
	 */
	public native String helloFromJNI();
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        System.out.println(helloFromJNI());
    }

    public void onClick(View view) {
    	//点击按钮显示从jni调用得到的字符串信息
    	Toast.makeText(getApplicationContext(), helloFromJNI(), 1).show();
	}
    
}
    **XML布局文件** :</div> 
    
    <div>
      ```

<RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android" xmlns:tools=“http://schemas.android.com/tools" android:layout_width=“match_parent” android:layout_height=“match_parent” android:paddingBottom="@dimen/activity_vertical_margin” android:paddingLeft="@dimen/activity_horizontal_margin” android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" >

&lt;Button
    android:id="@+id/bt"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="onClick"
    android:text="显示JNI返回的字符串" /&gt;

</RelativeLayout>

          
          ### (8) 将源码上传到GitHub中
        </div>
        
        <div>
        </div>
        
        <div>
        </div>
        
        <div>
          在上一篇博客 [http://blog.csdn.net/shulianghan/article/details/18812279](http://blog.csdn.net/shulianghan/article/details/18812279) 中对GitHub用法进行了详解;
        </div>
        
        <div>
        </div>
        
        <div>
          **在GitHub上创建工程** :
        </div>
        
        <div>
        </div>
        
        <div>
          **项目地址**
        </div>
        
        <div>
          &#8212; **HTTP**: https://github.com/han1202012/NDKHelloworld.git
        </div>
        
        <div>
          &#8212; **SSH** : git@github.com:han1202012/NDKHelloworld.git
        </div>
        
        <div>
        </div>
        
        <div>
          **生成的命令** :
 
          
          ```
touch README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin git@github.com:han1202012/NDKHelloworld.git
git push -u origin master
        **打开 Git Bash 命令行窗口** :</div> 
        
        <div>
          &#8212; **从GitHub上克隆项目到本地** : git clone git@github.com:han1202012/NDKHelloworld.git , 注意克隆的时候直接在仓库根目录即可, 不用再创建项目根目录 ;
        </div>
        
        <div>
          ![](http://img.blog.csdn.net/20140130232116609)
        </div>
        
        <div>
          &#8212; **添加文件** : git add ./* , 将目录中所有文件添加;
        </div>
        
        <div>
          ![](http://img.blog.csdn.net/20140130232554281)

查看状态 : git status ;

        <div>
          ![](http://img.blog.csdn.net/20140130232317546)
        </div>
        
        <div>
          &#8212; **提交缓存** : git commit -m &#8216;提交&#8217;;
        </div>
        
        <div>
          ![](http://img.blog.csdn.net/20140130232423812)

提交到远程GitHub仓库 : git push -u origin master ;