您的位置:首页 > 其它

动态加载so库文件

2017-05-26 15:01 267 查看
http://blog.csdn.net/zhangyongfeiyong/article/details/51603663

使用动态加载so库文件可以减小apk文件的大小,如:so库文件较大时,使用动态加载,在需要使用so库文件或者满足其他条件时,提示用户下载或自动下载,这样apk文件的大小就可以大大降低。
Android加载so库文件的机制:
加载so库文件基本都用的System类的loadLibrary方法,其实System类中还有一个load方法。

[java] view
plain copy

/** 

     * See {@link Runtime#load}. 

     */  

    public static void load(String pathName) {  

        Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());  

    }  

  

    /** 

     * See {@link Runtime#loadLibrary}. 

     */  

    public static void loadLibrary(String libName) {  

        Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());  

    }  

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

[java] view
plain copy

/* 

 * Searches for and loads the given shared library using the given ClassLoader. 

 */  

void loadLibrary(String libraryName, ClassLoader loader) {  

    if (loader != null) {  

        String filename = loader.findLibrary(libraryName);  

        String error = doLoad(filename, loader);  

        return;  

    }  

    ……  

}  

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

[java] view
plain copy

protected String findLibrary(String libName) {  

    return null;  

}  

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

[java] view
plain copy

public class BaseDexClassLoader extends ClassLoader {  

    public String findLibrary(String name) {  

        throw new RuntimeException("Stub!");  

    }  

}  

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

[java] view
plain copy

@Override  

    public String findLibrary(String name) {  

        return pathList.findLibrary(name);  

    }  

再看DexPathList类

[java] view
plain copy

/** 

     * Finds the named native code library on any of the library 

     * directories pointed at by this instance. This will find the 

     * one in the earliest listed directory, ignoring any that are not 

     * readable regular files. 

     * 

     * @return the complete path to the library or {@code null} if no 

     * library was found 

     */  

    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方法,看看

[java] view
plain copy

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方法就是干这个的,看看

[java] view
plain copy

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方法是包名+方法名的拼接,若不这样会找不到目标方法。

[java] view
plain copy

public class Hello {  

  

    public static native String hello();  

}  

 

[java] view
plain copy

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库可以从网络下载后再拷贝到内部存储中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: