一、NDK产生的背景

  Android平台从诞生起,就已经支持C、C++开发。众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第三方应用都必须使用Java语言。但这并不等同于“第三方应用只能使用Java”。在Android SDK首次发布时,Google就宣称其虚拟机Dalvik支持JNI编程方式,也就是第三方应用完全可以通过JNI调用自己的C动态库,即在Android平台上,“Java+C”的编程方式是一直都可以实现的。

  不过,Google也表示,使用原生SDK编程相比Dalvik虚拟机也有一些劣势,Android SDK文档里,找不到任何JNI方面的帮助。即使第三方应用开发者使用JNI完成了自己的C动态链接库(so)开发,但是so如何和应用程序一起打包成apk并发布?这里面也存在技术障碍。比如程序更加复杂,兼容性难以保障,无法访问Framework API,Debug难度更大等。开发者需要自行斟酌使用。

  于是NDK就应运而生了。NDK全称是Native Development Kit。

  NDK的发布,使“Java+C”的开发方式终于转正,成为官方支持的开发方式。NDK将是Android平台支持C开发的开端。

二、为什么使用NDK

  1.代码的保护。由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。

  2.可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。

  3.提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。

  4.便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。

三、NDK简介

1.NDK是一系列工具的集合

NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。

NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。

NDK可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。

2.NDK提供了一份稳定、功能有限的API头文件声明

Google明确声明该API是稳定的,在后续所有版本中都稳定支持当前发布的API。从该版本的NDK中看出,这些API支持的功能非常有限,包含有:C标准库(libc)、标准数学库(libm)、压缩库(libz)、Log库(liblog)。

四、NDK开发环境的搭建

1.下载安装Android NDK

  地址:http://developer.android.com/sdk/ndk/index.html

2.用 NDK 来编译程序

  a.现在我们用安装好的 NDK 来编译一个简单的程序吧,我们选择 ndk 自带的例子 hello-jni ,我的位于E:\android-ndk-r5\samples\hello-jni( 根据你具体的安装位置而定 ) ,

  b.运行 cygwin ,输入命令 cd /cygdrive/e/android-ndk-r5/samples/hello-jni ,进入到 E:\android-ndk-r5\samples\hello-jni 目录。

  c.输入 $NDK/ndk-build ,执行成功后,它会自动生成一个 libs 目录,把编译生成的 .so 文件放在里面。 ($NDK是调用我们之前配置好的环境变量, ndk-build 是调用 ndk 的编译程序 )

  d.此时去 hello-jni 的 libs 目录下看有没有生成的 .so 文件,如果有,你的 ndk 就运行正常啦!

3.在 eclipse 中集成 c/c++ 开发环境

  a.装 Eclipse 的 C/C++ 环境插件: CDT ,这里选择在线安装。  首先登录 http://www.eclipse.org/cdt/downloads.php ,找到对应你 Eclipse 版本的 CDT 插件 的在线安装地址。

  b.然后点 Help 菜单,找到 Install New Software 菜单

  c.点击 Add 按钮,把取的地址填进去,出来插件列表后,选 Select All ,然后选择下一步即可完成安装。

  d.安装完成后,在 eclispe 中右击新建一个项目,如果出现了 c/c++ 项目,则表明你的 CDT 插件安装成功啦!

