Android 静态和动态的调用so库(JNI)

简介: 转载请标明出处: http://blog.csdn.net/djy1992/article/details/78890252 本文出自:【奥特曼超人的博客】静态编译不再多说,可以查看这篇文章:《Android6.

转载请标明出处:
http://blog.csdn.net/djy1992/article/details/78890252
本文出自:【奥特曼超人的博客】

静态编译不再多说,可以查看这篇文章:《Android6.0 NDK 和 .So 之间的关系》

优点

为什么我们需要动态加载?因为静态加载中CPU的文件夹我们可能需要兼容的话需要放在不同arm文件夹下,那么就会导致apk 包体过大,还有安卓Android SDK系统版本导致的差异,所以我们采用动态加载 so 库文件的话最主要的好处就是可以减小包体,其次,我们去修改so库时也比较方便,因为是动态的,我们可以动态的同步更新。

还有另外一个原因就是,我们常常会用到第三方的 so 库,如果单个库可能没问题,如果多个第三方 so 库文件,同时加载可能会出现冲突,比如说腾讯的YSDK和BUGLY,而动态加载就能够很好的解决这个问题。

实现步骤

基本步骤如下:

  • 第一步,从网络下载 so库文件到指定目录。
  • 然后,从指定的目录中 复制 copy so库文件到自身包名目录下,比如:/data/data/packagename/…
    ,当然我们需要配置 gradle ,这一点很多人会不记得操作,记得需要指定 cpu 的架构和可动态加载的文件。如(/data/data/immqy/miqiyun/...)。
  • 最后load 加载。

    那我们如何操作?
    第一步:网络下载so库,其实就是下载文件,比较简单,这里不再过多的阐述,可查看文件下载的《Android 异步文件下载》
    第二步:我们假设指定目录为SdCard中的IMMQY文件夹,那么路径为:/mnt/sdcard/IMMQY/,把此路径复制到包下的可运行路径,代码如下:

/**
 *  
 * Author:Karl-Dujinyang
 * 
 * 2016/11/10
 */
public class SoFile {

   /**
     * 加载 so 文件
     * @param context
     * @param fromPath 下载到得sdcard目录
     */
    public static void loadSoFile(Context context, String fromPath) {
        File dirs = context.getDir("libs", Context.MODE_PRIVATE);
        if (!isLoadSoFiles(dirs)) {
            copyFile(fromPath, dir.getAbsolutePath());
        }
    }

    /**
     * 判断immqy so 文件是否存在
     * @param dir
     * @param name "libimmqy" so库
     * @return boolean
     */
    public static boolean isLoadSoFiles(String name,File dirs) {
        boolean getSoLib = false;
        File[] currentFiles;
        currentFiles = dirs.listFiles();
        if (currentFiles == null) {
            return false;
        }
        for (int i = 0; i < currentFiles.length; i++) {
            if (currentFiles[i].getName().contains(name)) {
                getSoLib = true;
            }
        }
        return getSoLib;
    }


