Android 开发中如何动态加载 so 库文件

我想对于静态加载 so 库文件,大家都已经很熟悉了,这里就不多说了。在 Android 开发中调用动态库文件(*.so)都是通过 jni 的方式,而静态加载往往是在 apk 或 jar 包中调用so文件时,都要将对应 so 文件打包进 apk 或 jar 包。

动态加载的优点

静态加载,不灵活,apk 包有可能大。所以采用动态加载 so 库文件,有以下几点好处:

  1. 灵活,so 文件可以动态加载,不是绑定死的,修改方便,so 库有问题,我们可以动态更新。
  2. so 库文件很大的话,采用动态加载可以减少 apk 的包,变小。
  3. 其实我们常用第三方 so 库,单个可能没问题,如果多个第三方 so 库文件,同时加载可能会出现冲突,而动态加载就能够解决这一问题。

注意路径陷阱

动态加载 so 库文件,并不是说可以把文件随便存放到某个 sdcard 文件目录下,这样做既不安全,系统也加载不了。

我们在 Android 中加载 so 文件,提供的 API 如下:

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

注意:而这里加载的文件路径只能加载两个目录下的 so 文件。那就是:

  1. /system/lib
  2. 应用程序安装包的路径,即:/data/data/packagename/…

所以,so 文件动态加载的文件目录不能随便放。这是需要注意的一点。

实现思路

既然使用动态加载的好处和陷阱我们都大致了解了,那就可以在实现的时候,注意陷阱就可以了。那基本思路如下:

  1. 网络下载 so 文件到指定目录
  2. 从指定下载的目录复制 copy so文件到可动态加载的文件目录下,比如:/data/data/packagename/…
  3. 配置 gradle ,指定 cpu 架构
  4. load 加载

第一步,我们这里可以简单忽略,假设我们把 so 文件下载到了 /mnt/sdcard/armeabi 目录下。

复制目录到包路径下

那我们就应该把 /mnt/sdcard/armeabi 目录下的 so 文件,复制到 应用的包路径下。

/**
 * Created by loonggg on 2017/3/29.
 */
public class SoFile {
    /**
     * 加载 so 文件
     * @param context
     * @param fromPath 下载到得sdcard目录
     */
    public static void loadSoFile(Context context, String fromPath) {
        File dir = context.getDir("libs", Context.MODE_PRIVATE);
        if (!isLoadSoFile(dir)) {
            copy(fromPath, dir.getAbsolutePath());
        }
    }

    /**
     * 判断 so 文件是否存在
     * @param dir
     * @return
     */
    public static boolean isLoadSoFile(File dir) {
        File[] currentFiles;
        currentFiles = dir.listFiles();
        boolean hasSoLib = false;
        if (currentFiles == null) {
            return false;
        }
        for (int i = 0; i < currentFiles.length; i++) {
            if (currentFiles[i].getName().contains("libwedsa23")) {
                hasSoLib = true;
            }
        }
        return hasSoLib;
    }

    /**
     *
     * @param fromFile 指定的下载目录
     * @param toFile 应用的包路径
     * @return
     */
    public static int copy(String fromFile, String toFile) {
        //要复制的文件目录
        File[] currentFiles;
        File root = new File(fromFile);
        //如同判断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()) {
                //如果当前项为子目录 进行递归
                copy(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;
    }


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

配置 grade 指定 cpu 架构

我们都知道,在使用 so 文件的时候,so 库类型和 CPU 架构类型,要一致,否则是会报错的。原因很简单,不同 CPU 架构的设备需要用不同类型 so 库。CPU架构有如下几种类型:ARMv5,ARMv7,x86,MIPS,ARMv8,MIPS64 和 x86_64。如果要适配很多手机,就需要在不同的类型下,放置对应的 so 文件。 配置方法如下:

defaultConfig {
        applicationId "xxxx"
        minSdkVersion 17
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"

        ndk {
            abiFilters "armeabi","armeabi-v7a","x86"
        }
    }

load 加载 so 文件

复制到可加载使用的包路径下后,配置完 gradle 之后,就可以使用 load API 调用了。

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

这样,我们就实现了动态加载 so 文件。

原文发布于微信公众号 - 非著名程序员(non-famous-coder)

原文发表时间:2017-03-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏SDNLAB

OpenDaylight Lithium版本简单应用及流表操作指南

OpenDaylight(以下简写为ODL)的Lithium(锂)版本的最新版Lithium-SR2已经与2015年10月8日发布,具体详情可参考ODL官网。L...

5018
来自专栏乐沙弥的世界

快速体验mongoDB分片

1、mongodb分片的实质是将数据分散到不同的物理机器,以分散IO,提供并发与吞吐量 2、mongodb分片依赖于片键,即任意一个需要开启的集合都需要创建...

1652
来自专栏Hellovass 的博客

社交化分享组件踩坑

问题是这样的,项目里的社交化分享是基于 UMShare 封装成的一个 ShareLib module,为了让这个 module 对调用者说更透明,我将 WXEn...

3305
来自专栏恰童鞋骚年

自己动手写三层代码生成器学习总结

  今天看了下老杨的视频教程,写了一把三层代码生成器,理解了一下简单的代码生成器是如何实现的,其重点就在于数据库系统视图。

714
来自专栏非典型程序猿

Golang任务队列machinery使用与源码剖析(二)

在Golang任务队列machinery使用与源码剖析(一)一文中,我们主要对golang中任务队列machinery的设计结构以及具体模块的功能与源码实现进行...

1.5K8
来自专栏有趣的django

Django rest framework源码分析(3)----节流

添加节流 自定义节流的方法  限制60s内只能访问3次 (1)API文件夹下面新建throttle.py,代码如下: # utils/throttle.py ...

5208
来自专栏琯琯博客

Yii2 学习笔记之 GridView DetailView

3536
来自专栏chenssy

【死磕Sharding-jdbc】---读写分离

先执行 sharding-jdbc-example-config-spring-masterslave模块中的的SQL脚本 all_schema.sql,这里有...

1714
来自专栏郭少华

Spring boot Mybatis-XML方式使用Druid连接池(四)

3804
来自专栏葡萄城控件技术团队

七天学会ASP.NET MVC (六)——线程问题、异常处理、自定义URL

? 本节又带了一些常用的,却很难理解的问题,本节从文件上传功能的实现引出了线程使用,介绍了线程饥饿的解决方法,异常处理方法,了解RouteTable自定义路...

27810

扫码关注云+社区

领取腾讯云代金券