4.配置 C/C++ 的编译器

  a.打开 eclipse ,导入ndk 自带的hello-jni 例子,右键单击项目名称,点击 Properties ,弹出配置界面,之后再点击 Builders ,弹出项目的编译工具列表,之后点击 New,新添加一个编译器,点击后出现添加界面,选择 Program ,点击 OK。

  b.出现了添加界面,首先给编译配置起个名字,如: C_Builder,设置 Location 为 < 你 cygwin 安装路径 >\bin\bash.exe 程序,例:E:\cygwin\bin\bash.exe ,设置Working Directory为<你 cygwin 安装路径 >\bin 目录,例如: E:\cygwin\bin,设置 Arguments 为 --login -c "cd /cygdrive/e/android-ndk-r5/samples/hello-jni && $NDK /ndk-build"

  上面的配置中 /cygdrive/e/android-ndk-r5/samples/hello-jni 是你当前要编译的程序的目录, $NDK 是之前配置  的 ndk 的环境变量,这两个根据你具体的安装目录进行配置,其他的不用变, Arguments 这串参数实际是  给 bash.exe 命令行程序传参数,进入要编译的程序目录,然后运行 ndk-build 编译程序

  c.接着切换到 Refresh 选项卡,给 Refresh resources upon completion 打上钩

  d.然后切换到 Build Options 选项卡,勾选上最后三项

  e.之后点击 Specify Resources 按钮,选择资源目录,勾选你的项目目录即可

  f.最后点击 Finish,点击 OK 一路把刚才的配置都保存下来,注意:如果你配置的编译器在其它编译器下边,记得一定要点 Up 按钮,把它排到第一位,否则 C 代码的编译晚于Java代码的编译,会造成你的 C 代码要编译两次才能看到最新的修改。

  g.编译配置也配置完成啦,现在来测试一下是否可以自动编译呢,打开项目 jni 目录里的 hello-jni.c 文件把提示 Hello from JNI! 改成其他的文字:如: Hello , My name is alex. ,然后再模 拟器中运行你的程序,如果模拟器中显示了你最新修改的文字,那么 Congratulations !你已经全部配置成功啦!

五、开发自己的NDK程序

  入门的最好办法就是学习Android自带的例子, 这里就通过学习Android的NDK自带的demo程序:hello-jni来达到这个目的。

1、 开发环境的搭建

  1)android的NDK开发需要在linux下进行: 因为需要把C/C++编写的代码生成能在arm上运行的.so文件,这就需要用到交叉编译环境,而交叉编译需要在linux系统下才能完成。

  2)安装android-ndk开发包,这个开发包可以在google android 官网下载: 通过这个开发包的工具才能将android jni 的C/C++的代码编译成库

  3)android应用程序开发环境: 包括eclipse、java、 android sdk、 adt等。

  如何下载和安装android-ndk我这里就不啰嗦了,安装完之后,需要将android-ndk的路劲加到环境变量PATH中:

    sudo gedit /etc/environment

  在environment的PATH环境变量中添加你的android-ndk的安装路劲,然后再让这个更改的环境变量立即生效:

     source  /etc/environment

  经过了上述步骤,在命令行下敲:

    ndk-bulid

  弹出如下的错误,而不是说ndk-build not found,就说明ndk环境已经安装成功了。

    Android NDK: Could not find application project directory !    
    Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.    
    /home/braincol/workspace/android/android-ndk-r5/build/core/build-local.mk:85: *** Android NDK: Aborting    .  Stop.

2.代码的编写

  1)首先是写java代码

  建立一个Android应用工程HelloJni,创建HelloJni.java文件:

  HelloJni.java :

import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle; public class HelloJni extends Activity
{
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState); TextView tv = new TextView(this);
tv.setText( stringFromJNI() );
setContentView(tv);
} /* A native method that is implemented by the 'hello-jni' native library, which is packaged with this application. */
public native String stringFromJNI(); public native String unimplementedStringFromJNI(); /* this is used to load the 'hello-jni' library on application startup. The library has already been unpacked into /data/data/com.example.HelloJni/lib/libhello-jni.so at installation time by the package manager. */
static {
System.loadLibrary("hello-jni");
}
}

  这段代码很简单,注释也很清晰,这里只提两点::

static{
System.loadLibrary("hello-jni");
}

  表明程序开始运行的时候会加载hello-jni, static区声明的代码会先于onCreate方法执行。如果你的程序中有多个类,而且如果HelloJni这个类不是你应用程序的入口,那么hello-jni(完整的名字是libhello-jni.so)这个库会在第一次使用HelloJni这个类的时候加载。