    /**
     * 要复制的目录下的所有非子目录(文件夹)文件拷贝
     * @param fromFile 指定的下载目录
     * @param toFile 应用的包路径
     * @return int
     */ 
    public static int copySdcardFile(String fromFiles, String toFile) {
        try {
            FileInputStream fileInput = new FileInputStream(fromFiles);
            FileOutputStream fileOutput = new FileOutputStream(toFile);
            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024*1];
            int len = -1;
            while ((len = fileInput.read(buffer)) != -1) {
                byteOut.write(buffer, 0, len);
            }
            // 从内存到写入到具体文件
            fileOutput.write(baos.toByteArray());
            // 关闭文件流
            byteOut.close();
            fileOutput.close();
            fileInput.close();
            return 0;
        } catch (Exception ex) {
            return -1;
        }
    }



    /**
     * 
     * @param fromFile 指定的下载目录
     * @param toFile 应用的包路径
     * @return int
     */
    public static int copyFile(String fromFiles, String toFile) {
        //要复制的文件目录
        File[] currentFiles;
        File root = new File(fromFiles);
        //如同判断SD卡是否存在或者文件是否存在,如果不存在则 return出去
        if (!root.exists()) {
            return -1;
        }
        //如果存在则获取当前目录下的全部文件 填充数组
        currentFiles = root.listFiles();

        //目标目录
        File targetDir = new File(toFile);
        //创建目录
        if (!targetDir.exists()) {
            targetDir.mkdirs();
        }
        //遍历要复制该目录下的全部文件
        for (int i = 0; i < currentFiles.length; i++) {
            if (currentFiles[i].isDirectory()) {
                //如果当前项为子目录 进行递归
                copyFile(currentFiles[i].getPath() + "/", toFile + currentFiles[i].getName() + "/");
            } else {
                //如果当前项为文件则进行文件拷贝
                if (currentFiles[i].getName().contains(".so")) {
                    int id = copySdcardFile(currentFiles[i].getPath(), toFile + File.separator + currentFiles[i].getName());
                }
            }
        }
        return 0;
    }
}

代码写入到目录后,我们就需要配置 grade 了。
配置方法如下:

defaultConfig {
        applicationId "immqy"
        minSdkVersion 16
        targetSdkVersion 26
        versionCode 2
        versionName "2.2" 
        ndk {
            abiFilters "armeabi","armeabi-v7a","x86"
        }
    }

提示:CPU架构有如下几种类型:ARMv5,ARMv7,ARMV7A,x86,MIPS,ARMv8,MIPS64 和 x86_64,so 库类型和 CPU 架构类型要一致,否则是会报错的。

配置完成后,就剩下最后一步了。
第三步:load代码加载进行调用,很简单,代码如下:

File dir = getApplicationContext().getDir("libs", Context.MODE_PRIVATE); 
File[] currFiles = null; 
currFiles = dir.listFiles(); 
for (int i = 0; i < currFiles.length; i++) {
     System.load(currFiles[i].getAbsolutePath()); 
} 

需要注意的是,getApplicationContext引用不能为空,所以最好还是先判断下上下文对象是否为空。

这样就可以使用 load API 调用动态加载 so 文件了。

踩坑

有人会想,文件下载过来是否安全,其实是否定的,大家也可以把文件加密,或者把文件路径丢深一点。
我们在 Android 中加载 so 文件,提供的 API 如下:

  1. 第一种,pathName 库文件的绝对路径 void System.load(String pathName)
  2. 第二种,参数为库文件名,不包含库文件的扩展名,必须是在JVM属性 Java.library.path 所指向的路径中,路径可以通过 System.getProperty('java.library.path'), 获得 void loadLibrary(String libname)

注意:而这里加载的文件路径只能加载两个目录下的 so 文件,那就是:/system/lib和/data/data/packagename/…(应用程序安装包下的路径)
所以,so 文件动态加载的文件目录不能随便放。这是需要注意的一点。

|| 版权声明:本文为博主杜锦阳原创文章,转载请注明出处。

