简介

前几天做一个视频播放的功能,用到了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;
    }

先判断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;
        }
    }

最后一步,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. 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"
        }
    }

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异常