欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

动态加载so库

程序员文章站 2022-05-28 11:16:11
...

简介

前几天做一个视频播放的功能,用到了bilibili开源ijkplayer播放器的(集成ijkplayer),功能确实强大,但就是用到的ffmpeg解码库太大,不得已只能只能将so文件拿出来,通过动态的方式来加载。

什么是动态加载?

就是讲so文件不打包进apk,在安装完应用打开app的时候通过后台下载so库,将下载下来的so文件再写入到app里面。
首先我们要知道,Android加载so文件的方式有两种:

  • System.loadLibrary
  • System.load

它们都可以用来装载库文件,但是System.load参数必须为库文件的绝对路径,可以是任意路径;System.loadLibrary参数为库文件名,不包含库文件的扩展名,必须是在JVM属性Java.library.path所指向的路径中,路径可以通过System.getProperty('java.library.path') 获得。所有动态加载的时候我们不能用System.loadLibrary,只能用System.load来加载。


介绍

  1. 下载so文件
  2. 安装
  3. load加载

    其中下载没什么说的,不过推荐Netroid,下载一般下载到sdcard,前面说过System.load参数为绝对路径,也就是说你可以直接加载sdcard上面的so文件,但是在sdcard上可能会被用户删除或者修改,所以我建议将so文件copy到我们app的目录(data/data/包名/app_lib)下面

   public static void loadSoFile(Context context) {
        File dir = context.getDir("libs", Context.MODE_PRIVATE);
        if (!isLoadSoFile(dir)) {
            copy(formPath, dir.getAbsolutePath());
        }
    }

  public static boolean isLoadSoFile(File dir) {
        File[] currentFiles;
        currentFiles = dir.listFiles();
        boolean hasJkffmpeg = false;
        if (currentFiles == null) {
            return false;
        }
        for (int i = 0; i < currentFiles.length; i++) {
           if (currentFiles[i].getName().contains(DuduUtil.SoFile.IJKFFMPEG)) {
                hasJkffmpeg = true;
            }
        }
        return hasJkffmpeg;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
动态加载so库
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

先判断app_lib下有没有我们需要加载的so文件,如果没有的话复制到指定目录,其中formPath是下载到sdcard上so文件的路径

ublic 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 {
                //如果当前项为文件则进行文件拷贝
                Log.e(TAG, "path:" + currentFiles[i].getPath());
                Log.e(TAG, "name:" + currentFiles[i].getName());
                if (currentFiles[i].getName().contains(".so")) {
                    int id = copySdcardFile(currentFiles[i].getPath(), toFile + File.separator + currentFiles[i].getName());
                    Log.e(TAG, "id:" + id);
                }
            }
        }
        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;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
动态加载so库
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

最后一步,load

File dir = context.getDir("libs", Context.MODE_PRIVATE);
File[] currentFiles;
currentFiles = dir.listFiles();
for (int i = 0; i < currentFiles.length; i++) {
     Log.e(TAG, "#:" + currentFiles[i].getAbsolutePath());
     // System.load(currentFiles[i].getAbsolutePath());

 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
动态加载so库
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

问题

  1. java.lang.UnsatisfiedLinkError: dlopen failed: “XXXX.so” is 32-bit instead of 64-bit
    大体原因是手机cpu为64位,并且在app中你配置了加载64位(arm64-v8a),所有系统会去加载64位路径下的so库,但是你load的so库却是32位,解决方法有两种:1、将32为库换成64位,2、只加载32位库,在build.gradle中配置,armeabi,armeabi-v7a,x86位32位,arm64-v8a,x86_64是64位。
defaultConfig {
        applicationId "xxxx"
        minSdkVersion 10
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"

        ndk {
            abiFilters "armeabi","armeabi-v7a","x86"
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
动态加载so库
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2.java.lang.UnsatisfiedLinkError: Couldn’t load ijkffmpeg from loader dalvik.system.DexClassLoader[DexPathList[]]: findLibrary returned null

加载时注意加载so文件的顺序,遇到的问题是load ijkffmpeg.so文件时需要先load另外一个so文件,但是我先load了ffmpeg,所以报了 couldn’t load异常