public native String stringFromJNI();
public native String unimplementedStringFromJNI();

  可以看到这两个方法的声明中有 native 关键字, 这个关键字表示这两个方法是本地方法,也就是说这两个方法是通过本地代码(C/C++)实现的,在java代码中仅仅是声明。

  用eclipse编译该工程,生成相应的.class文件,这步必须在下一步之前完成,因为生成.h文件需要用到相应的.class文件。

2)编写相应的C/C++代码

  刚开始学的时候,有个问题会让人很困惑,相应的C/C++代码如何编写,函数名如何定义? 这里讲一个方法,利用javah这个工具生成相应的.h文件,然后根据这个.h文件编写相应的C/C++代码。

  a. 生成相应.h文件:

  就拿我这的环境来说,首先在终端下进入刚刚建立的HelloJni工程的目录:

braincol@ubuntu:~$ cd workspace/android/NDK/hello-jni/

  ls查看工程文件  

braincol@ubuntu:~/workspace/android/NDK/hello-jni$ ls
AndroidManifest.xml  assets  bin  default.properties  gen  res  src

  可以看到目前仅仅有几个标准的android应用程序的文件(夹)。

  首先我们在工程目录下建立一个jni文件夹:

braincol@ubuntu:~/workspace/android/NDK/hello-jni$ mkdir jni
braincol@ubuntu:~/workspace/android/NDK/hello-jni$ ls
AndroidManifest.xml  assets  bin  default.properties  gen  jni  res  src

  cd /src下面就可以生成相应的.h文件了:

braincol@ubuntu:~/workspace/android/NDK/hello-jni$ javah com.example.hellojni.HelloJni 

  com.example.hellojni.HelloJni 则是完整类名

  这一步的成功要建立在已经在 bin/com/example/hellojni/  目录下生成了 HelloJni.class的基础之上。现在可以看到jni目录下多了个.h文件:

braincol@ubuntu:~/workspace/android/NDK/hello-jni$ cd jni/
braincol@ubuntu:~/workspace/android/NDK/hello-jni/jni$ ls
com_example_hellojni_HelloJni.h

  我们来看看com_example_hellojni_HelloJni.h的内容:

  com_example_hellojni_HelloJni.h :

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class com_example_hellojni_HelloJni */

#ifndef _Included_com_example_hellojni_HelloJni

#define _Included_com_example_hellojni_HelloJni

#ifdef __cplusplus

extern "C" {

#endif

/*

 * Class:     com_example_hellojni_HelloJni

 * Method:    stringFromJNI

 * Signature: ()Ljava/lang/String;

 */

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI

  (JNIEnv *, jobject);

/*

 * Class:     com_example_hellojni_HelloJni

 * Method:    unimplementedStringFromJNI

 * Signature: ()Ljava/lang/String;

 */

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI

  (JNIEnv *, jobject);

#ifdef __cplusplus

}

#endif

#endif

  上面代码中的JNIEXPORT 和 JNICALL 是jni的宏,在android的jni中不需要,当然写上去也不会有错。从上面的源码中可以看出这个函数名那是相当的长啊。。。。 不过还是很有规律的, 完全按照:java_pacakege_class_mathod 形式来命名。

  也就是说:

  Hello.java中 stringFromJNI() 方法对应于 C/C++中的 Java_com_example_hellojni_HelloJni_stringFromJNI() 方法

  HelloJni.java中的 unimplementedStringFromJNI() 方法对应于 C/C++中的 Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI() 方法

  注意下其中的注释:

  Signature: ()Ljava/lang/String;

  ()Ljava/lang/String;()表示函数的参数为空(这里为空是指除了JNIEnv *, jobject 这两个参数之外没有其他参数,JNIEnv*, jobject是所有jni函数必有的两个参数,分别表示jni环境和对应的java类(或对象)本身),Ljava/lang/String; 表示函数的返回值是java的String对象。

b. 编写相应的.c文件:

  hello-jni.c :

