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

ClaC12 7年前
   <p>我想对于静态加载 so 库文件,大家都已经很熟悉了,这里就不多说了。在 Android 开发中调用动态库文件(*.so)都是通过 jni 的方式,而静态加载往往是在 apk 或 jar 包中调用so文件时,都要将对应 so 文件打包进 apk 或 jar 包。</p>    <h2>动态加载的优点</h2>    <p>静态加载,不灵活,apk 包有可能大。所以采用动态加载 so 库文件,有以下几点好处:</p>    <ol>     <li>灵活,so 文件可以动态加载,不是绑定死的,修改方便,so 库有问题,我们可以动态更新。</li>     <li>so 库文件很大的话,采用动态加载可以减少 apk 的包,变小。</li>     <li>其实我们常用第三方 so 库,单个可能没问题,如果多个第三方 so 库文件,同时加载可能会出现冲突,而动态加载就能够解决这一问题。</li>    </ol>    <h2>注意路径陷阱</h2>    <p>动态加载 so 库文件,并不是说可以把文件随便存放到某个 sdcard 文件目录下,这样做既不安全,系统也加载不了。</p>    <p>我们在 Android 中加载 so 文件,提供的 API 如下:</p>    <pre>  <code class="language-java">//第一种,pathName 库文件的绝对路径  void System.load(String pathName);  //第二种,参数为库文件名,不包含库文件的扩展名,必须是在JVM属性Java.library.path所指向的路径中,路径可以通过System.getProperty('java.library.path') 获得  voidloadLibrary(String libname)  </code></pre>    <p>注意:而这里加载的文件路径只能加载两个目录下的 so 文件。那就是:</p>    <ol>     <li>/system/lib</li>     <li>应用程序安装包的路径,即:/data/data/packagename/…</li>    </ol>    <p>所以,so 文件动态加载的文件目录不能随便放。这是需要注意的一点。</p>    <h2>实现思路</h2>    <p>既然使用动态加载的好处和陷阱我们都大致了解了,那就可以在实现的时候,注意陷阱就可以了。那基本思路如下:</p>    <ol>     <li>网络下载 so 文件到指定目录</li>     <li>从指定下载的目录复制 copy so文件到可动态加载的文件目录下,比如:/data/data/packagename/…</li>     <li>配置 gradle ,指定 cpu 架构</li>     <li>load 加载</li>    </ol>    <p>第一步,我们这里可以简单忽略,假设我们把 so 文件下载到了 /mnt/sdcard/armeabi 目录下。</p>    <h3>复制目录到包路径下</h3>    <p>那我们就应该把 /mnt/sdcard/armeabi 目录下的 so 文件,复制到 应用的包路径下。</p>    <pre>  <code class="language-java">/**   * Created by loonggg on 2017/3/29.   */    public classSoFile{      /**       * 加载 so 文件       * @param context       * @param fromPath 下载到得sdcard目录       */      publicstaticvoidloadSoFile(Context context, String fromPath){          File dir = context.getDir("libs", Context.MODE_PRIVATE);          if (!isLoadSoFile(dir)) {              copy(fromPath, dir.getAbsolutePath());          }      }        /**       * 判断 so 文件是否存在       * @param dir       * @return       */      publicstaticbooleanisLoadSoFile(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       */      publicstaticintcopy(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;      }          //文件拷贝      //要复制的目录下的所有非子目录(文件夹)文件拷贝      publicstaticintcopySdcardFile(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;          }      }  }  </code></pre>    <h3>配置 grade 指定 cpu 架构</h3>    <p>我们都知道,在使用 so 文件的时候,so 库类型和 CPU 架构类型,要一致,否则是会报错的。原因很简单,不同 CPU 架构的设备需要用不同类型 so 库。CPU架构有如下几种类型:ARMv5,ARMv7,x86,MIPS,ARMv8,MIPS64 和 x86_64。如果要适配很多手机,就需要在不同的类型下,放置对应的 so 文件。</p>    <p>配置方法如下:</p>    <pre>  <code class="language-java">defaultConfig {          applicationId "xxxx"          minSdkVersion 17          targetSdkVersion 25          versionCode 1          versionName "1.0"            ndk {              abiFilters "armeabi","armeabi-v7a","x86"          }      }  </code></pre>    <h3>load 加载 so 文件</h3>    <p>复制到可加载使用的包路径下后,配置完 gradle 之后,就可以使用 load API 调用了。</p>    <pre>  <code class="language-java">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());  }  </code></pre>    <p>这样,我们就实现了动态加载 so 文件。</p>    <p> </p>    <p>来自:http://godcoder.me/2017/03/29/Android 开发中如何动态加载so库文件/</p>    <p> </p>