Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support multidex feature in plugin application for the ROM below LOLLIPOP #264

Merged
merged 11 commits into from
Aug 10, 2017
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ public class Constant {
*/
public static final String LOCAL_PLUGIN_APK_COVER_DIR = "p_c";

/**
* 插件extra dex(优化前)释放的以插件名独立隔离的子目录
* 适用于 android 5.0 以下,5.0以上不会用到该目录
*/
public static final String LOCAL_PLUGIN_INDEPENDENT_EXTRA_DEX_SUB_DIR = "_ed";

/**
* 插件extra dex(优化后)释放的以插件名独立隔离的子目录
*/
public static final String LOCAL_PLUGIN_INDEPENDENT_EXTRA_ODEX_SUB_DIR = "_eod";

/**
* 插件文件名,name-low-high-current.jar
* 插件文件名规范:barcode-1-10-2.jar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ final boolean loadDex(ClassLoader parent, int load) {
parent = getClass().getClassLoader().getParent(); // TODO: 这里直接用父类加载器
}
String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPath, out, soDir, parent);
mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent);
Log.i("dex", "load " + mPath + " = " + mClassLoader);

if (mClassLoader == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.res.Resources;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
Expand All @@ -37,9 +38,11 @@
import com.qihoo360.replugin.helper.LogRelease;
import com.qihoo360.replugin.model.PluginInfo;
import com.qihoo360.replugin.packages.PluginManagerProxy;
import com.qihoo360.replugin.utils.FileUtils;

import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
Expand Down Expand Up @@ -671,6 +674,17 @@ private boolean loadLocked(int load, boolean useCache) {
}
odex.delete();
}


if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// support for multidex below LOLLIPOP:delete Extra odex,if need
try {
FileUtils.forceDelete(mInfo.getExtraOdexDir());
} catch (IOException e) {
e.printStackTrace();
}
}

t1 = System.currentTimeMillis();
rc = doLoad(logTag, context, parent, manager, load);
if (LOG) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,25 @@

package com.qihoo360.replugin;

import com.qihoo360.replugin.utils.ReflectUtils;
import android.os.Build;

import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.model.PluginInfo;
import com.qihoo360.replugin.utils.CloseableUtils;
import com.qihoo360.replugin.utils.FileUtils;
import com.qihoo360.replugin.utils.ReflectUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import dalvik.system.DexClassLoader;

Expand All @@ -42,6 +56,7 @@ public class PluginDexClassLoader extends DexClassLoader {
/**
* 初始化插件的DexClassLoader的构造函数。插件化框架会调用此函数。
*
* @param pi the plugin's info,refer to {@link PluginInfo}
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
Expand All @@ -52,8 +67,11 @@ public class PluginDexClassLoader extends DexClassLoader {
* {@code null}
* @param parent the parent class loader
*/
public PluginDexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
public PluginDexClassLoader(PluginInfo pi, String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);

installMultiDexesBeforeLollipop(pi, dexPath, parent);

mHostClassLoader = RePluginInternal.getAppClassLoader();

initMethods(mHostClassLoader);
Expand Down Expand Up @@ -123,4 +141,198 @@ private Class<?> loadClassFromHost(String className, boolean resolve) throws Cla
}
return c;
}

/**
* install extra dexes
*
* @param pi
* @param dexPath
* @param parent
* @deprecated apply to ROM before Lollipop,may be deprecated
*/
private void installMultiDexesBeforeLollipop(PluginInfo pi, String dexPath, ClassLoader parent) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return;
}

try {

// get paths of extra dex
List<File> dexFiles = getExtraDexFiles(pi, dexPath);

if (dexFiles != null && dexFiles.size() > 0) {

List<Object[]> allElements = new LinkedList<>();

// get dexElements of main dex
Class<?> clz = Class.forName("dalvik.system.BaseDexClassLoader");
Object pathList = ReflectUtils.readField(clz, this, "pathList");
Object[] mainElements = (Object[]) ReflectUtils.readField(pathList.getClass(), pathList, "dexElements");
allElements.add(mainElements);

for (File file : dexFiles) {
if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) {
LogDebug.d(TAG, "dex file:" + file.getName());
}

// get dexElements of extra dex (need to load dex first)
String optimizedDirectory = pi.getExtraOdexDir().getAbsolutePath();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里应该在循环外获取,毕竟每次获取的值是一样的。

DexClassLoader dexClassLoader = new DexClassLoader(file.getAbsolutePath(), optimizedDirectory, optimizedDirectory, parent);
// delete extra dex, after optimized
FileUtils.forceDelete(pi.getExtraDexDir());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果有三个及以上的Dex,直接删除ExtraDir会不会导致第三个Dex无法被优化?

从流程上看:

  1. 第一个Dex在前面已经被释放了,不再赘述
  2. 第二个Dex在上面那行被释放了,然后在这里删除了整个目录,进入下一步
  3. 第三个Dex在new DexClassLoader时,发现由于之前已把目录全删掉了,所以无法再找到这个Dex,出现问题


Object obj = ReflectUtils.readField(clz, dexClassLoader, "pathList");
Object[] dexElements = (Object[]) ReflectUtils.readField(obj.getClass(), obj, "dexElements");
allElements.add(dexElements);
}

// combine Elements
Object combineElements = combineArray(allElements);

// rewrite Elements combined to classLoader
ReflectUtils.writeField(pathList.getClass(), pathList, "dexElements", combineElements);

//Test whether the Extra Dex is installed
if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) {

Object object = ReflectUtils.readField(pathList.getClass(), pathList, "dexElements");
int length = Array.getLength(object);
LogDebug.d(TAG, "dexElements length:" + length);
}
}

} catch (Exception e) {
e.printStackTrace();
}

}

