1. 请教关于android linux动态库.so的加载调用
有这两种办法:
第一种:
需求:
有时候应用修复了native层一个小BUG,应用需要更新了,但是用户必须下载整个APK包进行安装,而我们需要的只是替换SO
于是想,能不能加载自定义路径下的 SO 文件呢
答案是完全没问题:
使用系统方法:
void java.lang.System.load(String pathName)
但是有一点,pathName 路径必须有执行权限,意思就是说我们不能加载SD卡上的SO,因为没有执行权限
那也没关系,我们复制到应用私有目录下就OK嘛。
看码
private void load() {
File dir = getDir("libs", Context.MODE_PRIVATE);
File soFile = new File(dir, "libTestJNI.so");
FileUtils.assetToFile(this, "libTestJNI.so", soFile);
try {
System.load(soFile.getAbsolutePath());
} catch (Exception e) {
}
}
这样就完全OK,
我们只需要架个服务器,每次启动时动态监测 SO 文件有没有更新,有则下载SO,然后加载,这样就可以避免用户安装新的应用,
要知道重新安装应用的用户体验是很差的,要让用户无感知的更新他。
第二种:
采用dlopen动态加载第三方库,无非和system.load一样,就是要实现指定路径加载so的目的,这种方法升级so的话,那就的需要一个基本so,一直不变,用来调用dlopen,然后升级另一个so。
这两种办法都会遇到一个问题,就是不能直接加载SD卡中的so,因为sd卡没有执行权限,不能直接加载这种二进制文件,需要拷贝到data/data/packagename/files/ 目录下,再次进行加载即可,拷贝也是有讲究的,需要用到context.openFileOutput方法。
2. 如何查看ndk编译的动态库符号表
$ /path/to/ndk/buid/prebuilt/windows/arm-eabi-4.4.0/bin/arm-eabi-nm libs/armeabi/libsanangeles.so
00003600 T Java_com_example_SanAngeles_DemoGLSurfaceView_nativePause
00003638 T Java_com_example_SanAngeles_DemoRenderer_nativeDone
0000367c T Java_com_example_SanAngeles_DemoRenderer_nativeInit
000035b4 T Java_com_example_SanAngeles_DemoRenderer_nativeRender
00003644 T Java_com_example_SanAngeles_DemoRenderer_nativeResize
00007334 a _DYNAMIC
0000740c a _GLOBAL_OFFSET_TABLE_
复制代码
这里可以看到几乎所有的函数名全局变量名都会被导出。其中有Java_com_example_SanAngeles_为前缀的JNI接口函数,有importGLInit这些普通函数,有freeGLObject这些局部(static)函数,还有sStartTick等全局变量名。其实在这个动态发布的时候,只需要导出java_com_开头的jni函数就可以了,里面这些细节函数名完全不需要暴露出来。
如何做到这一点呢?首先,我们需要了解gcc新引进的选项-fvisibility=hidden,这个编译选项可以把所有的符号名(包括函数名和全局变量名)都强制标记成隐藏属性。我们可以在Android.mk中可以通过修改LOCAL_CFLAGS选项加入-fvisibility=hidden来做到这一点,这样编译之后的.so看到的符号表为:
000033d0 t Java_com_example_SanAngeles_DemoGLSurfaceView_nativePause
00003408 t Java_com_example_SanAngeles_DemoRenderer_nativeDone
0000344c t Java_com_example_SanAngeles_DemoRenderer_nativeInit
00003384 t Java_com_example_SanAngeles_DemoRenderer_nativeRender
00003414 t Java_com_example_SanAngeles_DemoRenderer_nativeResize
00007104 a _DYNAMIC
3. 有没有一个配置或一个函数可以强制让android以32位加载动态库so文件
1、 .so动态库的生成
可使用gcc或者g++编译器生成动态库文件(此处以g++编译器为例)
g++ -shared -fPIC -c XXX.cpp
g++ -shared -fPIC -o XXX.so XXX.o
2、 .so动态库的动态调用接口函数说明
动态库的调用关系可以在需要调用动态库的程序编译时,通过g++的-L和-l命令来指定。例如:程序test启动时需要加载目录/root/src/lib中的libtest_so1.so动态库,编译命令可照如下编写执行:
g++ -g -o test test.cpp –L/root/src/lib –ltest_so1
(此处,我们重点讲解动态库的动态调用的方法,关于静态的通过g++编译命令调用的方式不作详细讲解,具体相关内容可上网查询)
Linux下,提供专门的一组API用于完成打开动态库,查找符号,处理出错,关闭动态库等功能。
下面对这些接口函数逐一介绍(调用这些接口时,需引用头文件#include <dlfcn.h>):
1) dlopen
函数原型:void *dlopen(const char *libname,int flag);
功能描述:dlopen必须在dlerror,dlsym和dlclose之前调用,表示要将库装载到内存,准备使用。如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen操作失败,返回NULL值;如果库已经被装载过,则dlopen会返回同样的句柄。
参数中的libname一般是库的全路径,这样dlopen会直接装载该文件;如果只是指定了库名称,在dlopen会按照下面的机制去搜寻:
a.根据环境变量LD_LIBRARY_PATH查找
b.根据/etc/ld.so.cache查找
c.查找依次在/lib和/usr/lib目录查找。
flag参数表示处理未定义函数的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内存,等用到没定义的函数再说;RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。
2) dlerror
函数原型:char *dlerror(void);
功能描述:dlerror可以获得最近一次dlopen,dlsym或dlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。
3) dlsym
函数原型:void *dlsym(void *handle,const char *symbol);
功能描述:在dlopen之后,库被装载到内存。dlsym可以获得指定函数(symbol)在内存中的位置(指针)。如果找不到指定函数,则dlsym会返回NULL值。但判断函数是否存在最好的方法是使用dlerror函数,
4) dlclose
函数原型:int dlclose(void *);
功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose之后,析构函数会被调用。
3、 普通函数的调用
此处以源码实例说明。各源码文件关系如下:
test_so1.h和test_so1.cpp生成test_so1.so动态库。
test_so2.h和test_so2.cpp生成test_so2.so动态库。
test_dl.cpp生成test_dl可执行程序,test_dl通过dlopen系列等API函数,并使用函数指针以到达动态调用不同so库中test函数的目的。
4. ndk-Android NDk 怎么编译时动态链接第三方so库,有头文件
问题描述:Android如何调用第三方SO库;
已知条件:SO库为Android版本连接库(*.so文件),并提供了详细的接口说明;
已了解解决方案:
1.将SO文件直接放到libs/armeabi下,然后代码中System.loadLibrary("xxx");再public native static int xxx_xxx_xxx();接下来就可以直接调用xxx_xxx_xxx()方法;
2.第二种方案,创建自己的SO文件,在自己的SO文件里调用第三方SO,再在程序中调用自己的SO,这种比较复杂,需要建java类文件,生成.h文件,编写C源文件include之前生成的.h文件并实现相应方法,最后用android NDK开发包中的ndk-build脚本生成对应的.so共享库;
求解:
1.上面两种方案是否可行?不可行的话存在什么问题?
2.两种方案有什么区别?为什么网上大部都是用的第二种方案?
3.只有一个*.so文件,并提供了详细的接口说明,是否可在ANDROID中使用它?
首先要看这个SO是不是JNI规范的SO,比如有没有返回JNI不直接支持的类型。也就是说这个SO是不是可以直接当作JNI来调用。如果答案是否定的,你只能选第二个方案。
如果答案是肯定的,还要看你是不是希望这个SO的库直接暴露给JAVA层,如果答案是否定的,你只能选第二个方案,比如你本身也是一个库的提供者。
一般如果你只有SO,就说明这个是别人提供给你的,你可以要求对方给你提供配套的JAVA调用文件。
1、这个要看这个SO是不是符合JNI调用的规范。还要看你自己的意愿。
2、因为第二种方法最灵活,各种情况都可以实现。
3、可以
看能不能直接从JAVA调用的最简单的方法就是看SO里的函数名是不是Java_XXX_XXX_XXX格式的
是就可以,你可以自己写一个配套的JAVA文件,注意一下SO函数名和JAVA函数名的转换规则,或者向SO提供方索要;
不是的话就选第二种方案吧。
1、检查所需文件是否齐全
使用第三方动态库,应该至少有2个文件,一个是动态库(.so),另一个是包含
动态库API声明的头文件(.h)
2、封装原动态库
原动态库文件不包含jni接口需要的信息,所以我们需要对其进行封装,所以我
们的需求是:将libadd.so 里面的API封装成带jni接口的动态
3、编写库的封装函数libaddjni.c
根据前面生成的com_android_libjni_LibJavaHeader.h 文件,编写libaddjni.c,用
来生成libaddjni.so
Android中集成第三方软件包(.jar, .so)
Android中可能会用到第三方的软件包,这包括Java包.jar和Native包.so。jar包既可通过Eclipse开发环境集成,也可通过编译源码集成,看你的工作环境。
假定自己开发的程序为MyMaps,需要用到BaiMaps的库,包括mapapi.jar和libBMapApiEngine_v1_3_1.so。
一、Eclipse中集成第三方jar包及.so动态库
MyMaps工程下创建目录libs以及libs/armeabi,把mapapi.jar放在的libs/目录下,把libBMapApiEngine_v1_3_1.so放在libs/armeabi/下。
Eclipse中把第三方jar包mapapi.jar打包到MyMaps的步骤:
1. 右击工程,选择Properties;
2. Java Build Path,选择Libraries;
3. Libraries页面点击右面按钮“Add Library…”;
4. 选择“User Library”,点击“Next”;
5. 点击“User Libraries”按钮;
6. 在弹出界面中,点击“New…”;
7. 输入“User library name”,点击“OK”确认;
8. 返回之后,选择刚刚创建的User library,右面点击“AddJARs”;
9. 选择MyMaps/libs/下的mapapi.jar;
10. 确认,返回。
这样,编译之后,该jar包就会被打进MyMaps.apk中,libBMapApiEngine_v1_3_1.so也被打包在lib/armeabi/中。
程序运行过程中,libBMapApiEngine_v1_3_1.so被放在/data/data/<yourAppPackage>/lib/下,加载动态库时系统会从程序的该lib/目录下查找.so库。
二、源码中集成第三方集成jar包及.so动态库
Android源码中MyMaps放在packages/apps下。MyMaps下创建目录libs以及libs/armeabi,并把mapapi.jar放在libs/,把libBMapApiEngine_v1_3_1.so放在libs/armeabi。
2.1 修改Android.mk文件
Android.mk文件如下:
[plain] view plain
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := libmapapi
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := MyMaps
include $(BUILD_PACKAGE)
##################################################
include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES :=libmapapi:libs/mapapi.jar
LOCAL_PREBUILT_LIBS :=libBMapApiEngine_v1_3_1:libs/armeabi/libBMapApiEngine_v1_3_1.so
LOCAL_MODULE_TAGS := optional
include $(BUILD_MULTI_PREBUILT)
# Use the following include to make our testapk.
include $(callall-makefiles-under,$(LOCAL_PATH))
1 集成jar包
LOCAL_STATIC_JAVA_LIBRARIES取jar库的别名,可以任意取值;
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES指定prebuiltjar库的规则,格式:别名:jar文件路径。注意:别名一定要与LOCAL_STATIC_JAVA_LIBRARIES里所取的别名一致,且不含.jar;jar文件路径一定要是真实的存放第三方jar包的路径。
编译用BUILD_MULTI_PREBUILT。
2 集成.so动态库
LOCAL_PREBUILT_LIBS指定prebuilt so的规则,格式:别名:so文件路径。注意:别名一般不可改变,特别是第三方jar包使用.so库的情况,且不含.so;so文件路径一定要是真实的存放第三方so文件的路径。
编译拷贝用BUILD_MULTI_PREBUILT。
2.2 加入到GRANDFATHERED_USER_MODULES
在文件user_tags.mk中,把libBMapApiEngine_v1_3_1加入到GRANDFATHERED_USER_MODULES中
[plain] view plain
GRANDFATHERED_USER_MODULES += \
… \
libBMapApiEngine_v1_3_1
user_tags.mk可以是build/core下的,也可以是$(TARGET_DEVICE_DIR)下的,推荐修改$(TARGET_DEVICE_DIR)下的。
2.3 编译结果
MyMaps.apk编译生成在out/target/proct/<YourProct>/system/app/下;
libBMapApiEngine_v1_3_1.so放在out/target/proct/<YourProct>/system/lib/下,这也是系统加载动态库时搜索的路径。
5. 请教关于android linux动态库.so的加载调用
1.在使用第三方的.so库做android开发,发现仅仅放到AndroidProject/libs/armeabi/libminivenus.so这个位置,使用System.loadLibrary加载起来可以回正常使用。
2.库的名字必须是答libminivenus.so,不可以改名字。也不可以使用System.load从其他地方加载(非SD卡)。如果将库的名字或者加载位置改动,调用的jni接口就返回错误。
3.libminivenus.so中确实有libminivenus的字段,将库的名字与该字段一起修改结果无效。
6. Android NDK 把jni要调的方法编译成静态库,再转动态库,报找不到对应的函数
用那个PREBUILT_STATIC_LIBRARY 这个来配置编译好了的静态库,而不是传统的的BUILD_STATIC_LIBRARY!!
7. 请问 android中 是否可以 调用C++编写并封装的动态链接库文件(DLL)该怎么实现
dll 是 Windows 平台的动态库,而 so 是 Linux 平台的。即使你用别的编译器如 gcc 把 VC 开发的动态库能内编译为 so 也能被 Android 调用,但归根容结底这个 so 还是要调用 Windows API,这根本就是不可能生效。要 Android 能调用,那么就必须要用标准 C 以及 Android 提供的系统 API 函数在 Linux 上编译。在 Windows 平台,你可以试试安装 MinGW,使用其 gcc 编译器来编译 so 库。就是不知道是否可以设置 CPU 指令集,如果不能设置 ARM 那么编译了没法用。
8. Android怎么调用第三方SO动态链接库
已了解解决方案: 1.将SO文件直接放到libs/armeabi下,然后代码中System.loadLibrary("xxx");再publicnativestaticintxxx_xxx_xxx();接下来就可以直接调用xxx_xxx_xxx()方法; 2.第二种方案,创建自己的SO文件,在自己的SO文件里调用第三方SO,再在程序中调用自己的SO,这种比较复杂,需要建java类文件,生成.h文件,编写C源文件include之前生成的.h文件并实现相应方法,最后用androidNDK开发包中的ndk-build脚本生成对应的.so共享库;求解: 网上说的第二种方案,是自己引用so库,最后声称JAR ------解决方案-------------------------------------------------------- 首先要看这个SO是不是JNI规范的SO,比如有没有返回JNI不直接支持的类型。也就是说这个SO是不是可以直接当作JNI来调用。如果答案是否定的,你只能选第二个方案。 如果答案是肯定的,还要看你是不是希望这个SO的库直接暴露给JAVA层,如果答案是否定的,你只能选第二个方案,比如你本身也是一个库的提供者。 一般如果你只有SO,就说明这个是别人提供给你的,你可以要求对方给你提供配套的JAVA调用文件。 1、这个要看这个SO是不是符合JNI调用的规范。还要看你自己的意愿。 2、因为第二种方法最灵活,各种情况都可以实现。3、可以------解决方案-------------------------------------------------------- 看能不能直接从JAVA调用的最简单的方法就是看SO里的函数名是不是Java_XXX_XXX_XXX格式的 是就可以,你可以自己写一个配套的JAVA文件,注意一下SO函数名和JAVA函数名的转换规则,或者向SO提供方索要; 不是的话就选第二种方案吧。
9. 有没有一个配置或一个函数可以强制让android以32位加载动态库so文件
在Android中调用动态库文件(*.so)都是通过jni的方式,而且往往在apk或jar包中调用so文件时,都要将对应so文件打包进apk或jar包,工程目录下图:
以上方式的存在的问题:
1、缺少灵活性比较类似静态加载了(不是静态加载),能加载的so文件绑定死了;
2、但so文件很多或很大时,会导致对应的apk和jar包很大;
3、不能动态的对so文件更新;
Android中加载so文件的提供的API:
void System.load(String pathName);
说明:
1、pathName:文件名+文件路劲;
2、该方法调用成功后so文件中的导出函数都将插入的系统提供的一个映射表(类型Map);
看到以上对System.load(String pathName);的函数说明可定有人会想到将so文件放到一个指定的目录然后再通过参数pathName直接引用该目录的路劲和对应的so文件问题不就解决了吗?
这里有个问题被忽略了,那就是System.load只能加载两个目录路劲下的so文件:
1、/system/lib ;
2、安装包的路劲,即:/data/data/<packagename>/…
而且这两个路劲又是有权限保护的不能直接访问;
问题解决方法:
先从网络下载so文件到手机目录(如:/test/device/test.so) –> 将test.so加载到内存(ByteArrayOutputStream) –> 然后保存到对用安装包目录;
具体代码如下:
try {
String localPath = Environment.getExternalStorageDirectory() + path;
Log.v(TAG, "LazyBandingLib localPath:" + localPath);
String[] tokens = mPatterns.split(path);
if (null == tokens || tokens.length <= 0
|| tokens[tokens.length - 1] == "") {
Log.v(TAG, "非法的文件路径!");
return -3;
}
// 开辟一个输入流
File inFile = new File(localPath);
// 判断需加载的文件是否存在
if (!inFile.exists()) {
// 下载远程驱动文件
Log.v(TAG, inFile.getAbsolutePath() + " is not fond!");
return 1;
}
FileInputStream fis = new FileInputStream(inFile);
File dir = context.getDir("libs", Context.MODE_PRIVATE);
// 获取驱动文件输出流
File soFile = new File(dir, tokens[tokens.length - 1]);
if (!soFile.exists()) {
Log.v(TAG, "### " + soFile.getAbsolutePath() + " is not exists");
FileOutputStream fos = new FileOutputStream(soFile);
Log.v(TAG, "FileOutputStream:" + fos.toString() + ",tokens:"
+ tokens[tokens.length - 1]);
// 字节数组输出流,写入到内存中(ram)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
// 从内存到写入到具体文件
fos.write(baos.toByteArray());
// 关闭文件流
baos.close();
fos.close();
}
fis.close();
Log.v(TAG, "### System.load start");
// 加载外设驱动
System.load(soFile.getAbsolutePath());
Log.v(TAG, "### System.load End");
return 0;
} catch (Exception e) {
Log.v(TAG, "Exception " + e.getMessage());
e.printStackTrace();
return -1;
}
10. Android studio如何通过jni调用openssl生成的.so动态链接库
(1)老版本,方法如下:
task NativeLibs(type: Copy) {
from(new File(project(':MyProject').buildDir, 'native-libs')) { include '**/*.so' }
into new File(buildDir, 'native-libs')
}
tasks.withType(Compile) { compileTask -> compileTask.dependsOn NativeLibs }
clean.dependsOn 'cleanCopyNativeLibs'
tasks.withType(com.android.build.gradle.PackageApplicationTask) { pkgTask ->
pkgTask.jniDir new File(buildDir, 'native-libs')
}
(2)新版本三种方法:
(2.1)打包前先生成.Jar文件后自动解包到apk文件
task nativeLibsToJar(type: Zip, description: 'create a jar archive of the native libs') {
destinationDir file("$buildDir/native-libs")
baseName 'native-libs'
extension 'jar'
from fileTree(dir: 'libs', include: '**/*.so')
into 'lib/'
}
tasks.withType(Compile) {
compileTask -> compileTask.dependsOn(nativeLibsToJar)
}
下面一句话就是打包生成目录(build\native-libs)中的.jar文件
compile fileTree(dir: "$buildDir/native-libs", include: 'native-libs.jar')
(2.2)手动生成.Jar文件后自动解包到apk文件
这个方式需要自己手动进行.SO文件压缩,具体步骤为:将所有需要使用的.so文件压缩为.zip文件(zip中的文件目录结构为: lib/armeabi/*.so)然后把zip文件后缀改为.Jar然后放到libs生成apk就ok
默认就是自动打包所有.Jar文件:
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
}
(2.3)这也就是现在正在使用的方式(推荐) ,其实无非就是把.SO文件打包到APK的lib文件夹中,假如仔细阅读了Gradle的使用方法,自然就知道其实Gradle官方在新版已经自动实现了打包.SO文件的.很简单级就是在配置的android节点下加入下面的内容就ok:
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
其他地方无需修改,整个项目的配置文件如下:
apply plugin: 'android'
android {
compileSdkVersion 19
buildToolsVersion "19.0.0"
defaultConfig {
minSdkVersion 16
targetSdkVersion 19
versionCode 1
versionName "1.0"
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}