#include <string.h>
#include <jni.h> /* This is a trivial JNI example where we use a native method * to return a new VM String. See the corresponding Java source * file located at: * apps/samples/hello-jni/project/src/com/example/HelloJni/HelloJni.java */ jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz )
{
return (*env)->NewStringUTF(env, "Hello from JNI !");
}

  这里只是实现了Java_com_example_hellojni_HelloJni_stringFromJNI方法,而 Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI 方法并没有实现,因为在HelloJni.java中只调用了stringFromJNI()方法,所以unimplementedStringFromJNI()方法没有实现也没关系,不过建议最好还是把所有java中定义的本地方法都实现了,写个空函数也行啊。。。有总比没有好。

  Java_com_example_hellojni_HelloJni_stringFromJNI() 函数只是简单的返回了一个内容为 "Hello from JNI !" 的jstring对象(对应于java中的String对象)。hello-jni.c文件已经编写好了,现在可以把com_example_hellojni_HelloJni.h文件给删了,当然留着也行,只是我还是习惯把不需要的文件给清理干净了。

3)编译hello-jni.c 生成相应的库

a 编写Android.mk文件

  在jni目录下(即hello-jni.c 同级目录下)新建一个Android.mk文件,Android.mk 文件是Android 的 makefile文件,内容如下:

# Copyright (C) 2009 The Android Open Source Project

#

# Licensed under the Apache License, Version 2.0 (the "License");

# you may not use this file except in compliance with the License.

# You may obtain a copy of the License at

#

#      http://www.apache.org/licenses/LICENSE-2.0

#

# Unless required by applicable law or agreed to in writing, software

# distributed under the License is distributed on an "AS IS" BASIS,

# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

# See the License for the specific language governing permissions and

# limitations under the License.

#

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello-jni

LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)

  这个Androd.mk文件很短,下面我们来逐行解释下:

    LOCAL_PATH := $(call my-dir)

  一个Android.mk 文件首先必须定义好LOCAL_PATH变量。它用于在开发树中查找源文件。在这个例子中,宏函数’my-dir’, 由编译系统提供,用于返回当前路径(即包含Android.mk file文件的目录)。

    include $( CLEAR_VARS)

  CLEAR_VARS由编译系统提供,指定让GNU MAKEFILE为你清除许多LOCAL_XXX变量(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, 等等...), 除LOCAL_PATH 。这是必要的,因为所有的编译控制文件都在同一个GNU MAKE执行环境中,所有的变量都是全局的。

    LOCAL_MODULE := hello-jni

  编译的目标对象,LOCAL_MODULE变量必须定义,以标识你在Android.mk文件中描述的每个模块。名称必须是唯一的,而且不包含任何空格。

注意:编译系统会自动产生合适的前缀和后缀,换句话说,一个被命名为'hello-jni'的共享库模块,将会生成'libhello-jni.so'文件。

  重要注意事项:如果你把库命名为‘libhello-jni’,编译系统将不会添加任何的lib前缀,也会生成 'libhello-jni.so',这是为了支持来源于Android平台的源代码的Android.mk文件,如果你确实需要这么做的话。

    LOCAL_SRC_FILES := hello-jni.c

  LOCAL_SRC_FILES变量必须包含将要编译打包进模块中的C或C++源代码文件。注意,你不用在这里列出头文件和包含文件,因为编译系统将会自动为你找出依赖型的文件;仅仅列出直接传递给编译器的源代码文件就好。

  注意,默认的C++源码文件的扩展名是’.cpp’. 指定一个不同的扩展名也是可能的,只要定义LOCAL_DEFAULT_CPP_EXTENSION变量,不要忘记开始的小圆点(也就是’.cxx’,而不是’cxx’)

    include $(BUILD_SHARED_LIBRARY)

  BUILD_SHARED_LIBRARY表示编译生成共享库,是编译系统提供的变量,指向一个GNU Makefile脚本,负责收集自从上次调用'include $(CLEAR_VARS)'以来,定义在LOCAL_XXX变量中的所有信息,并且决定编译什么,如何正确地去做。还有 BUILD_STATIC_LIBRARY变量表示生成静态库:lib$(LOCAL_MODULE).a, BUILD_EXECUTABLE 表示生成可执行文件。