/**
* combine dexElements Array
*
* @param allElements all dexElements of dexes
* @return the combined dexElements
*/
private Object combineArray(List<Object[]> allElements) {

int startIndex = 0;
int arrayLength = 0;
Object[] originalElements = null;

for (Object[] elements : allElements) {

if (originalElements == null) {
originalElements = elements;
}

arrayLength += elements.length;
}

Object[] combined = (Object[]) Array.newInstance(
originalElements.getClass().getComponentType(), arrayLength);

for (Object[] elements : allElements) {

System.arraycopy(elements, 0, combined, startIndex, elements.length);
startIndex += elements.length;
}

return combined;
}

/**
* get paths of extra dex
*
* @param pi
* @param dexPath
* @return the File list of the extra dexes
*/
private List<File> getExtraDexFiles(PluginInfo pi, String dexPath) {

ZipFile zipFile = null;
List<File> files = null;

try {

if (pi != null) {
zipFile = new ZipFile(dexPath);
files = traverseExtraDex(pi, zipFile);
}

} catch (Exception e) {
e.printStackTrace();
} finally {
CloseableUtils.closeQuietly(zipFile);
}

return files;

}

/**
* traverse extra dex files
*
* @param pi
* @param zipFile
* @return the File list of the extra dexes
*/
private static List<File> traverseExtraDex(PluginInfo pi, ZipFile zipFile) {

String dir = null;
List<File> files = new LinkedList<>();
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (name.contains("../")) {
// 过滤,防止被攻击
continue;
}

try {
if (name.contains(".dex") && !name.equals("classes.dex")) {

if (dir == null) {
dir = pi.getExtraDexDir().getAbsolutePath();
}

File file = new File(dir, name);
extractFile(zipFile, entry, file);
files.add(file);

if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) {
LogDebug.d(TAG, "dex path:" + file.getAbsolutePath());
}
}
} catch (Exception e) {
e.printStackTrace();
}

}

return files;
}

/**
* extract File
*
* @param zipFile
* @param ze
* @param outFile
* @throws IOException
*/
private static void extractFile(ZipFile zipFile, ZipEntry ze, File outFile) throws IOException {
InputStream in = null;
try {
in = zipFile.getInputStream(ze);
FileUtils.copyInputStreamToFile(in, outFile);
if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) {
LogDebug.d(TAG, "extractFile(): Success! fn=" + outFile.getName());
}
} finally {
CloseableUtils.closeQuietly(in);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,15 @@ public RePluginClassLoader createClassLoader(ClassLoader parent, ClassLoader ori
* return new MyDexClassLoader(dexPath, optimizedDirectory, librarySearchPath, parent);
* </code>
*
* @param pi 插件信息
* @param dexPath 插件APK所在路径
* @param optimizedDirectory 插件释放odex/oat的路径
* @param librarySearchPath 插件SO库所在路径
* @param parent 插件ClassLoader的父亲
* @return 插件自己可用的PluginDexClassLoader对象。
*/
public PluginDexClassLoader createPluginClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
return new PluginDexClassLoader(dexPath, optimizedDirectory, librarySearchPath, parent);
public PluginDexClassLoader createPluginClassLoader(PluginInfo pi, String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
return new PluginDexClassLoader(pi, dexPath, optimizedDirectory, librarySearchPath, parent);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
import android.content.pm.PackageInfo;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.text.TextUtils;

import com.qihoo360.loader2.Constant;
Expand Down Expand Up @@ -380,6 +382,48 @@ public File getApkFile() {
return new File(dir, makeInstalledFileName() + ".jar");
}

/**
* 获取或创建(如果需要)某个插件的Dex目录,用于放置dex文件
* 注意:仅供框架内部使用;仅适用于Android 4.4.x及以下
*
* @param dirSuffix 目录后缀
* @return 插件的Dex所在目录的File对象
*/
@NonNull
private File getDexDir(File dexDir, String dirSuffix) {

File dir = new File(dexDir, makeInstalledFileName() + dirSuffix);

if (!dir.exists()) {
dir.mkdir();
}
return dir;
}

/**
* 获取Extra Dex(优化前)生成时所在的目录 <p>
* 若为"纯APK"插件,则会位于app_p_od/xx_ed中;若为"p-n"插件,则会位于"app_plugins_v3_odex/xx_ed"中 <p>
* 若支持同版本覆盖安装的话,则会位于app_p_c/xx_ed中; <p>
* 注意:仅供框架内部使用;仅适用于Android 4.4.x及以下
*
* @return 优化前Extra Dex所在目录的File对象
*/
public File getExtraDexDir() {
return getDexDir(getDexParentDir(), Constant.LOCAL_PLUGIN_INDEPENDENT_EXTRA_DEX_SUB_DIR);
}

/**
* 获取Extra Dex(优化后)生成时所在的目录 <p>
* 若为"纯APK"插件,则会位于app_p_od/xx_eod中;若为"p-n"插件,则会位于"app_plugins_v3_odex/xx_eod"中 <p>
* 若支持同版本覆盖安装的话,则会位于app_p_c/xx_eod中; <p>
* 注意:仅供框架内部使用;仅适用于Android 4.4.x及以下
*
* @return 优化后Extra Dex所在目录的File对象
*/
public File getExtraOdexDir() {
return getDexDir(getDexParentDir(), Constant.LOCAL_PLUGIN_INDEPENDENT_EXTRA_ODEX_SUB_DIR);
}

/**
* 获取Dex(优化后)生成时所在的目录 <p>
* 若为"纯APK"插件,则会位于app_p_od中;若为"p-n"插件,则会位于"app_plugins_v3_odex"中 <p>
Expand Down
Loading