so文件不打包进apk,在安装完应用打开app的时候通过后台下载so库,将下载下来的so文件再写入到app里面。
Android加载so文件的方式有两种:
System.loadLibrary();
System.load();
它们都可以用来装载库文件,但是System.load参数必须为库文件的绝对路径,可以是任意路径;System.loadLibrary参数为库文件名,不包含库文件的扩展名,必须是在JVM属性java.library.path所指向的路径中,路径可以通过System.getProperty(‘java.library.path’) 获得。所有动态加载的时候我们不能用System.loadLibrary,只能用System.load来加载。
最终的结果如下图:我们把我们自定义的路径加入到系统能认识的dexPathList里面
Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
Object dexPathList = pathListField.get(classLoader);
Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories");
List libDirs = (List) nativeLibraryDirectories.get(dexPathList);
libDirs.add(0, folder);
Field systemNativeLibraryDirectories =
// 就是这个路径,把我们下载的路径添加到这里
ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories");
List systemLibDirs = (List) systemNativeLibraryDirectories.get(dexPathList);
Method makePathElements =
ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class);
libDirs.addAll(systemLibDirs);
Object[] elements = (Object[]) makePathElements.
invoke(dexPathList, libDirs);
Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements");
nativeLibraryPathElements.setAccessible(true);
nativeLibraryPathElements.set(dexPathList, elements);
packagingOptions {
exclude 'lib/armeabi/libindoor.so'
exclude 'lib/armeabi/libBaiduMapSDK_base_v5_2_0.so'
exclude 'lib/armeabi/liblocSDK7b.so'
}
private void dynamicSo() {
String dir = FileUtils.getAppRecordDir(sApplication).toString();
SoFile.loadSoFile(sApplication, dir);
}
public class TinkerLoadLibrary {
private static final String TAG = "Tinker.LoadLibrary";
public static synchronized void installNativeLibraryPath(ClassLoader classLoader, File folder)
throws Throwable {
if (folder == null || !folder.exists()) {
Logger.e(TAG, "installNativeLibraryPath, folder %s is illegal", folder);
return;
}
// android o sdk_int 26
// for android o preview sdk_int 25
if ((Build.VERSION.SDK_INT == 25 && getPreviousSdkInt() != 0)
|| Build.VERSION.SDK_INT > 25) {
try {
V25.install(classLoader, folder);
return;
} catch (Throwable throwable) {
// install fail, try to treat it as v23
// some preview N version may go here
Logger.e(TAG, "installNativeLibraryPath, v25 fail, sdk: %d, error: %s, try to fallback to V23",
Build.VERSION.SDK_INT, throwable.getMessage());
V23.install(classLoader, folder);
}
} else if (Build.VERSION.SDK_INT >= 23) {
try {
V23.install(classLoader, folder);
} catch (Throwable throwable) {
// install fail, try to treat it as v14
Logger.e(TAG, "installNativeLibraryPath, v23 fail, sdk: %d, error: %s, try to fallback to V14",
Build.VERSION.SDK_INT, throwable.getMessage());
V14.install(classLoader, folder);
}
} else if (Build.VERSION.SDK_INT >= 14) {
V14.install(classLoader, folder);
}
}
/**
* fuck部分机型删了该成员属性,兼容
*
* @return 被厂家删了返回1,否则正常读取
*/
@TargetApi(Build.VERSION_CODES.M)
private static int getPreviousSdkInt() {
try {
return Build.VERSION.PREVIEW_SDK_INT;
} catch (Throwable ignore) {
}
return 1;
}
private static final class V14 {
private static void install(ClassLoader classLoader, File folder) throws Throwable {
Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
Object dexPathList = pathListField.get(classLoader);
ShareReflectUtil.expandFieldArray(dexPathList, "nativeLibraryDirectories", new File[]{folder});
}
}
private static final class V23 {
private static void install(ClassLoader classLoader, File folder) throws Throwable {
Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
Object dexPathList = pathListField.get(classLoader);
Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories");
List libDirs = (List) nativeLibraryDirectories.get(dexPathList);
libDirs.add(0, folder);
Field systemNativeLibraryDirectories =
ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories");
List systemLibDirs = (List) systemNativeLibraryDirectories.get(dexPathList);
Method makePathElements =
ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class, List.class);
ArrayList suppressedExceptions = new ArrayList<>();
libDirs.addAll(systemLibDirs);
Object[] elements = (Object[]) makePathElements.
invoke(dexPathList, libDirs, null, suppressedExceptions);
Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements");
nativeLibraryPathElements.setAccessible(true);
nativeLibraryPathElements.set(dexPathList, elements);
}
}
private static final class V25 {
private static void install(ClassLoader classLoader, File folder) throws Throwable {
Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
Object dexPathList = pathListField.get(classLoader);
Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories");
List libDirs = (List) nativeLibraryDirectories.get(dexPathList);
libDirs.add(0, folder);
Field systemNativeLibraryDirectories =
ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories");
List systemLibDirs = (List) systemNativeLibraryDirectories.get(dexPathList);
Method makePathElements =
ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class);
libDirs.addAll(systemLibDirs);
Object[] elements = (Object[]) makePathElements.
invoke(dexPathList, libDirs);
Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements");
nativeLibraryPathElements.setAccessible(true);
nativeLibraryPathElements.set(dexPathList, elements);
}
}
}
// 获取路径的类
public class ShareReflectUtil {
/**
* Locates a given field anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the field into.
* @param name field name
* @return a field object
* @throws NoSuchFieldException if the field cannot be located
*/
public static Field findField(Object instance, String name) throws NoSuchFieldException {
for (Class> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Field field = clazz.getDeclaredField(name);
if (!field.isAccessible()) {
field.setAccessible(true);
}
return field;
} catch (NoSuchFieldException e) {
// ignore and search next
}
}
throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
}
public static Field findField(Class> originClazz, String name) throws NoSuchFieldException {
for (Class> clazz = originClazz; clazz != null; clazz = clazz.getSuperclass()) {
try {
Field field = clazz.getDeclaredField(name);
if (!field.isAccessible()) {
field.setAccessible(true);
}
return field;
} catch (NoSuchFieldException e) {
// ignore and search next
}
}
throw new NoSuchFieldException("Field " + name + " not found in " + originClazz);
}
/**
* Locates a given method anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the method into.
* @param name method name
* @param parameterTypes method parameter types
* @return a method object
* @throws NoSuchMethodException if the method cannot be located
*/
public static Method findMethod(Object instance, String name, Class>... parameterTypes)
throws NoSuchMethodException {
for (Class> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Method method = clazz.getDeclaredMethod(name, parameterTypes);
if (!method.isAccessible()) {
method.setAccessible(true);
}
return method;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}
throw new NoSuchMethodException("Method "
+ name
+ " with parameters "
+ Arrays.asList(parameterTypes)
+ " not found in " + instance.getClass());
}
/**
* Locates a given method anywhere in the class inheritance hierarchy.
*
* @param clazz a class to search the method into.
* @param name method name
* @param parameterTypes method parameter types
* @return a method object
* @throws NoSuchMethodException if the method cannot be located
*/
public static Method findMethod(Class> clazz, String name, Class>... parameterTypes)
throws NoSuchMethodException {
for (; clazz != null; clazz = clazz.getSuperclass()) {
try {
Method method = clazz.getDeclaredMethod(name, parameterTypes);
if (!method.isAccessible()) {
method.setAccessible(true);
}
return method;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}
throw new NoSuchMethodException("Method "
+ name
+ " with parameters "
+ Arrays.asList(parameterTypes)
+ " not found in " + clazz);
}
/**
* Locates a given constructor anywhere in the class inheritance hierarchy.
*
* @param instance an object to search the constructor into.
* @param parameterTypes constructor parameter types
* @return a constructor object
* @throws NoSuchMethodException if the constructor cannot be located
*/
public static Constructor> findConstructor(Object instance, Class>... parameterTypes)
throws NoSuchMethodException {
for (Class> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Constructor> ctor = clazz.getDeclaredConstructor(parameterTypes);
if (!ctor.isAccessible()) {
ctor.setAccessible(true);
}
return ctor;
} catch (NoSuchMethodException e) {
// ignore and search next
}
}
throw new NoSuchMethodException("Constructor"
+ " with parameters "
+ Arrays.asList(parameterTypes)
+ " not found in " + instance.getClass());
}
/**
* Replace the value of a field containing a non null array, by a new array containing the
* elements of the original array plus the elements of extraElements.
*
* @param instance the instance whose field is to be modified.
* @param fieldName the field to modify.
* @param extraElements elements to append at the end of the array.
*/
public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field jlrField = findField(instance, fieldName);
Object[] original = (Object[]) jlrField.get(instance);
Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);
// NOTE: changed to copy extraElements first, for patch load first
System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
System.arraycopy(original, 0, combined, extraElements.length, original.length);
jlrField.set(instance, combined);
}
/**
* Replace the value of a field containing a non null array, by a new array containing the
* elements of the original array plus the elements of extraElements.
*
* @param instance the instance whose field is to be modified.
* @param fieldName the field to modify.
*/
public static void reduceFieldArray(Object instance, String fieldName, int reduceSize)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
if (reduceSize <= 0) {
return;
}
Field jlrField = findField(instance, fieldName);
Object[] original = (Object[]) jlrField.get(instance);
int finalLength = original.length - reduceSize;
if (finalLength <= 0) {
return;
}
Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), finalLength);
System.arraycopy(original, reduceSize, combined, 0, finalLength);
jlrField.set(instance, combined);
}
public static Object getActivityThread(Context context,
Class> activityThread) {
try {
if (activityThread == null) {
activityThread = Class.forName("android.app.ActivityThread");
}
Method m = activityThread.getMethod("currentActivityThread");
m.setAccessible(true);
Object currentActivityThread = m.invoke(null);
if (currentActivityThread == null && context != null) {
// In older versions of Android (prior to frameworks/base 66a017b63461a22842)
// the currentActivityThread was built on thread locals, so we'll need to try
// even harder
Field mLoadedApk = context.getClass().getField("mLoadedApk");
mLoadedApk.setAccessible(true);
Object apk = mLoadedApk.get(context);
Field mActivityThreadField = apk.getClass().getDeclaredField("mActivityThread");
mActivityThreadField.setAccessible(true);
currentActivityThread = mActivityThreadField.get(apk);
}
return currentActivityThread;
} catch (Throwable ignore) {
return null;
}
}
/**
* Handy method for fetching hidden integer constant value in system classes.
*
* @param clazz
* @param fieldName
* @return
*/
public static int getValueOfStaticIntField(Class> clazz, String fieldName, int defVal) {
try {
final Field field = findField(clazz, fieldName);
return field.getInt(null);
} catch (Throwable thr) {
return defVal;
}
}
}
/**
* 文件描述:提供一个文件读写和加载指定路径下so的类
*
* @author :feilong on 2018/11/6
*/
public class SoFile {
/**
* 加载 so 文件(直接指定你so下载的路径即可)
*
* @param context
* @param fromPath 下载到得sdcard目录
*/
public static void loadSoFile(Context context, String fromPath) {
File dir = context.getDir("libs", Context.MODE_PRIVATE);
if (!isLoadSoFile(dir)) {
copy(fromPath, dir.getAbsolutePath());
}
try {
TinkerLoadLibrary.installNativeLibraryPath(VchatApplication.getMyApplication().getBaseContext().getClassLoader(), dir);
} catch (Throwable throwable) {
Logger.e(throwable.getMessage());
}
}
/**
* 判断 so 文件是否存在
*
* @param dir
* @return
*/
public static boolean isLoadSoFile(File dir) {
File[] currentFiles;
currentFiles = dir.listFiles();
boolean hasSoLib = false;
if (currentFiles == null) {
return false;
}
for (int i = 0; i < currentFiles.length; i++) {
if (currentFiles[i].getName().contains("libwedsa23")) {
hasSoLib = true;
}
}
return hasSoLib;
}
/**
* @param fromFile 指定的下载目录
* @param toFile 应用的包路径
* @return
*/
public static int copy(String fromFile, String toFile) {
//要复制的文件目录
File root = new File(fromFile);
//如同判断SD卡是否存在或者文件是否存在,如果不存在则 return出去
if (!root.exists()) {
return -1;
}
//如果存在则获取当前目录下的全部文件 填充数组
File[] currentFiles = root.listFiles();
//目标目录
File targetDir = new File(toFile);
//创建目录
if (!targetDir.exists()) {
targetDir.mkdirs();
}
if (currentFiles != null && currentFiles.length > 0) {
//遍历要复制该目录下的全部文件
for (File currentFile : currentFiles) {
if (currentFile.isDirectory()) {
//如果当前项为子目录 进行递归
copy(currentFile.getPath() + "/", toFile + currentFile.getName() + "/");
} else {
//如果当前项为文件则进行文件拷贝
if (currentFile.getName().contains(".so")) {
int id = copySdcardFile(currentFile.getPath(), toFile + File.separator + currentFile.getName());
}
}
}
}
return 0;
}
//文件拷贝
//要复制的目录下的所有非子目录(文件夹)文件拷贝
public static int copySdcardFile(String fromFile, String toFile) {
try {
FileInputStream fosfrom = new FileInputStream(fromFile);
FileOutputStream fosto = new FileOutputStream(toFile);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = fosfrom.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
// 从内存到写入到具体文件
fosto.write(baos.toByteArray());
// 关闭文件流
baos.close();
fosto.close();
fosfrom.close();
return 0;
} catch (Exception ex) {
return -1;
}
}
}
(因为我也不会写原理,再不上代码,还TM写个毛啊…)