动态加载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库可以从网络下载后再拷贝到内部存储中。
使用动态加载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库可以从网络下载后再拷贝到内部存储中。
相关文章推荐
- Linux C动态加载SO库文件
- Android动态加载包含so文件的jar的自定义view控件
- JNI动态加载so文件
- Linux下动态加载SO文件
- Android动态加载so文件
- gdb加载可执行文件,动态so代码。
- CrossWalk - android 动态加载so库文件实践
- 动态加载.so文件并执行类函数
- 在程序中动态加载so文件
- 动态加载so文件
- Linux动态链接之五:运行时显式加载共享文件.so
- linux 下创建并动态加载.so 文件
- Android动态加载so文件
- Android JNI学习笔记——so文件动态加载
- Android 开发中如何动态加载 so 库文件
- 动态加载so文件
- Java之—— JAVA Web项目中DLL/SO文件动态加载方法
- 动态加载so库文件
- CrossWalk - Android 动态加载so库文件
- Linux下动态加载SO文件