android NDK入门篇----混合使用java和c/c++代码

        在android应用开发过程中,从Java调用C/C++函数主要经历以下几个步骤:

        1. 必须在java代码中声明本地方法。

        2.需要实现java本地接口(JNI)粘合层。

3.必须创建Android makefile文件。

4.必须用C/C++实现本地方法。

5.必须编译本地库。

6.必须加载本地库。

    

        android-NDK为我们提供了一整套的机制来完成这些步骤。我采用的是NDKr8版本,完全不用Cygwin(貌似从NDKr7开始就可以完全不用Cygwin了)。

        前篇:新建android工程MyJNITest,步骤省略,示例代码如下:

       代码清单1:activity_main.xml



    
         
        
代码清单2:MainActivity.java

package com.example.myjnitest;

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

public class MainActivity extends Activity {

	private EditText additionResult,multiplicationResult;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		additionResult=(EditText)findViewById(R.id.addtionResult);
		multiplicationResult=(EditText)findViewById(R.id.multiplicationResult);
	}
	
	public void calculate(View view)
	{
		int id=view.getId();
		switch (id) {
		case R.id.addition:
			long addResult=addition(20, 70);
			additionResult.setText("20+70="+addResult);
			break;

		case R.id.multiplication:
			long mulResult=multiplication(20, 70);
			multiplicationResult.setText("20*70="+mulResult);
			break;
		}
	}
	
    //本地方法,由java调用
    private native long addition(int i,int j);
    
    public static native long multiplication(int a,int b);

}

1. 声明本地方法:

示例代码2中有两个方法,addition()和multiplication()方法。这就是java中声明的本地方法,只是比普通的方法多了native关键字(native关键字不可省略),这些方法并没有具体的实现。从调用者角度来看,一旦声明了本地方法就可以在java代码中调用,会顺利通过编译。不过,如果应用运行并调用这两个本地方法的话,就会蹦出IllegalStateException异常,这很好理解,因为本地方法还没有具体实现。

        本地方法可以是公共的,也可以是受保护的、私有的或包作用域的,如同任何其他java方法一样。同样,本地方法不一定必须是静态的,也不是必须使用基本类型。

2.实现JNI粘合层:

java通过JNI框架调用库里的C/C++方法。可以利用JDK(java开发工具包)建立JNI粘合层。首先,需要在C/C++头文件中声明要实现的函数。建议使用JDK的javah工具自动生成头文件。

在cmd中,进入MyJNITest工程根目录,例如我的工程根目录为 E:\workSpace\MyJNITest,然后在此目录下使用javah命令:

       将本地方法放在了MainActivity.java类中,使用javah命令:

javah -classpath ./bin/classes -d jni com.example.myjnitest.MainActivity

android NDK入门篇----混合使用java和c/c++代码_第1张图片

会蹦出如此错误,找不到android.app.Activity的类文件。跳出该错误,猜想可能得原因是由于没有找到android.jar包,可能得在某个参数中指定下路径,在此不纠结其原因,以后的文章中再详细解释吧。

避免该问题有两种解决办法,仅是避免而已:

                方法一: 在cmd中进入工程的src目录下,执行javah命令:

        javah  -d jni com.example.myjnitest.MainActivity

          

        这样会在src目录下成功生成一个jni文件夹,头文件就在该jni文件夹中。

方法二:不要将本地方法写在MainActivity.java类中,而是单独放在一个普通类中

        代码清单3:NativeMethodsClass.java

package com.example.myjnitest;

public class NativeMethodsClass {

	  //本地方法,由java调用
    private native long addition(int i,int j);
    
    public static native long multiplication(int a,int b);
}
再执行javah命令:

javah -classpath ./bin/classes -d jni com.example.myjnitest.NativeMethodsClass

就会在工程的根目录下生成jni文件夹,该文件夹中包含头文件。


采用方法一,生成头文件,

代码清单4:com_example_myjnitest_MainActivity.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_example_myjnitest_MainActivity */

#ifndef _Included_com_example_myjnitest_MainActivity
#define _Included_com_example_myjnitest_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_myjnitest_MainActivity
 * Method:    addition
 * Signature: (II)J
 */
JNIEXPORT jlong JNICALL Java_com_example_myjnitest_MainActivity_addition
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     com_example_myjnitest_MainActivity
 * Method:    multiplication
 * Signature: (II)J
 */
