http://blog.csdn.net/zhangyongfeiyong/article/details/51603663
使用动态加载so库文件可以减小apk文件的大小,如:so库文件较大时,使用动态加载,在需要使用so库文件或者满足其他条件时,提示用户下载或自动下载,这样apk文件的大小就可以大大降低。
Android加载so库文件的机制:
加载so库文件基本都用的System类的loadLibrary方法,其实System类中还有一个load方法。
-
-
-
- public static void load(String pathName) {
- Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());
- }
-
-
-
-
- public static void loadLibrary(String libName) {
- Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
- }
先看看loadLibrary,这里调用了Runtime的loadLibrary,进去一看,又是动态加载熟悉的ClassLoader了(这里也佐证了so库的使用就是一种动态加载的说法)。
-
-
-
- void loadLibrary(String libraryName, ClassLoader loader) {
- if (loader != null) {
- String filename = loader.findLibrary(libraryName);
- String error = doLoad(filename, loader);
- return;
- }
- ……
- }
看样子就像是通过库名称获取一个文件路径,再调用doLoad方法加载这个文件,先看看loader.findLibrary(libraryName)
- protected String findLibrary(String libName) {
- return null;
- }
ClassLoader只是一个抽象类,它的大部分工作都在BaseDexClassLoader类中实现,进去看看
- public class BaseDexClassLoader extends ClassLoader {
- public String findLibrary(String name) {
- throw new RuntimeException("Stub!");
- }
- }
不对啊,这里只是抛了个异常,什么都没做。。。
其实这里有个误区,Android SDK自带的源码其实只是给我们开发者参考的,基本只是一些常用的类,Google不会把整个Android系统的源码都放在这里来,因为整个项目非常大,ClassLoader类平时我们接触得少,所以它具体实现的源码并没有打包进SDK里。
到Android在线源码中可以看到:
- @Override
- public String findLibrary(String name) {
- return pathList.findLibrary(name);
- }
再看DexPathList类
-
-
-
-
-
-
-
-
-
- public String findLibrary(String libraryName) {
- String fileName = System.mapLibraryName(libraryName);
- for (File directory : nativeLibraryDirectories) {
- File file = new File(directory, fileName);
- if (file.exists() && file.isFile() && file.canRead()) {
- return file.getPath();
- }
- }
- return null;
- }
到这里已经明朗了,根据传进来的libName,扫描apk内部的nativeLibrary目录,获取并返回内部so库文件的完整路径filename。再回到Runtime类,获取filename后调用了doLoad方法,看看
- private String doLoad(String name, ClassLoader loader) {
- String ldLibraryPath = null;
- String dexPath = null;
- if (loader == null) {
- ldLibraryPath = System.getProperty("java.library.path");
- } else if (loader instanceof BaseDexClassLoader) {
- BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
- ldLibraryPath = dexClassLoader.getLdLibraryPath();
- }
- synchronized (this) {
- return nativeLoad(name, loader, ldLibraryPath);
- }
- }
到这里就彻底清楚了,调用Native方法nativeLoad,通过完整的so库路径filename,把目标so库加载进来。
说了半天还没有进入正题呢,不过我们可以想到,如果使用loadLibrary方法,到最后还是要找到目标so库的完整路径,再把so库加载进来,那我们能不能一开始就给出so库的完整路径,然后直接加载进来呢?我们猜想load方法就是干这个的,看看
- void load(String absolutePath, ClassLoader loader) {
- if (absolutePath == null) {
- throw new NullPointerException("absolutePath == null");
- }
- String error = doLoad(absolutePath, loader);
- if (error != null) {
- throw new UnsatisfiedLinkError(error);
- }
- }
我嘞个去,一上来就直接来到doLoad方法了,这证明我们的猜想可能是正确的,那么在实际项目中测试验证吧。
我们先把so文件放到asset中,再复制到内部存储,再使用load方法把其加载进来。
例子结构
这样做的原因是:native方法是包名+方法名的拼接,若不这样会找不到目标方法。
- public class Hello {
-
- public static native String hello();
- }
- public class MainActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- File dir = getDir("jniLibs", Context.MODE_PRIVATE);
- File distFile = new File(dir.getAbsolutePath() + File.separator + "libHello_jni.so");
- if (copyFileFromAssets(this, "libHello_jni.so", distFile.getAbsolutePath())) {
- System.load(distFile.getAbsolutePath());
- String hello = Hello.hello();
- System.out.println("hello:" + hello);
- }
- }
-
- private boolean copyFileFromAssets(Context context, String fileName, String path) {
- boolean copyFinish = false;
- try {
- InputStream is = context.getAssets().open(fileName);
- File file = new File(path);
- file.createNewFile();
- FileOutputStream fos = new FileOutputStream(file);
- byte[] temp = new byte[1024];
- int i = 0;
- while ((i = is.read(temp)) > 0) {
- fos.write(temp, 0, i);
- }
- fos.close();
- is.close();
- copyFinish = true;
- } catch (Exception e) {
- e.printStackTrace();
- }
- return copyFinish;
- }
- }
当然这里还可以使用反射来调用,方法名必须使用全方法名。
上面例子只是为了说明能动态加载so库,copy操作没有做必要的判断。另外so库可以从网络下载后再拷贝到内部存储中。