Android studio使用JNI实例(2)- 调用外部so库

注意:本人是初学Android开发,Android studio开发环境也是第一次使用,在本文及Android开发系列文章中均为本人个人的见解及网络资料,为自己的学习笔记,同时给初学者提供参考,不正确之处还望包含指正!

前言

本文是在《Android studio使用JNI实例(1)》的基础之上完成的。
外部so库在Java下没办法直接调用(个人意见,至少本人不知道直接调用的办法),所以要使用so库需要添加Java调用接口,即再次将so库的接口封装为Java可以调用的接口,即就是Native方法。

注意:
本文介绍的方法需要将so库封装到APK中,否则无法在调试时运行。
据网上资料介绍,在线调试及ADB调试安装的APK与将APK封装到Android系统镜像中(此时安装系统是会自动安装)这两种方法安装的APK权限不一样。在线调试安装的APK如果依赖so库,就算该so库在系统库(/system/lib路径下)中存在APK也不能直接使用,如果不封装到APK中APK会运行出错(找不到so库文件),应该是Android为了安全而限制的,而封装到Android镜像中的APK可以不用封装so库到APK而直接使用系统库下的so库,本文介绍的使用在线调试,所以使用的将so库封装到APK中,第二种方法在本文中不做介绍,本人目前也没有验证。

调用外部库

本实例的目的是在Android的APK中调用外部so库,其结果与《Android studio使用JNI实例(1)》效果一样,在Android中显示一段字符串,不同的是本例中的字符串来自so库。

生成库文件

首先生成可以在对应平台上运行的so库文件,本文实例运行在全志H6平台,是ARM体系结构,所以需要交叉编译。这些基本的不在赘述,对做Linux开发及嵌入式开发的程序猿都很基础。
本文不介绍生成Android平台so库的方法,也许直接交叉编译生成的库可以使用(本人没验证),为了防止出现不必要的问题,本人在Android源码编译环境中编译so库,此处只贴出库源码,不介绍生成库的方法,可以参考网路上的资料,也可以关注本人博客后期文章,后期本人会整理在Android源码环境下生成so库的方法。
生成名为libhello.so的库文件,其源码(hello.c)如下:

static const char *hello_string = "Hello, I'm library";

const char *hello_hello()
{
    return hello_string;
}

该库只有一个借口函数hello_hello(),其功能是返回存在静态去的字符串“Hello, I’m library”的地址,需要将其显示在屏幕上。

新建工程

由于本例是《Android studio使用JNI实例(1)》的升级,是在此基础上完成调用so库的操作,所以先参考《Android studio使用JNI实例(1)》新建名为CallLIB的工程,并做完《Android studio使用JNI实例(1)》中的所用步骤,确认其APK可以运行。
如下图是CallLIB工程的运行截图:
Android studio使用JNI实例(2)- 调用外部so库_第1张图片
上图是在全志H6机顶盒(Android7.0)上运行的结果,HDMI接口显示,从图中可知已经完成《Android studio使用JNI实例(1)》的步骤并成功执行。

导入外部so库文件

1.在src/main目录下新建一个jniLibs文件夹;

注意:
1)jniLibs文件夹不能写错,据网上资料介绍是因为Android Studio默认的;
2)libs文件夹下只放jar包,不要放so库(文件),如果将so库放入libs目录下们还需要其他配置,并且可能出错;

2.将armeabi,armeabi-v7a,x86,x86_64等平台的so库拷贝到jniLibs文件夹下对应的目录(jniLibs下也需要建立如armeabi的平台目录,然后才能放库文件);
拷贝libhello.so库后的目录结构如下图所示(注意左侧目录结构):
Android studio使用JNI实例(2)- 调用外部so库_第2张图片
本实例使用全志H6机顶盒(ARM体系)作为模拟终端,所以仅仅只有armeabi平台的so库文件,如果是其他平台,需要对应平台的库。
3.将库拷入NDK的platforms目录下。
这一步很重要,否则编译时会报错(找不到库和函数接口),上边的1、2步骤是将APK需要的库放如对应的目录,在打包时会打包到APK中,但是在编译时需要系统环境(编译环境)中能够找到对应的文件,编译过程中不会引用jniLibs路径(不知道什么原因,亲测必须拷贝到NDK系统环境中),所以必须拷贝到NDK的平台目录下。
NDK安装目录在SDK目录下的ndk-bundle目录(NDK目录永远是SDK目录下的ndk-bundle目录,在Ubuntu无法自己选NDK路径),如本人的SDK目录为:

/home/linux/Softwares/android/android-sdk-linux

那么我的NDK目录为:

/home/linux/Softwares/android/android-sdk-linux/ndk-bundle