JNIEXPORT jlong JNICALL Java_com_example_myjnitest_MainActivity_multiplication
  (JNIEnv *, jclass, jint, jint);

#ifdef __cplusplus
}
#endif
#endif
不要 直接修改该文件,若需要添加本地方法或者其他操作的话,可以使用javah命令重新生成一次。

C头文件本身做不了任何事情。需要创建com_example_myjnitest_MainActivity.c文件实现其中的函数。

代码清单5:com_example_myjnitest_MainActivity.c

#include "com_example_myjnitest_MainActivity.h"
/*
 * Class:     com_example_myjnitest_MainActivity
 * Method:    addition
 * Signature: (II)J
 */
JNIEXPORT jlong JNICALL Java_com_example_myjnitest_MainActivity_addition
  (JNIEnv * env, jobject clazz, jint a, jint b)
  {
  	return 0;
  }

/*
 * Class:     com_example_myjnitest_MainActivity
 * Method:    multiplication
 * Signature: (II)J
 */
JNIEXPORT jlong JNICALL Java_com_example_myjnitest_MainActivity_multiplication
  (JNIEnv * env, jobject clazz, jint a, jint b);
{
	return 0;
}
JNI层的所有函数有一个共同点:他们的第一个参数都是JNIEnv*类型(JNIEnv对象的指针)。JNIEnv的对象是JNI环境本身,使用它可以与虚拟机交互。第二个参数,当方法被声明为静态时为jclass类型,否则为jobject类型。

3.创建Makefile文件

在此之前,请先将src目录下的jni文件夹迁移到MyJNITest工程跟目录下,然后在该jni目录下创建mk文件。否则,后面使用NDK编译so库的时候,会编译不成功。

代码清单6:Android.mk

 LOCAL_PATH := $(call my-dir)

 include $(CLEAR_VARS)
 LOCAL_MODULE    := myjnitest

 LOCAL_SRC_FILES := com_example_myjnitest_MainActivity.c

 include $(BUILD_SHARED_LIBRARY)

现在,我们解释一下:

LOCAL_PATH  :=  $(callmy-dir)

Android.mk文件必须先定义LOCAL_PATH变量,它用来定位开发树中的源文件。在本例中,编译系统提供的宏‘my-dir’,用来返回当前目录(即这个目录包含了Android.mk文件自身)。

include$(CLEAR_VARS)

CLEAR_VARS变量由编译系统提供,它指向一个特殊的GNU Makefile,这个Makefile会为你清除很多LOCAL_XXX这样的变量(如LOCAL_MODULELOCAL_SRC_FIELS, LOCAL_STATIC_LIBARIES, 等等),当然LOCAL_PATH除外。这样做是必须的,因为所有的编译控制文件都是在单一的GNU Make可执行上下文环境中解析的,而在这个上下文环境中所有的变量都是全局的。

LOCAL_MODULE  :=myjnitest

LOCAL_MODULE变量用来唯一标示Android.mk文件中的每个module。这个名字必须是唯一的,并且不能包含任何空白字符。编译系统会根据生成的文件自动添加前缀和后缀。换句话说,一个叫‘foo’的动态库module,会生成‘libfoo.so’。

重要提示:

如果你的module命名为‘libfoo’,编译系统将不会另外添加‘lib’前缀,同样会生成libfoo.so。(注:为了养成统一习惯,个人建议不要自行加lib前缀了)

LOCAL_SRC_FILES  := com_example_myjnitest_MainActivity.c

LOCAL_SRC_FILES变量必须包含cc++源文件列表,这些源文件会被编译和组装到一个module中。注意这里你不必列出头文件和包含文件,因为编译系统会自动为你计算出它们之间的依赖关系;你只需要列出源文件,这些源文件将直接传递给编译器。

注意,默认的c++源文件的扩展名是‘.cpp’,当然你可以通过定义变量LOCAL_CPP_EXTENSION指定不同的扩展名,但是要记住不能忘记前面的那个点(即‘.cxx’可以工作,但是‘cxx’不行的)。

include $(BUILD_SHARED_LIBRARY)

BUILD_SHARED_LIBRARY是编译系统提供的一个变量,它指向一个GNU Makefile脚本,这个脚本负责收集从最近的‘include $(CLEAR_VARS)’,在LOCAL_XXX变量中定义的所有信息,同时决定编译什么,以及怎么编译。显然,BUILD_STATIC_LIBRARY是用来生成静态库的。


