private void initEntries(String packageName){
try {
DexFile df = new DexFile(context.getPackageCodePath());//通过DexFile查找当前的APK中可执行文件
Enumeration<String> enumeration = df.entries();//获取df中的元素 这里包含了所有可执行的类名 该类名包含了包名+类名的方式
while (enumeration.hasMoreElements()) {//遍历
String className = (String) enumeration.nextElement();
if (className.contains(packageName)) {//在当前所有可执行的类里面查找包含有该包名的所有类
Class<?> cls = Class.forName(className);
IPlugin plugin = null;
if (cls.isAnnotationPresent(CMPlugin.class))
{
plugin = (IPlugin)cls.newInstance();
plugin.init(context,webView);
String clsName = className.substring((className.lastIndexOf("."))+1);
entries.put(clsName,plugin);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
注意要把Install Run给关了
如果开启InstantRun需要额外的处理。完整的处理流程遍历流程如下(参考Arouter源码):
/**
* 通过指定包名,扫描包下面包含的所有的ClassName
*
* @param context Uknow
* @param packageName 包名
* @return 所有class的集合
*/
public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
final Set<String> classNames = new HashSet<>();
List<String> paths = getSourcePaths(context);
final CountDownLatch parserCtl = new CountDownLatch(paths.size());
for (final String path : paths) {
DefaultPoolExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
DexFile dexfile = null;
try {
if (path.endsWith(EXTRACTED_SUFFIX)) {
//NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
dexfile = DexFile.loadDex(path, path + ".tmp", 0);
} else {
dexfile = new DexFile(path);
}
Enumeration<String> dexEntries = dexfile.entries();
while (dexEntries.hasMoreElements()) {
String className = dexEntries.nextElement();
if (className.startsWith(packageName)) {
classNames.add(className);
}
}
} catch (Throwable ignore) {
Log.e("ARouter", "Scan map file in dex files made error.", ignore);
} finally {
if (null != dexfile) {
try {
dexfile.close();
} catch (Throwable ignore) {
}
}
parserCtl.countDown();
}
}
});
}
parserCtl.await();
Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
return classNames;
}
/**
* get all the dex path
*
* @param context the application context
* @return all the dex path
* @throws PackageManager.NameNotFoundException
* @throws IOException
*/
public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
File sourceApk = new File(applicationInfo.sourceDir);
List<String> sourcePaths = new ArrayList<>();
sourcePaths.add(applicationInfo.sourceDir); //add the default apk path
//the prefix of extracted file, ie: test.classes
String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
// 如果VM已经支持了MultiDex,就不要去Secondary Folder加载 Classesx.zip了,那里已经么有了
// 通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的
if (!isVMMultidexCapable()) {
//the total dex numbers
int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
//for each dex file, ie: test.classes2.zip, test.classes3.zip...
String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
File extractedFile = new File(dexDir, fileName);
if (extractedFile.isFile()) {
sourcePaths.add(extractedFile.getAbsolutePath());
//we ignore the verify zip part
} else {
throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
}
}
}
if (ARouter.debuggable()) { // Search instant run support only debuggable
sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
}
return sourcePaths;
}
/**
* Get instant run dex path, used to catch the branch usingApkSplits=false.
*/
private static List<String> tryLoadInstantRunDexFile(ApplicationInfo applicationInfo) {
List<String> instantRunSourcePaths = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && null != applicationInfo.splitSourceDirs) {
// add the split apk, normally for InstantRun, and newest version.
instantRunSourcePaths.addAll(Arrays.asList(applicationInfo.splitSourceDirs));
Log.d(Consts.TAG, "Found InstantRun support");
} else {
try {
// This man is reflection from Google instant run sdk, he will tell me where the dex files go.
Class pathsByInstantRun = Class.forName("com.android.tools.fd.runtime.Paths");
Method getDexFileDirectory = pathsByInstantRun.getMethod("getDexFileDirectory", String.class);
String instantRunDexPath = (String) getDexFileDirectory.invoke(null, applicationInfo.packageName);
File instantRunFilePath = new File(instantRunDexPath);
if (instantRunFilePath.exists() && instantRunFilePath.isDirectory()) {
File[] dexFile = instantRunFilePath.listFiles();
for (File file : dexFile) {
if (null != file && file.exists() && file.isFile() && file.getName().endsWith(".dex")) {
instantRunSourcePaths.add(file.getAbsolutePath());
}
}
Log.d(Consts.TAG, "Found InstantRun support");
}
} catch (Exception e) {
Log.e(Consts.TAG, "InstantRun support error, " + e.getMessage());
}
}
return instantRunSourcePaths;
}
/**
* Identifies if the current VM has a native support for multidex, meaning there is no need for
* additional installation by this library.
*
* @return true if the VM handles multidex
*/
private static boolean isVMMultidexCapable() {
boolean isMultidexCapable = false;
String vmName = null;
try {
if (isYunOS()) { // YunOS需要特殊判断
vmName = "'YunOS'";
isMultidexCapable = Integer.valueOf(System.getProperty("ro.build.version.sdk")) >= 21;
} else { // 非YunOS原生Android
vmName = "'Android'";
String versionString = System.getProperty("java.vm.version");
if (versionString != null) {
Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString);
if (matcher.matches()) {
try {
int major = Integer.parseInt(matcher.group(1));
int minor = Integer.parseInt(matcher.group(2));
isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR)
|| ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)
&& (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));
} catch (NumberFormatException ignore) {
// let isMultidexCapable be false
}
}
}
}
} catch (Exception ignore) {
}
Log.i(Consts.TAG, "VM with name " + vmName + (isMultidexCapable ? " has multidex support" : " does not have multidex support"));
return isMultidexCapable;
}
/**
* 判断系统是否为YunOS系统
*/
private static boolean isYunOS() {
try {
String version = System.getProperty("ro.yunos.version");
String vmName = System.getProperty("java.vm.name");
return (vmName != null && vmName.toLowerCase().contains("lemur"))
|| (version != null && version.trim().length() > 0);
} catch (Exception ignore) {
return false;
}
}