相关文章
|
2月前
|
存储 缓存 Android开发
安卓Jetpack Compose+Kotlin, 使用ExoPlayer播放多个【远程url】音频,搭配Okhttp库进行下载和缓存,播放完随机播放下一首
这是一个Kotlin项目,使用Jetpack Compose和ExoPlayer框架开发Android应用,功能是播放远程URL音频列表。应用会检查本地缓存,如果文件存在且大小与远程文件一致则使用缓存,否则下载文件并播放。播放完成后或遇到异常,会随机播放下一首音频,并在播放前随机设置播放速度(0.9到1.2倍速)。代码包括ViewModel,负责音频管理和播放逻辑,以及UI层,包含播放和停止按钮。
180 0
|
3月前
|
Android开发
Android JNI与CAN通信遇到的问题总结
Android JNI与CAN通信遇到的问题总结
88 1
|
17天前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
很多文章都介绍了FTPClient如何连接ftp服务器,但却很少有人说如何连接一台开了SSL认证的ftp服务器,现在代码来了。
42 2
|
23天前
|
存储 数据库 Android开发
🔥Android Jetpack全解析!拥抱Google官方库,让你的开发之旅更加顺畅无阻!🚀
【7月更文挑战第28天】在Android开发中追求高效稳定的路径?Android Jetpack作为Google官方库集合,是你的理想选择。它包含多个独立又协同工作的库,覆盖UI到安全性等多个领域,旨在减少样板代码,提高开发效率与应用质量。Jetpack核心组件如LiveData、ViewModel、Room等简化了数据绑定、状态保存及数据库操作。引入Jetpack只需在`build.gradle`中添加依赖。例如,使用Room进行数据库操作变得异常简单,从定义实体到实现CRUD操作,一切尽在掌握之中。拥抱Jetpack,提升开发效率,构建高质量应用!
41 4
|
1月前
|
Java API 开发工具
一个专为Android平台设计的高度可定制的日历库
Calendar库是Android开发的工具,支持RecyclerView和Compose,提供高度定制的日历组件。功能包括:单选/多选/范围日期选择、周/月模式、禁用特定日期、设置边界、自定义视图、每周起始日、滚动方式、热力图、标题和脚注、滑动导航及兼容低版本API。示例应用和源码可在GitHub找到,通过Gradle集成,有详细文档指导。
47 16
|
2月前
|
Android开发
Android中如何动态的调整Dialog的背景深暗
在Android开发中,Dialog和DialogFragment可通过设置`Window`的`backgroundDimAmount`来控制背景变暗,突出对话框。在DialogFragment的`onCreateDialog`或`onViewCreated`中,获取`Dialog`的`Window`,设置`LayoutParams.dimAmount`(例如0.5f)并添加`FLAG_DIM_BEHIND`标志。要动态调整,可保存`LayoutParams`并在需要时更新。对于Dialog,创建时直接设置同样属性。还可以通过定义主题样式设置背景模糊程度。
42 7
|
3月前
|
安全 Linux Android开发
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
该文介绍了如何在Linux服务器上交叉编译Android的FFmpeg库以支持HTTPS视频播放。首先,从GitHub下载openssl源码,解压后通过编译脚本`build_openssl.sh`生成64位静态库。接着,更新环境变量加载openssl,并编辑FFmpeg配置脚本`config_ffmpeg_openssl.sh`启用openssl支持。然后,编译安装FFmpeg。最后,将编译好的库文件导入App工程的相应目录,修改视频链接为HTTPS,App即可播放HTTPS在线视频。
73 3
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
|
2月前
|
XML Java Android开发
Android RecyclerView用代码动态设置item的selector
Android RecyclerView用代码动态设置item的selector
27 0
|
2月前
|
存储 API 开发工具
kotlin安卓开发,如何获取设备的唯一id, 有哪些开源库
在Kotlin的Android开发中,获取设备唯一ID的方法包括不稳定的ANDROID_ID、需要权限的IMEI、使用UUID与SharedPreference结合,以及考虑隐私的Firebase Installations ID和Advertising ID。由于隐私问题和Google Play政策,IMEI和ANDROID_ID不推荐作为长期唯一标识。推荐使用UUID(首次安装时生成并存储),或在涉及广告时使用Advertising ID(需用户同意),而Firebase Installations ID则提供了一种合规的设备标识选项。在选择方法时,必须遵守隐私指南和政策。
241 0
|
2月前
|
Java API Android开发
安卓开发app 调用usb 摄像头 需要用到哪个库
在安卓开发中,调用USB摄像头常常使用libuvc库,这是一个跨平台处理USB视频设备的库。有多个基于libuvc的开源项目简化了在安卓上的使用,如UVCCamera和Android EasyCap UVC。例如,UVCCamera提供了一个更简单的接口来访问USB摄像头,并且可以在Jetpack Compose中显示预览。开发者可以参考官方文档、开源项目以及相关教程和资源来学习和实现这一功能。
226 0