NDK的platforms目录下有很多android版本文件,本人的platforms如下图所示:
Android studio使用JNI实例(2)- 调用外部so库_第3张图片
此处不介绍NDK及platforms下android-xx的目录结构,直接将库拷贝到android-24/arch-arm/usr/lib目录下即可。
我放在android-24版本里边的,具体放在哪个版本目录本人也不清楚,本人在测试时也遇到了如何选择版本的问题,经测试放在其他版本目录编译都会报错,只有24版本没有报错,具体为什么可能与创建工程时有关,但是我没有找到相关资料及介绍,希望有大神能解释一下!!!

注意,其他同学在做这一步时具体目录需要自己测试,以自己工程能够编译通过为准。

拷贝库到NDK系统目录后截图:

调用so库接口

1.修改Native实现源文件(hello.c)
以前是直接返回“JNI hahahahahahahaha”字符串,现在需要修改为调用so库接口hello_hello()。
原hello.c源文件:

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

#ifndef _Included_com_example_linux_calllib_myJNI
#define _Included_com_example_linux_calllib_myJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_linux_calllib_myJNI
 * Method:    sayHello
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_linux_calllib_myJNI_sayHello
  (JNIEnv *env, jclass jobj)
{
    return (*env)->NewStringUTF(env,"JNI hahahahahahahaha");
}

#ifdef __cplusplus
}
#endif
#endif

修改后的hello.c源文件:

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

#ifndef _Included_com_example_linux_calllib_myJNI
#define _Included_com_example_linux_calllib_myJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_linux_calllib_myJNI
 * Method:    sayHello
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_linux_calllib_myJNI_sayHello
  (JNIEnv *env, jclass jobj)
{
    return (*env)->NewStringUTF(env,hello_hello());
}

#ifdef __cplusplus
}
#endif
#endif

Android studio使用JNI实例(2)- 调用外部so库_第4张图片
从以上两文件对比可知,仅仅是将”JNI hahahahahahahaha”替换为hello_hello(),由直接使用字符串改为由外部库接口返回字符串,实现调用外部so库的功能。
2.修改app Module的build.gradle文件
在defaultConfig节点里的ndk节点的ldLibs变量添加值”hello”,修改后的ndk节点如下:

ndk {
    moduleName "JniTest"
    ldLibs "log", "z", "m", "hello"
    abiFilters "armeabi"
}

截图如下:
Android studio使用JNI实例(2)- 调用外部so库_第5张图片

编译APK

到此源码修改结束,需要编译APK。
1.编译JIN(“Make Project”/“Rebuild Project”);
2.编译APK(“Build APK”)。
Android studio使用JNI实例(2)- 调用外部so库_第6张图片

调试

Analyze APK工具查看

使用Android Studio自带的“Analyze APK”工具查看生成的APK文件,“Analyze APK”工具在“Build”标签下。
打开后会默认选择当前工程编译的APK(app-debug.apk):
Android studio使用JNI实例(2)- 调用外部so库_第7张图片
点击“OK”后会出现APK的详细结构,此处只关注“lib”目录结构,如下图所示:
Android studio使用JNI实例(2)- 调用外部so库_第8张图片
由上图可知,APK包含了外部库libhello.so和JNI库libJniTest.so,在《Android studio使用JNI实例(1)》的最后步骤中如果使用“Analyze APK”查看应该在lib中找到libJniTest.so,并且只有libJniTest.so,因为没有使用其他库。
现在知道所需要的库包含在APK中了,就可以上机调试了。

模拟器调试

此处使用全志H6机顶盒为仿真端,截图如下:
Android studio使用JNI实例(2)- 调用外部so库_第9张图片

dlopen failed: library “libc++.so” not found

这是因为APK中缺少libc++.so库,libhello.so依赖该库,所以报错。使用“ADB shell”可以查看到在/system/lib/下libc++.so和libhello.so都存在:
Android studio使用JNI实例(2)- 调用外部so库_第10张图片
在/system/lib/下libc++.so和libhello.so都存在但是APK却不能使用,这就是本文前沿描述的权限问题。
在本文中,解决错误的办法是从Android系统或源码中将将libc++.so拷贝出来,与libhello.so一样将libc++.so拷贝到jniLibs/armeabi目录下(与libhello.so放在一起):
Android studio使用JNI实例(2)- 调用外部so库_第11张图片
再次编译APK(“Build APK”)即可,编译后使用“Analyze APK”工具查看,截图如下:
Android studio使用JNI实例(2)- 调用外部so库_第12张图片
由此可知lib下有libhello.so、libc++.so和libJniTest.so,再次运行:

从上图可知,显示的字符串是来自libhello.so库的字符串”Hello, I’m library”。
到此,说明其过程是正确的,调用外部so库实验完成。

你可能感兴趣的:(android)