在学习Android系统的过程中,无论是在顶层应用还是底层驱动,都会涉及到Android NDK的使用。因为够效率,最新版的Android NDK支持ARMV5TE机器指令,并且提供了大量的C语言库。包括Libm(Math库),OpenGL ES,JNI接口以及其他的库。
好了,废话不多说,这里介绍一下基本的用法:
1. NDK程序的命名规则。
我们来看看一个简单的jni例程。可以在ndk安装目录下samples文件里面找到官方提供的几个例子。比如我们打开android-ndk-r9d/samples/hello-jni/jni/hello-jni.c这个源文件:
#include <string.h>
#include <jni.h>
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz )
{
return (*env)->NewStringUTF(env, "Hello from JNI ! Compiled with ABI " ABI ".");
}
这是一段非常简单的ndk程序,只有一个C语言函数,也就是给java调用的函数,这个函数的命名规则很有讲究,规则如下:
Java_PackageName_ClassName_MethodName
从中可以看出,一个用于给java程序调用的函数名词必须是:Java开头,然后是包名+类名+方法名,中间用_隔开。这样才能被指定的java程序所调用。
2.NDK程序的简单配置
NDK源代码必须存储在JNI目录中,其中除了源代码还有另外两个文件:Android.mk和Application.mk(非必须)。
下面来介绍一下Android.mk文件是如何配置NDK程序的。
通常一个最简单的Android.mk文件需要包含如下内容:
#hello-jni/jni/Android.mk的内容
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello_jni
LOCAL_SRC_FILES := hello_jni.c
include $(BUILD_SHARED_LIBRARY)
- LOCAL_PATH:设置工作目录,而my-dir则会返回Android.mk文件所在的目录。
- CLEAR——VARS:清除几乎所有以LOCAL——PATH开头的变量(不包括LOCAL_PATH。
- LOCAL_MODULE:用来设置模块的名字。
- LOCAL_SRC_FILES:用来指定参与模块编译的C/C++源文件名。
- BUILD_SHARED_LIBRARY:作用是指定生成的静态库或者共享库在运行时依赖的共享库模块列表。
3.通过java程序向ndk发送数据
首先编写Android.mk文件,可以直接用上面的版本。
然后是NDK程序,功能是复制某个文件的内容并写入指定文件中,如下:
#include <stdio.h>
#include <jni.h>
//filename1表示源目录,filename2表示目标目录
void copy(const char *filename1, const char * filename2)
{
char ch;
FILE *fp1 = fopen(filename1, "rt"); //以只读方式打开
FILE *fp2 = fopen(filename2, "wt"); //以只写方式打开
//循环都去文件内容
do
{
ch = fgetc(fp1);
fputc(ch, fp2);
} while (!feof(fp1));
fclose(fp1);
fclose(fp2);
}
JNIEXPORT void JNICALL Java_com_emercy_change_Copy_convert
(JNIEnv *env, jobject obj, jstring filename1, jstring filename2)
{
const char *c_str1 = (*env)->GetStringUTFChars(env, filename1, NULL);
const char *c_str2 = (*env)->GetStringUTFChars(env, filename2, NULL);
lowercase_to_uppercase(c_str1, c_str2);
(*env)->ReleaseStringUTFChars(env, filename1, c_str1);
(*env)->ReleaseStringUTFChars(env, filename2, c_str2);
return;
}
需要注意:任何一个NDK函数前两个参数必须是JNIENV*和jobject,其中JNIEnv*表示当前jni环境
接下来编写java程序来调用ndk函数。
这里还是要强调调用NDK函数的Java程序所在类的包名和类名需要符合NDK的命名规则。注意上面的NDK函数的名字,这里对应的包名、类名、方法名必须固定。
调用方法如下:
package com.emercy.change;
public class Copy
{
// filename1表示源文件,filename2表示目标文件
public native void convert(String filename1, String filename2);
static
{
//这里so文件
System.loadLibrary("Emercy");
}
}
在主窗口类中的onCreate方法中调用Copy方法即可完成工作。
package com.emercy.change;
import android.app.Activity;
import android.os.Bundle;
public class Main extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Copy().convert("/sdcard/src.txt", "/sdcard/dest.txt");
}
}
这样既可以完成通过Java程序调用C语言函数的功能了。
问题分析:
代码虽然比较简单,但是本人在调试的过程中仍然出现了一个问题,也花了一些时间。。。
当我把写好的Java代码在Android终端运行的时候,出现了闪退的现象。通过Logcat可以看到如下提示:
06-21 03:43:37.273: A/libc(2610): Fatal signal 11 (SIGSEGV) at 0x0000000c (code=1)
提示的内容大概是说出现了内存泄露的问题。最后通过分析,查阅相关资料得知,问题是由于NDK程序向SD卡做了写入操作,尽管这里是用的C语言实现的写入,但NDK程序同样会受到系统的监控。所以在此需要加入SD卡写入权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
再次运行程序,即可完美运行!
顺便提醒大家注意,即使是在NDK程序下,所有的权限同样会受控于系统,所以大家在编写NDK程序的时候不要忘了加入所需权限
好了,到这里应该NDK的基本使用都没什么问题了,以后在学习当中的问题欢迎大家一起探讨交流~~~