android应用如何访问C库

        最近由于项目的开发需要,需要用到安卓系统,由此开始安卓的一些学习,下面只作为个人的一些记录。

        安卓应用是由java编写的,而在android底层,如安卓底层的硬件驱动程序,都是由C/C++编写的,那应用层java是必须通过某种手段才能使用底层提供的硬件资源。毫无疑问,在java看要调用底层C实现的代码,中间必须要有一层接口,那就是JNI。本例要实现的就是JAVA如何访问底层C库。

        假设JAVA应用需要实现的功能是控制底层硬件LED设备,包括开,关及控制操作。需要实现下述流程:

       1 实现一个JAVA硬件控制类,声明本地的Native方法(操作硬件的接口),并使用static模块加载C的动态库。实现代码如下:

package com.example.zhm.hardlibrary; //声明目录名

public class HardControl{
    //声明本地的Native方法
    public static native int ledCtrl(int which, int status);
    public static native int ledOpen();
    public static native void ledClose();

    //我们要在这个java程序里面加载C库,定义一个static模块
    static {
        //用System方法来加载C库
        try { //添加捕获异常代码的快捷键为:ctrl+alt+T(前提先用鼠标选中需要捕获的代码)
            System.loadLibrary("hardcontrol"); //参数为C库的库名
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
        注意, 加载C库的方法为System.loadLibrary.

上面声明的本地native方法就是要操作底层硬件的几个功能,它是与硬件关联的,既然是Native,自然不是在本地实现,所以,它自然是由C库中实现的。那本地的这些操作硬件的native方法与C库的硬件操作接口是如何关联的呢?请看下面。


        2 JAVA本地native方法与C库接口的关联

        先上代码再说,以下为C文件,将其命名为:hardcontrol.c

#include 
#include 
#include 

#include  /* liblog */


jint ledOpen(JNIEnv *env, jobject cls)
{

	__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo",  "native_ledOpen"); //调用这句就可以打印了。

	return 0;
}


void ledClose(JNIEnv *env, jobject cls)
{
	__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo",  "native_ledClose"); //调用这句就可以打印了。

}

void ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
{
	__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo",  "native_ledCtrl:%d %d", which, status); //调用这句就可以打印了。

	return 0;
}


static const JNINativeMethod methods[] = {
		{"ledOpen", "()I", (void *)ledOpen}, //() inside empty=> no params, 'I' means: return type is int
		{"ledClose", "()V", (void *)ledClose},//() inside empty=> no params, 'V' means: return type is void
		{"ledCtrl", "(II)I", (void *)ledCtrl}, //()inside two I => means two params, types is int, ouside () is 'I' ==> return type is int
			
};


/* System.LoadLibrary */
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
	JNIEnv *env;
	jclass cls;
	if((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_4) != JNI_OK) {
		return JNI_ERR;; //jni version not supported
	}

    //find the class that realize the local native method: *.java, here is HardControl
    //PS:here need to indification the package & class name.
    // "." --> "/"
	cls = (*env)->FindClass(env, "com/example/zhm/hardlibrary/HardControl");
	if( cls == NULL )
	{
		return JNI_ERR;
	}

	//find the class then register their methods .  Java's method <==correspond with ==> C's method
	if((*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])) < 0 ) // 3: means there are 3 funcs in methods.
	{
		
	}

}
        首先, JNINativeMethod  变量methods的第一个元素为java的本地native方法,第二个元素为C库的参数与返回值说明,第三个元素是与本地native方法关联的C函数。由此就完成了它们之间的关联。   除了关联之外,还需要在进行注册,就是它了:
                                  RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0]))

        3 将hardcontrol.c编译成libhardcontrol.so库:

        使用交叉编译工具:arm-linux-gcc  -fPIC -shared hardcontrol.c -o libhardcontrol.so -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include

        ps:后面的-I是寻找到jni.h的目录,编译出的动态库要么要在/system/lib或/vendor/lib或者放到应用程序中。问题:如何放到java应用中以及让应用能访问C库的方法:

      (1)需要在安卓工程下的app/libs/下创建armeabi目录,并将上面so文件拷入此目录下

       (2)修改build.gradle文件(这种文件有2个,修改Module:app)这个,在这个文件中增加:

    sourceSets{
        main{
           jniLibs.srcDirs=['libs'] 
       }
    }
上面代码是告诉应用去/app/libs寻找so文件,即so文件的存放位置要告诉应用
      (3) 编译后运行,此时不能用模拟器,因为模拟器没有arm指令集,必须用开发板,用USB接上开发板,然后编译烧入。

        

途中会遇到的一些问题及解决办法:

ps:
 1编译后产生的apk包路径在app/build/outputs/apk/下。
 2 caused by library 'libc.so.6' not found原因: 我们的libhardcontrol.so文件是依赖于这个libc.so.6这个库的,解决办法:
  先查看开发板上是否有这个库。
  在开发板上shell上输入:
    ls /vendor/lib回车 ---》没找到
    ls /system/lib/libc.so*   -->只找到 /system/lib/libc.so 无 libc.so.6,怎么办?
    要么 重新构造系统生成libc.so.6这个库?太慢,麻烦
    或者 直接使用这个libc.so库而不使用libc.so.6这个库,就是在生成so文件编译时增加选项 -nostdlib    ==》表示它生成so文件时不使用标准的libc库,我们可以自己指定使用哪个libc
          在系统目录下:cd /work/anroid-5.0.2/
                                   find -name "libc.so"     ==> 这个会出来很多的libc.so,我们从中找到一个版本再编译
arm-linux-gcc  -fPIC -shared hardcontrol.c -o libhardcontrol.so -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include -nostdlib /work/android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/lib/libc.so (最后面这个是路径)
这个选项指的是不使用标准的libc库,而是使用安卓系统目录下自带的libc库。

3 在JNI文件中增加头文件 #include ,然后就可以调用如下语句进行日志打印:

__android_log_print(ANDROID_LOG_DEBUG,"JNI_NFC", "native ledCtrl...:%d, %d", which, status);

但是在编译过程中,需要指定log.h头文件的路径,所以需要在编译选项中添加: -I /home/lxj/rk312x/prebuilts/ndk/9/platforms/android-21/arch-arm/usr/include/

arm-linux-gcc  -fPIC -shared hardcontrol.c -o libhardcontrol.so -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include -nostdlib /work/android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/lib/libc.so -I /home/lxj/rk312x/prebuilts/ndk/9/platforms/android-21/arch-arm/usr/include/

但是在运行程序过程中会出现如下错误:cannot locate symbol "__android_log_print" referenced by "/data/app/com.reallife.app_nfcdemo_test-2/lib/arm/libhardcontrol.so

说明动态库hardcontrol.so中找不到__android_log_print符号,表明依赖liblog.so库,因为在编译此动态库时还需要指定需要链接的库的路径:如下所示:

 

arm-linux-gcc -fPIC -shared hardcontrol.c -o libhardcontrol.so -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include -nostdlib  /home/lxj/rk312x/prebuilts/ndk/9/platforms/android-9/arch-arm/usr/lib/libc.so -I /home/lxj/rk312x/prebuilts/ndk/9/platforms/android-21/arch-arm/usr/include/    /home/lxj/rk312x/prebuilts/ndk/9/platforms/android-21/arch-arm/usr/lib/liblog.so


你可能感兴趣的:(Android系统)