java.lang.UnsatisfiedLinkError 的解决办法

日期 2016-10-22
java.lang.UnsatisfiedLinkError 的解决办法

在实际开发中时不时的在部分手机上会出现一些

java.lang.UnsatisfiedLinkError: Couldn’t load solibrary from loader dalvik.system.PathClassLoader: findLibrary returned null
java.lang.UnsatisfiedLinkError: Library solibrary not found

等错误,虽然概率不高但经常有。
根据提示就可以知道是so库找不到造成的,但是so应该在安装时就放到了/data/data/com.your.packagename/lib 下面,根据KeepSafe提供的经验,是因为安装时pm不稳定造成的。

怎么复现这些问题呢?

就是安装好应用后删掉/data/data/com.your.packagename/lib下面的so库就会出现。
正常情况下是不会丢失so库的。

如何解决这种问题?

本来so应该在我们apk里面,找不到时再复制一份不就可以了吗?
思路是这样,中间也有很多情况,比如不同arm&mips&x86 architecture以及32&64-bit 区别等。
这里推荐Relinker 用法很简单。
System.loadLibrary("mylibrary") 替换成ReLinker.loadLibrary(context, "mylibrary") 就可以了。

Relinker实现原理

我们看它的核心加载在
com/getkeepsafe/relinker/ReLinkerInstance.java#L154

private void loadLibraryInternal(final Context context,
final String library,
final String version) {
if (loadedLibraries.contains(library) && !force) {
log("%s already loaded previously!", library);
return;
}

try {
libraryLoader.loadLibrary(library);
loadedLibraries.add(library);
//正常情况下加载so成功后不再做任何处理
log("%s (%s) was loaded normally!", library, version);
return;
} catch (final UnsatisfiedLinkError e) {
// :-(
log("Loading the library normally failed: %s", Log.getStackTraceString(e));
}

//加载不成功的情况
log("%s (%s) was not loaded normally, re-linking...", library, version);

//首先尝试从 /data/data/packagename/app-lib/下加载so
//这个目录下存放的是ReLinker从apk里面copy出来的so文件

final File workaroundFile = getWorkaroundLibFile(context, library, version);
if (!workaroundFile.exists() || force) {
if (force) {
log("Forcing a re-link of %s (%s)...", library, version);
}

//如果没有或者需要更新so文件,先清理旧文件

cleanupOldLibFiles(context, library, version);

//从apk中copy so文件到app-lib/目录下

libraryInstaller.installLibrary(context, libraryLoader.supportedAbis(),
libraryLoader.mapLibraryName(library), workaroundFile, this);
}

try {
//支持加载so的依赖
if (recursive) {
final ElfParser parser = new ElfParser(workaroundFile);
final List<String> dependencies = parser.parseNeededDependencies();
for (final String dependency : dependencies) {
loadLibrary(context, libraryLoader.unmapLibraryName(dependency));
}
}
} catch (IOException ignored) {
// This a redundant step of the process, if our library resolving fails, it will likely
// be picked up by the system's resolver, if not, an exception will be thrown by the
// next statement, so its better to try twice.
}

//最后加载app-lib/下的so文件
libraryLoader.loadPath(workaroundFile.getAbsolutePath());
loadedLibraries.add(library);
log("%s (%s) was re-linked!", library, version);
}

其中支持加载so的依赖库是因为System.loadLibrary(libraryName)System.load(libraryPath) 是有区别的,看参数名称就可以知道System.loadLibrary(libraryName) 只需要知道库名称就会自动在java.library.path 中加载库同时会加载对应依赖的库,而System.load(libraryPath) 只会加载指定的库不会加载它所依赖的其他库,所以使用ReLinker加载有外部依赖的库时需要使用ReLinker.recursively().loadLibrary(...) 方法去加载。

对于第三方sdk里面的so出现异常就没办法了,只希望尽量不要crash。
最后大赞facebook fresco,对于so加载也提供了对外扩展的方法com/facebook/common/soloader/SoLoaderShim.java 这样就可以通过扩展配合ReLinker安全的加载so库了。