b. 生成.so共享库文件

  Andro文件已经编写好了,现在可以用android NDK开发包中的 ndk-build脚本生成对应的.so共享库了,方法如下:

    braincol@ubuntu:~/workspace/android/NDK/hello-jni/jni$ cd .. 
    braincol@ubuntu:~/workspace/android/NDK/hello-jni$ ls 
    AndroidManifest.xml  assets  bin  default.properties  gen  jni  libs  obj  res  src 
    braincol@ubuntu:~/workspace/android/NDK/hello-jni$ ndk-build 
    Gdbserver      : [arm-linux-androideabi-4.4.3] libs/armeabi/gdbserver 
    Gdbsetup       : libs/armeabi/gdb.setup 
    Install        : libhello-jni.so => libs/armeabi/libhello-jni.so

  可以看到已经正确的生成了libhello-jni.so共享库了, 我们去 libs/armeabi/ 目录下看看:

    braincol@ubuntu:~/workspace/android/NDK/hello-jni$ cd libs/ 
    braincol@ubuntu:~/workspace/android/NDK/hello-jni/libs$ ls 
    armeabi 
    braincol@ubuntu:~/workspace/android/NDK/hello-jni/libs$ cd armeabi/ 
    braincol@ubuntu:~/workspace/android/NDK/hello-jni/libs/armeabi$ ls 
    gdbserver  gdb.setup  libhello-jni.so

4)在eclipse重新编译HelloJni工程,生成apk

  eclipse中刷新下HelloJni工程,重新编译生成apk,libhello-jni.so共享库会一起打包在apk文件内。在模拟器中看看运行结果。

开发过程可能遇到的问题以及解决方案

1.使用javah生成头文件常见的问题:

1>

Exception in thread "main" java.lang.IllegalArgumentException: Not a valid class name: .

at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:129)

at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:107)

at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:64)

at com.sun.tools.javah.JavahTask.run(JavahTask.java:503)

at com.sun.tools.javah.JavahTask.run(JavahTask.java:329)

at com.sun.tools.javah.Main.main(Main.java:46)

2>

错误: 无法访问android.app.Activity

找不到android.app.Activity的类文件

3.

错误: 找不到 'JNI' 的类文件。

上述的三个问题可以归为一个问题,命令和路径不对引起的。

路径:cd 工程绝对路径/bin/classes

命令:

javah -classpath . 包名.类名(不能以class结尾)

最新文章

  1. 转载一些Android性能优化建议
  2. HTTPS与强制门户
  3. ORACLE数据库删除表中记录报record is locked by another user
  4. EF Code First 导航属性 与外键
  5. 【面试题】Google of Greater China Test for New Grads of 2014总结
  6. VISIO 2007 修改形状默认字体 自定义模具
  7. JNI编程(二) —— 让C++和Java相互调用(1)
  8. cordova crosswalk android 7.0 问题
  9. angular指令笔记(一):ng-options
  10. 使用maven将项目打成jar包
  11. Kruskal模板
  12. p211有界自共轭算子T是实数集合的子集
  13. WebGL编程指南高级技术篇(常见需求的处理)
  14. 3.1 shell简介、变量、运算符
  15. Spring整合MyBatis(五)MapperScannerConfigurer
  16. 解决 Ubuntu Software (Software Center) Crash 问题
  17. while(cin&gt;&gt;word)时的结束方法
  18. Android Studio中由于gradle插件版本和gradle版本对应关系导致的编译失败的问题
  19. ECharts模块化使用5分钟上手
  20. PokeCats开发者日志(十四)——终章

热门文章

  1. 在cmd中获取ip地址和主机名
  2. Java之fianl修饰符
  3. 调用iframe中父页面/子页面中的JavaScript方法
  4. UVa 101 - The Blocks Problem(积木问题,指令操作)
  5. 在阿里云主机的Debian操作系统上安装Docker
  6. win+Nginx+php+mysql 环境配置
  7. python yield
  8. TreeSize工具介绍
  9. Force.com平台基础
  10. 最短路径之迪杰斯特拉(Dijkstra)算法