4. 实现本地函数

一般情况下,需要新建C/C++源文件以及相应的头文件来完成本地函数的具体实现,然后从JNI粘合层调用函数,而不是直接com_example_myjnitest_MainActivity.c文件中实现,若直接将实现代码在com_example_myjnitest_MainActivity.c文件中实现,虽然在技术上可行,但不推荐这么做。这样会将粘合层与本地函数实现捆绑在一起,使得本地函数代码难以在非java程序中重要,如,可能需要在IOS程序中重用相同的C/C++头文件和实现。

由于该工程代码功能比较简单,因此没有将方法实现放在单独的C源文件中,而是在粘合层进行了实现。

代码清单7:com_example_myjnitest_MainActivity.c

#include "com_example_myjnitest_MainActivity.h"
/*
 * Class:     com_example_myjnitest_MainActivity
 * Method:    addition
 * Signature: (II)J
 */
JNIEXPORT jlong JNICALL Java_com_example_myjnitest_MainActivity_addition
  (JNIEnv * env, jobject clazz, jint a, jint b)
  {
  	return a+b;
  }

/*
 * Class:     com_example_myjnitest_MainActivity
 * Method:    multiplication
 * Signature: (II)J
 */
JNIEXPORT jlong JNICALL Java_com_example_myjnitest_MainActivity_multiplication
  (JNIEnv * env, jobject clazz, jint a, jint b);
{
	return a*b;
}


5. 编译本地库

 使用Eclipse工具与android NDK生成so动态库。注意,需要将NDK的路径添加到系统环境变量path中。

新建并配置一个新的Builder

    1) 点击Project->Properties->Builders->New,新建立一个Builder。在弹出的对话框上面点击Program,点击OK;

    2) 在弹出的对话框【Edit Configuration】中,配置选项卡【Main】:

               Location中需要填入nkd-build.cmd的路径(NDK安装目录下)。

               WorkingDiretcoty中需要填入MyJNITest的工程根目录。

    3) 在【EditConfiguration】中,配置选项卡【Refresh】:

              勾选“Refresh resources upon completion”,

              勾选“The entire workspace”,

              勾选“Recuresively include sub-folders”。

    4)在【EditConfiguration】中,配置选项卡【Build Options】:

             勾选“After a “Clean””,

             勾选“During manual builds”,

             勾选“During auto builds”,

             勾选“Specify working set of relevant resources”。

             点击“Specify Resources…”勾选MyJNITest工程的“jni“目录,Finish!

      保存设置,点击OK。

由于我们勾选了 “During auto builds” ,所以在工程有所改变的时候,so文件便会自动编译,正确生成以后就能在工程目录下发现多了两个文件夹,文件夹libs\armeabi目录下生成了一个叫 libhello-jni.so 的文件。至此,使用NDK生成so文件的工作就完成了。

6. 加载本地库

在静态初始化块中调用System.loadLibrary加载本地库,是加载库的最简单方法。静态代码块中的代码是在虚拟机加载类时执行的,此时还没调用过任何方法。静态初始化块可能会显著增加开销,这是在用某些特定函数时需要避免的。

代码清单8:加载so库

  //加载本地库
    static
    {
        System.loadLibrary("myjnitest");
    }
注意加载本地库时,本地库名称不能带前缀lib,也不能带后缀.so,否则都将蹦出错误信息。

可以运行程序了,试试看吧。


补充: 关于上面的javah命令执行, 本地代码放在MainActivity.java中,就蹦出找不到android.app.Activity类文件的错误解决:

之所以跳出该错误信息,是因为MainActivity继承了Activity,是引导类的问题,就是javah -help 中所提到的“-bootclasspath”的作用了。在cmd中,进入到MyJNITest工程根目录下,执行javah命令:

E:\workSpace\MyJNITest\src>javah -classpath bin/classes -bootclasspath E:\android-sdk-windows\android-sdk-windows\platforms\android-8\android.jar  -d jni com.example.myjnitest.MainActivity

好了,在根目录下成功生成jni目录和头文件了,省了需要在src目录下往外复制啦。


源码工程下载:点击打开链接 http://download.csdn.net/detail/u010978228/6969263

你可能感兴趣的:(android学习)