动态加载so库文件

来源:互联网 发布:淘宝 买飞机 编辑:程序博客网 时间:2024/04/27 23:38

http://blog.csdn.net/zhangyongfeiyong/article/details/51603663

使用动态加载so库文件可以减小apk文件的大小,如:so库文件较大时,使用动态加载,在需要使用so库文件或者满足其他条件时,提示用户下载或自动下载,这样apk文件的大小就可以大大降低。

Android加载so库文件的机制:

加载so库文件基本都用的System类的loadLibrary方法,其实System类中还有一个load方法。

[java] view plain copy
  1. /** 
  2.      * See {@link Runtime#load}. 
  3.      */  
  4.     public static void load(String pathName) {  
  5.         Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());  
  6.     }  
  7.   
  8.     /** 
  9.      * See {@link Runtime#loadLibrary}. 
  10.      */  
  11.     public static void loadLibrary(String libName) {  
  12.         Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());  
  13.     }  

先看看loadLibrary,这里调用了Runtime的loadLibrary,进去一看,又是动态加载熟悉的ClassLoader了(这里也佐证了so库的使用就是一种动态加载的说法)。

[java] view plain copy
  1. /* 
  2.  * Searches for and loads the given shared library using the given ClassLoader. 
  3.  */  
  4. void loadLibrary(String libraryName, ClassLoader loader) {  
  5.     if (loader != null) {  
  6.         String filename = loader.findLibrary(libraryName);  
  7.         String error = doLoad(filename, loader);  
  8.         return;  
  9.     }  
  10.     ……  
  11. }  


看样子就像是通过库名称获取一个文件路径,再调用doLoad方法加载这个文件,先看看loader.findLibrary(libraryName)

[java] view plain copy
  1. protected String findLibrary(String libName) {  
  2.     return null;  
  3. }  


ClassLoader只是一个抽象类,它的大部分工作都在BaseDexClassLoader类中实现,进去看看

[java] view plain copy
  1. public class BaseDexClassLoader extends ClassLoader {  
  2.     public String findLibrary(String name) {  
  3.         throw new RuntimeException("Stub!");  
  4.     }  
  5. }  


不对啊,这里只是抛了个异常,什么都没做。。。

其实这里有个误区,Android SDK自带的源码其实只是给我们开发者参考的,基本只是一些常用的类,Google不会把整个Android系统的源码都放在这里来,因为整个项目非常大,ClassLoader类平时我们接触得少,所以它具体实现的源码并没有打包进SDK里。

到Android在线源码中可以看到:

[java] view plain copy
  1. @Override  
  2.     public String findLibrary(String name) {  
  3.         return pathList.findLibrary(name);  
  4.     }  


再看DexPathList类

[java] view plain copy
  1. /** 
  2.      * Finds the named native code library on any of the library 
  3.      * directories pointed at by this instance. This will find the 
  4.      * one in the earliest listed directory, ignoring any that are not 
  5.      * readable regular files. 
  6.      * 
  7.      * @return the complete path to the library or {@code null} if no 
  8.      * library was found 
  9.      */  
  10.     public String findLibrary(String libraryName) {  
  11.         String fileName = System.mapLibraryName(libraryName);  
  12.         for (File directory : nativeLibraryDirectories) {  
  13.             File file = new File(directory, fileName);  
  14.             if (file.exists() && file.isFile() && file.canRead()) {  
  15.                 return file.getPath();  
  16.             }  
  17.         }  
  18.         return null;  
  19.     }  


到这里已经明朗了,根据传进来的libName,扫描apk内部的nativeLibrary目录,获取并返回内部so库文件的完整路径filename。再回到Runtime类,获取filename后调用了doLoad方法,看看

[java] view plain copy
  1. private String doLoad(String name, ClassLoader loader) {  
  2.         String ldLibraryPath = null;  
  3.         String dexPath = null;  
  4.         if (loader == null) {  
  5.             ldLibraryPath = System.getProperty("java.library.path");  
  6.         } else if (loader instanceof BaseDexClassLoader) {  
  7.             BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;  
  8.             ldLibraryPath = dexClassLoader.getLdLibraryPath();  
  9.         }  
  10.         synchronized (this) {  
  11.             return nativeLoad(name, loader, ldLibraryPath);  
  12.         }  
  13.     }  


到这里就彻底清楚了,调用Native方法nativeLoad,通过完整的so库路径filename,把目标so库加载进来。

说了半天还没有进入正题呢,不过我们可以想到,如果使用loadLibrary方法,到最后还是要找到目标so库的完整路径,再把so库加载进来,那我们能不能一开始就给出so库的完整路径,然后直接加载进来呢?我们猜想load方法就是干这个的,看看

[java] view plain copy
  1. void load(String absolutePath, ClassLoader loader) {  
  2.         if (absolutePath == null) {  
  3.             throw new NullPointerException("absolutePath == null");  
  4.         }  
  5.         String error = doLoad(absolutePath, loader);  
  6.         if (error != null) {  
  7.             throw new UnsatisfiedLinkError(error);  
  8.         }  
  9.     }  


我嘞个去,一上来就直接来到doLoad方法了,这证明我们的猜想可能是正确的,那么在实际项目中测试验证吧。

我们先把so文件放到asset中,再复制到内部存储,再使用load方法把其加载进来。

例子结构

这样做的原因是:native方法是包名+方法名的拼接,若不这样会找不到目标方法。

[java] view plain copy
  1. public class Hello {  
  2.   
  3.     public static native String hello();  
  4. }  


 

[java] view plain copy
  1. public class MainActivity extends Activity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.           
  8.         File dir = getDir("jniLibs", Context.MODE_PRIVATE);  
  9.         File distFile = new File(dir.getAbsolutePath() + File.separator + "libHello_jni.so");  
  10.         if (copyFileFromAssets(this"libHello_jni.so", distFile.getAbsolutePath())) {  
  11.             System.load(distFile.getAbsolutePath());  
  12.             String hello = Hello.hello();  
  13.             System.out.println("hello:" + hello);  
  14.         }  
  15.     }  
  16.   
  17.     private boolean copyFileFromAssets(Context context, String fileName, String path) {  
  18.         boolean copyFinish = false;  
  19.         try {  
  20.             InputStream is = context.getAssets().open(fileName);  
  21.             File file = new File(path);  
  22.             file.createNewFile();  
  23.             FileOutputStream fos = new FileOutputStream(file);  
  24.             byte[] temp = new byte[1024];  
  25.             int i = 0;  
  26.             while ((i = is.read(temp)) > 0) {  
  27.                 fos.write(temp, 0, i);  
  28.             }  
  29.             fos.close();  
  30.             is.close();  
  31.             copyFinish = true;  
  32.         } catch (Exception e) {  
  33.             e.printStackTrace();  
  34.         }  
  35.         return copyFinish;  
  36.     }  
  37. }  


当然这里还可以使用反射来调用,方法名必须使用全方法名。

上面例子只是为了说明能动态加载so库,copy操作没有做必要的判断。另外so库可以从网络下载后再拷贝到内部存储中。