From c2f295d8990ec2a9c5aa667761214f6986e96574 Mon Sep 17 00:00:00 2001 From: atanasovg Date: Mon, 1 Jun 2015 08:48:56 +0300 Subject: [PATCH 1/2] Use DexClassLoader instead of the MultiDex library. --- src/src/com/tns/DexFactory.java | 249 ++++---- src/src/com/tns/JsDebugger.java | 24 +- src/src/com/tns/NativeScriptApplication.java | 2 - src/src/com/tns/Platform.java | 5 - src/src/com/tns/multidex/MultiDex.java | 553 ------------------ .../com/tns/multidex/MultiDexExtractor.java | 444 -------------- src/src/com/tns/multidex/ZipUtil.java | 143 ----- 7 files changed, 127 insertions(+), 1293 deletions(-) delete mode 100644 src/src/com/tns/multidex/MultiDex.java delete mode 100644 src/src/com/tns/multidex/MultiDexExtractor.java delete mode 100644 src/src/com/tns/multidex/ZipUtil.java diff --git a/src/src/com/tns/DexFactory.java b/src/src/com/tns/DexFactory.java index 28293d5e6..6df72f85b 100644 --- a/src/src/com/tns/DexFactory.java +++ b/src/src/com/tns/DexFactory.java @@ -10,14 +10,14 @@ import java.io.InputStreamReader; import java.io.InvalidClassException; import java.io.OutputStreamWriter; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; -import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import com.tns.bindings.ProxyGenerator; -import com.tns.multidex.MultiDex; +import dalvik.system.DexClassLoader; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -26,34 +26,31 @@ public class DexFactory { - private Context context; - private static final String SECONDARY_DEX_FOLDER_NAME = "code_cache" + File.separator + "secondary-dexes"; - private String proxyPath; + private String dexPath; + private String odexPath; + private String dexThumb; + private Context context; private ProxyGenerator proxyGenerator; - - private File odexDir; - - private HashSet injectedProxyClasses = new HashSet(); + private HashMap> injectedDexClasses = new HashMap>(); - private String proxyThumb; - public DexFactory(Context context) { this.context = context; - ApplicationInfo applicationInfo = context.getApplicationInfo(); - proxyPath = applicationInfo.dataDir + File.separator + SECONDARY_DEX_FOLDER_NAME + File.separator; - proxyGenerator = new ProxyGenerator(proxyPath); + ApplicationInfo applicationInfo = this.context.getApplicationInfo(); + this.dexPath = applicationInfo.dataDir + File.separator + SECONDARY_DEX_FOLDER_NAME + File.separator; + this.odexPath = applicationInfo.dataDir + File.separator + SECONDARY_DEX_FOLDER_NAME + File.separator + "odex" + File.separator; + this.proxyGenerator = new ProxyGenerator(dexPath); ProxyGenerator.IsLogEnabled = Platform.IsLogEnabled; - File dexDir = new File(proxyPath); - odexDir = new File(dexDir.getAbsolutePath() + File.separator + "odex" + File.separator); + + File odexDir = new File(dexPath + File.separator + "odex" + File.separator); odexDir.mkdirs(); - - updateProxyThumbAndPurgeCachedProxies(dexDir); - proxyGenerator.setProxyThumb(proxyThumb); + + this.updateDexThumbAndPurgeCache(); + this.proxyGenerator.setProxyThumb(this.dexThumb); } static long totalGenTime = 0; @@ -62,92 +59,79 @@ public DexFactory(Context context) public Class resolveClass(String name, String className, String[] methodOverrides) throws ClassNotFoundException, IOException { + if(className.contains("NativeScriptActivity")) + { + // Do not extend NativeScriptActivity - it is already extended + return NativeScriptActivity.class; + } + String fullClassName = className.replace("$", "_") + "-" + name; - - if (!injectedProxyClasses.contains(fullClassName)) + Class existingClass = this.injectedDexClasses.get(fullClassName); + if(existingClass != null) { - File proxyFile = getProxyFile(fullClassName); - - if (proxyFile == null) - { - long startGenTime = System.nanoTime(); - String proxyFilePath = ""; - - if (Platform.IsLogEnabled) - { - Log.d(Platform.DEFAULT_LOG_TAG, "generating proxy in place"); - } - proxyFilePath = generateProxy(name, className, methodOverrides); - proxyFile = new File(proxyFilePath); - long stopGenTime = System.nanoTime(); - totalGenTime += stopGenTime - startGenTime; - if (Platform.IsLogEnabled) - { - Log.d(Platform.DEFAULT_LOG_TAG, "Finished inplace gen took: " + (stopGenTime - startGenTime) / 1000000.0 + "ms"); - Log.d(Platform.DEFAULT_LOG_TAG, "TotalGenTime: " + totalGenTime / 1000000.0 + "ms"); - } - } + return existingClass; + } - long startMultiDexTime = System.nanoTime(); - List files = new ArrayList(); - files.add(proxyFile); - try - { - MultiDex.installSecondaryDexes(context.getClassLoader(), odexDir, files); - injectedProxyClasses.add(fullClassName); - } - catch (IllegalArgumentException e) - { - e.printStackTrace(); - } - catch (IllegalAccessException e) - { - e.printStackTrace(); - } - catch (NoSuchFieldException e) - { - e.printStackTrace(); - } - catch (InvocationTargetException e) - { - e.printStackTrace(); - } - catch (NoSuchMethodException e) - { - e.printStackTrace(); - } - long stopMultiDexTime = System.nanoTime(); - totalMultiDexTime += (stopMultiDexTime - startMultiDexTime); + String classToProxy = this.getClassToProxyName(className); + String dexFilePath = classToProxy + "-" + name; + File dexFile = this.getDexFile(dexFilePath); + + if (dexFile == null) + { + long startGenTime = System.nanoTime(); if (Platform.IsLogEnabled) { - Log.d(Platform.DEFAULT_LOG_TAG, "Finished injecting into multidex: " + proxyFile.getAbsolutePath() + " took: " + (stopMultiDexTime - startMultiDexTime) / 1000000.0 + "ms"); - Log.d(Platform.DEFAULT_LOG_TAG, "TotalMultiDexTime: " + totalMultiDexTime / 1000000.0 + "ms"); + Log.d(Platform.DEFAULT_LOG_TAG, "generating proxy in place"); } - - - long startLoadDexTime = System.nanoTime(); - // String classToProxyName = className.replace("$", "_"); - // className = classToProxyName; - - Class loaded = context.getClassLoader().loadClass(fullClassName); - long stopLoadDexTime = System.nanoTime(); - totalLoadDexTime += (stopLoadDexTime - startLoadDexTime); + + dexFilePath = this.generateDex(name, classToProxy, methodOverrides); + dexFile = new File(dexFilePath); + long stopGenTime = System.nanoTime(); + totalGenTime += stopGenTime - startGenTime; if (Platform.IsLogEnabled) { - Log.d(Platform.DEFAULT_LOG_TAG, "Finished loading class : " + fullClassName + " took: " + (stopMultiDexTime - startMultiDexTime) / 1000000.0 + "ms"); - Log.d(Platform.DEFAULT_LOG_TAG, "TotalLoadDexTime: " + totalLoadDexTime / 1000000.0 + "ms"); + Log.d(Platform.DEFAULT_LOG_TAG, "Finished inplace gen took: " + (stopGenTime - startGenTime) / 1000000.0 + "ms"); + Log.d(Platform.DEFAULT_LOG_TAG, "TotalGenTime: " + totalGenTime / 1000000.0 + "ms"); } + } + + String jarFilePath = dexFile.getPath().replace(".dex", ".jar"); + File jarFile = new File(jarFilePath); + + Class result; + if(!jarFile.exists()) + { + FileOutputStream jarFileStream = new FileOutputStream(jarFile); - return loaded; + ZipOutputStream out = new ZipOutputStream(jarFileStream); + out.putNextEntry(new ZipEntry("classes.dex")); + byte[] dexData = new byte[(int)dexFile.length()]; + FileInputStream fi = new FileInputStream(dexFile); + fi.read(dexData, 0, dexData.length); + fi.close(); + out.write(dexData); + out.closeEntry(); + out.close(); + } - - return findClass(fullClassName); + + DexClassLoader dexClassLoader = new DexClassLoader(jarFilePath, this.odexPath, null, this.context.getClassLoader()); + result = dexClassLoader.loadClass(fullClassName); + + this.injectedDexClasses.put(fullClassName, result); + + return result; } public Class findClass(String className) throws ClassNotFoundException { String canonicalName = className.replace('/', '.'); - return context.getClassLoader().loadClass(canonicalName); + Class existingClass = this.injectedDexClasses.get(canonicalName); + if(existingClass != null) + { + return existingClass; + } + return this.context.getClassLoader().loadClass(canonicalName); } public static String strJoin(String[] array, String separator) @@ -168,8 +152,8 @@ public static String strJoin(String[] array, String separator) } return sbStr.toString(); } - - private File getProxyFile(String className) throws InvalidClassException + + private String getClassToProxyName(String className) throws InvalidClassException { String classToProxy = className; @@ -182,46 +166,40 @@ private File getProxyFile(String className) throws InvalidClassException { throw new InvalidClassException("Can't generate proxy of proxy"); } + + return classToProxy; + } - String classToProxyFile = classToProxy.replace("$", "_"); + private File getDexFile(String className) throws InvalidClassException + { + String classToProxyFile = className.replace("$", "_"); - if (proxyThumb != null) + if (this.dexThumb != null) { - classToProxyFile += "-" + proxyThumb; + classToProxyFile += "-" + this.dexThumb; } - String proxyFilePath = proxyPath + classToProxyFile + ".dex"; - File proxyFile = new File(proxyFilePath); - if (proxyFile.exists()) + String dexFilePath = dexPath + classToProxyFile + ".dex"; + File dexFile = new File(dexFilePath); + if (dexFile.exists()) { if (Platform.IsLogEnabled) { - Log.d(Platform.DEFAULT_LOG_TAG, "Looking for proxy file: " + proxyFilePath + " Result: proxy file Found. ClassName: " + className); + Log.d(Platform.DEFAULT_LOG_TAG, "Looking for proxy file: " + dexFilePath + " Result: proxy file Found. ClassName: " + className); } - return proxyFile; + return dexFile; } if (Platform.IsLogEnabled) { - Log.d(Platform.DEFAULT_LOG_TAG, "Looking for proxy file: " + proxyFilePath + " Result: NOT Found. Proxy Gen needed. ClassName: " + className); + Log.d(Platform.DEFAULT_LOG_TAG, "Looking for proxy file: " + dexFilePath + " Result: NOT Found. Proxy Gen needed. ClassName: " + className); } return null; } - private String generateProxy(String proxyName, String className, String[] methodOverrides) throws ClassNotFoundException, IOException + private String generateDex(String proxyName, String className, String[] methodOverrides) throws ClassNotFoundException, IOException { - String classToProxyName = className; - if (className.startsWith("com.tns.gen.")) - { - classToProxyName = className.substring(12); - } - - if (classToProxyName.startsWith("com.tns.gen.")) - { - throw new InvalidClassException("Can't generate proxy of proxy"); - } - - Class classToProxy = Class.forName(classToProxyName); + Class classToProxy = Class.forName(className); HashSet methodOverridesSet = null; if (methodOverrides != null) @@ -237,43 +215,46 @@ private String generateProxy(String proxyName, String className, String[] method return proxyGenerator.generateProxy(proxyName, classToProxy, methodOverridesSet); } - private void updateProxyThumbAndPurgeCachedProxies(File proxyDir) + private void updateDexThumbAndPurgeCache() { - proxyThumb = generateProxyThumb(); - if (proxyThumb == null) + this.dexThumb = this.generateDexThumb(); + if (this.dexThumb == null) { throw new RuntimeException("Error generating proxy thumb 1"); } - String oldProxyThumb = getCachedProxyThumb(proxyDir); - if (proxyThumb.equals(oldProxyThumb)) + File dexDir = new File(this.dexPath); + String oldDexThumb = this.getCachedProxyThumb(dexDir); + if (this.dexThumb.equals(oldDexThumb)) { return; } - if (oldProxyThumb != null) + if (oldDexThumb != null) { - purgeProxiesByThumb(oldProxyThumb, proxyDir); + this.purgeDexesByThumb(oldDexThumb, dexDir); + this.purgeDexesByThumb(oldDexThumb, new File(odexPath)); } else { - //purge all dex files in proxy dir if no thumg file is found - purgeProxiesByThumb(".dex", proxyDir); + //purge all dex files if no thumb file is found + this.purgeDexesByThumb(null, dexDir); + this.purgeDexesByThumb(null, new File(odexPath)); } - saveNewProxyThumb(proxyThumb, proxyDir); + this.saveNewDexThumb(this.dexThumb, dexDir); } - private void saveNewProxyThumb(String newProxyThumb, File proxyDir) + private void saveNewDexThumb(String newDexThumb, File dexDir) { - File cachedThumbFile = new File(proxyDir, "proxyThumb"); + File cachedThumbFile = new File(dexDir, "proxyThumb"); try { FileOutputStream out = new FileOutputStream(cachedThumbFile, false); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out)); try { - writer.write(newProxyThumb); + writer.write(newDexThumb); writer.newLine(); writer.flush(); } @@ -295,7 +276,7 @@ private void saveNewProxyThumb(String newProxyThumb, File proxyDir) } } - private String generateProxyThumb() + private String generateDexThumb() { try { @@ -313,7 +294,7 @@ private String generateProxyThumb() return null; } - private void purgeProxiesByThumb(String cachedproxyThumb, File pathToPurge) + private void purgeDexesByThumb(String cachedDexThumb, File pathToPurge) { if (!pathToPurge.isDirectory()) { @@ -328,13 +309,13 @@ private void purgeProxiesByThumb(String cachedproxyThumb, File pathToPurge) File purgeCandidate = new File(pathToPurge, filename); if (purgeCandidate.isDirectory()) { - purgeProxiesByThumb(cachedproxyThumb, purgeCandidate); + this.purgeDexesByThumb(cachedDexThumb, purgeCandidate); } else { - if (!filename.contains(cachedproxyThumb)) + if (cachedDexThumb != null && !filename.contains(cachedDexThumb)) { - return; + continue; } boolean b = purgeCandidate.delete(); @@ -374,4 +355,4 @@ private String getCachedProxyThumb(File proxyDir) return null; } -} +} \ No newline at end of file diff --git a/src/src/com/tns/JsDebugger.java b/src/src/com/tns/JsDebugger.java index c2c310041..d6b96c19b 100644 --- a/src/src/com/tns/JsDebugger.java +++ b/src/src/com/tns/JsDebugger.java @@ -50,7 +50,7 @@ public class JsDebugger private static final String portEnvOutputFile = "envDebug.out"; - private static final String DEBUG_BRAKE_FILENAME = "debugbreak"; + private static final String DEBUG_BREAK_FILENAME = "debugbreak"; private static int currentPort = INVALID_PORT; @@ -346,7 +346,7 @@ int getDebuggerPortFromEnvironment() w = null; } - if (shouldDebugBrake(context)) + if (shouldDebugBreak(context)) { try { @@ -538,31 +538,31 @@ public static boolean shouldEnableDebugging(Context context) } - public static Boolean shouldDebugBrakeFlag = null; + public static Boolean shouldDebugBreakFlag = null; - public static boolean shouldDebugBrake(Context context) + public static boolean shouldDebugBreak(Context context) { - if (shouldDebugBrakeFlag != null) + if (shouldDebugBreakFlag != null) { - return shouldDebugBrakeFlag; + return shouldDebugBreakFlag; } if (!shouldEnableDebugging(context)) { - shouldDebugBrakeFlag = false; + shouldDebugBreakFlag = false; return false; } String appRoot = context.getFilesDir().getPath() + File.separator; - File debugBrakeFile = new File(appRoot, DEBUG_BRAKE_FILENAME); - if (debugBrakeFile.exists()) + File debugBreakFile = new File(appRoot, DEBUG_BREAK_FILENAME); + if (debugBreakFile.exists()) { - debugBrakeFile.delete(); - shouldDebugBrakeFlag = true; + debugBreakFile.delete(); + shouldDebugBreakFlag = true; return true; } - shouldDebugBrakeFlag = false; + shouldDebugBreakFlag = false; return false; } } diff --git a/src/src/com/tns/NativeScriptApplication.java b/src/src/com/tns/NativeScriptApplication.java index e2468dd67..1b2a6235a 100644 --- a/src/src/com/tns/NativeScriptApplication.java +++ b/src/src/com/tns/NativeScriptApplication.java @@ -96,8 +96,6 @@ protected void attachBaseContext(android.content.Context param_0) { } else { super.attachBaseContext(param_0); } - - com.tns.Platform.installSecondaryDexes(this); } public boolean bindService(android.content.Intent param_0, android.content.ServiceConnection param_1, int param_2) { diff --git a/src/src/com/tns/Platform.java b/src/src/com/tns/Platform.java index b5075c1f7..85587a058 100644 --- a/src/src/com/tns/Platform.java +++ b/src/src/com/tns/Platform.java @@ -127,11 +127,6 @@ public static int init(Context context) throws RuntimeException return appJavaObjectId; } - static void installSecondaryDexes(Context context) - { - com.tns.multidex.MultiDex.install(context); - } - public static void enableVerboseLogging() { IsLogEnabled = true; diff --git a/src/src/com/tns/multidex/MultiDex.java b/src/src/com/tns/multidex/MultiDex.java deleted file mode 100644 index 3b97ac3e3..000000000 --- a/src/src/com/tns/multidex/MultiDex.java +++ /dev/null @@ -1,553 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tns.multidex; - -import android.app.Application; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Build; -import android.util.Log; -import dalvik.system.DexFile; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.ZipFile; - -/** - * Monkey patches {@link Context#getClassLoader() the application context class - * loader} in order to load classes from more than one dex file. The primary - * {@code classes.dex} must contain the classes necessary for calling this class - * methods. Secondary dex files named classes2.dex, classes3.dex... found in the - * application apk will be added to the classloader after first call to - * {@link #install(Context)}. - * - *

- * This library provides compatibility for platforms with API level 4 through - * 20. This library does nothing on newer versions of the platform which provide - * built-in support for secondary dex files. - */ -public final class MultiDex -{ - - static final String TAG = "MultiDex"; - - private static final String OLD_SECONDARY_FOLDER_NAME = "secondary-dexes"; - - private static final String SECONDARY_FOLDER_NAME = "code_cache" + File.separator + "secondary-dexes"; - - private static final int MAX_SUPPORTED_SDK_VERSION = 20; - - private static final int MIN_SDK_VERSION = 4; - - private static final int VM_WITH_MULTIDEX_VERSION_MAJOR = 2; - - private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1; - - private static final Set installedApk = new HashSet(); - - private static final boolean IS_VM_MULTIDEX_CAPABLE = isVMMultidexCapable(System.getProperty("java.vm.version")); - - private MultiDex() - { - } - - /** - * Patches the application context class loader by appending extra dex files - * loaded from the application apk. This method should be called in the - * attachBaseContext of your {@link Application}, see - * {@link MultiDexApplication} for more explanation and an example. - * - * @param context - * application context. - * @throws RuntimeException - * if an error occurred preventing the classloader extension. - */ - public static void install(Context context) - { - Log.i(TAG, "install"); - if (IS_VM_MULTIDEX_CAPABLE) - { - Log.i(TAG, "VM has multidex support, MultiDex support library is disabled."); - return; - } - - if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) - { - throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + "."); - } - - try - { - ApplicationInfo applicationInfo = getApplicationInfo(context); - if (applicationInfo == null) - { - // Looks like running on a test Context, so just return without - // patching. - return; - } - - synchronized (installedApk) - { - String apkPath = applicationInfo.sourceDir; - if (installedApk.contains(apkPath)) - { - return; - } - installedApk.add(apkPath); - - if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) - { - Log.w(TAG, "MultiDex is not guaranteed to work in SDK version " + Build.VERSION.SDK_INT + ": SDK version higher than " + MAX_SUPPORTED_SDK_VERSION + " should be backed by " + "runtime with built-in multidex capabilty but it's not the " + "case here: java.vm.version=\"" + System.getProperty("java.vm.version") + "\""); - } - - /* - * The patched class loader is expected to be a descendant of - * dalvik.system.BaseDexClassLoader. We modify its - * dalvik.system.DexPathList pathList field to append additional - * DEX file entries. - */ - ClassLoader loader; - try - { - loader = context.getClassLoader(); - } - catch (RuntimeException e) - { - /* - * Ignore those exceptions so that we don't break tests - * relying on Context like a android.test.mock.MockContext - * or a android.content.ContextWrapper with a null base - * Context. - */ - Log.w(TAG, "Failure while trying to obtain Context class loader. " + "Must be running in test mode. Skip patching.", e); - return; - } - if (loader == null) - { - // Note, the context class loader is null when running - // Robolectric tests. - Log.e(TAG, "Context class loader is null. Must be running in test mode. " + "Skip patching."); - return; - } - - try - { - clearOldDexDir(context); - } - catch (Throwable t) - { - Log.w(TAG, "Something went wrong when trying to clear old MultiDex extraction, " + "continuing without cleaning.", t); - } - - File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME); - List files = MultiDexExtractor.load(context, applicationInfo, dexDir, false); - if (checkValidZipFiles(files)) - { - installSecondaryDexes(loader, dexDir, files); - } - else - { - Log.w(TAG, "Files were not valid zip files. Forcing a reload."); - // Try again, but this time force a reload of the zip file. - files = MultiDexExtractor.load(context, applicationInfo, dexDir, true); - - if (checkValidZipFiles(files)) - { - installSecondaryDexes(loader, dexDir, files); - } - else - { - // Second time didn't work, give up - throw new RuntimeException("Zip files were not valid."); - } - } - } - - } - catch (Exception e) - { - Log.e(TAG, "Multidex installation failure", e); - throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ")."); - } - Log.i(TAG, "install done"); - } - - private static ApplicationInfo getApplicationInfo(Context context) throws NameNotFoundException - { - PackageManager pm; - String packageName; - try - { - pm = context.getPackageManager(); - packageName = context.getPackageName(); - } - catch (RuntimeException e) - { - /* - * Ignore those exceptions so that we don't break tests relying on - * Context like a android.test.mock.MockContext or a - * android.content.ContextWrapper with a null base Context. - */ - Log.w(TAG, "Failure while trying to obtain ApplicationInfo from Context. " + "Must be running in test mode. Skip patching.", e); - return null; - } - if (pm == null || packageName == null) - { - // This is most likely a mock context, so just return without - // patching. - return null; - } - ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); - return applicationInfo; - } - - /** - * 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 - */ - /* package visible for test */ - static boolean isVMMultidexCapable(String versionString) - { - // quick and dirty hack for Android 5 until dex files are located in apk - // root - return false; - /* - * boolean isMultidexCapable = false; 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 e) { - * // let isMultidexCapable be false } } } Log.i(TAG, "VM with version " - * + versionString + (isMultidexCapable ? " has multidex support" : - * " does not have multidex support")); return isMultidexCapable; - */ - } - - public static void installSecondaryDexes(ClassLoader loader, File dexDir, List files) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException - { - if (!files.isEmpty()) - { - if (Build.VERSION.SDK_INT >= 19) - { - V19.install(loader, files, dexDir); - } - else if (Build.VERSION.SDK_INT >= 14) - { - V14.install(loader, files, dexDir); - } - else - { - V4.install(loader, files); - } - } - } - - /** - * Returns whether all files in the list are valid zip files. If - * {@code files} is empty, then returns true. - */ - private static boolean checkValidZipFiles(List files) - { - for (File file : files) - { - if (!MultiDexExtractor.verifyZipFile(file)) - { - return false; - } - } - return true; - } - - /** - * 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 - */ - private 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()); - } - - /** - * 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 - */ - private 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()); - } - - /** - * 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. - */ - private 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); - System.arraycopy(original, 0, combined, 0, original.length); - System.arraycopy(extraElements, 0, combined, original.length, extraElements.length); - jlrField.set(instance, combined); - } - - private static void clearOldDexDir(Context context) throws Exception - { - File dexDir = new File(context.getFilesDir(), OLD_SECONDARY_FOLDER_NAME); - if (dexDir.isDirectory()) - { - Log.i(TAG, "Clearing old secondary dex dir (" + dexDir.getPath() + ")."); - File[] files = dexDir.listFiles(); - if (files == null) - { - Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ")."); - return; - } - for (File oldFile : files) - { - Log.i(TAG, "Trying to delete old file " + oldFile.getPath() + " of size " + oldFile.length()); - if (!oldFile.delete()) - { - Log.w(TAG, "Failed to delete old file " + oldFile.getPath()); - } - else - { - Log.i(TAG, "Deleted old file " + oldFile.getPath()); - } - } - if (!dexDir.delete()) - { - Log.w(TAG, "Failed to delete secondary dex dir " + dexDir.getPath()); - } - else - { - Log.i(TAG, "Deleted old secondary dex dir " + dexDir.getPath()); - } - } - } - - /** - * Installer for platform versions 19. - */ - private static final class V19 - { - - private static void install(ClassLoader loader, List additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException - { - /* - * The patched class loader is expected to be a descendant of - * dalvik.system.BaseDexClassLoader. We modify its - * dalvik.system.DexPathList pathList field to append additional DEX - * file entries. - */ - Field pathListField = findField(loader, "pathList"); - Object dexPathList = pathListField.get(loader); - ArrayList suppressedExceptions = new ArrayList(); - expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions)); - if (suppressedExceptions.size() > 0) - { - for (IOException e : suppressedExceptions) - { - Log.w(TAG, "Exception in makeDexElement", e); - } - Field suppressedExceptionsField = findField(loader, "dexElementsSuppressedExceptions"); - IOException[] dexElementsSuppressedExceptions = (IOException[]) suppressedExceptionsField.get(loader); - - if (dexElementsSuppressedExceptions == null) - { - dexElementsSuppressedExceptions = suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); - } - else - { - IOException[] combined = new IOException[suppressedExceptions.size() + dexElementsSuppressedExceptions.length]; - suppressedExceptions.toArray(combined); - System.arraycopy(dexElementsSuppressedExceptions, 0, combined, suppressedExceptions.size(), dexElementsSuppressedExceptions.length); - dexElementsSuppressedExceptions = combined; - } - - suppressedExceptionsField.set(loader, dexElementsSuppressedExceptions); - } - } - - /** - * A wrapper around - * {@code private static final dalvik.system.DexPathList#makeDexElements} - * . - */ - private static Object[] makeDexElements(Object dexPathList, ArrayList files, File optimizedDirectory, ArrayList suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException - { - Method makeDexElements = findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class); - - return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions); - } - } - - /** - * Installer for platform versions 14, 15, 16, 17 and 18. - */ - private static final class V14 - { - - private static void install(ClassLoader loader, List additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException - { - /* - * The patched class loader is expected to be a descendant of - * dalvik.system.BaseDexClassLoader. We modify its - * dalvik.system.DexPathList pathList field to append additional DEX - * file entries. - */ - Field pathListField = findField(loader, "pathList"); - Object dexPathList = pathListField.get(loader); - expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory)); - } - - /** - * A wrapper around - * {@code private static final dalvik.system.DexPathList#makeDexElements} - * . - */ - private static Object[] makeDexElements(Object dexPathList, ArrayList files, File optimizedDirectory) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException - { - Method makeDexElements = findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class); - - return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory); - } - } - - /** - * Installer for platform versions 4 to 13. - */ - private static final class V4 - { - private static void install(ClassLoader loader, List additionalClassPathEntries) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, IOException - { - /* - * The patched class loader is expected to be a descendant of - * dalvik.system.DexClassLoader. We modify its fields mPaths, - * mFiles, mZips and mDexs to append additional DEX file entries. - */ - int extraSize = additionalClassPathEntries.size(); - - Field pathField = findField(loader, "path"); - - StringBuilder path = new StringBuilder((String) pathField.get(loader)); - String[] extraPaths = new String[extraSize]; - File[] extraFiles = new File[extraSize]; - ZipFile[] extraZips = new ZipFile[extraSize]; - DexFile[] extraDexs = new DexFile[extraSize]; - for (ListIterator iterator = additionalClassPathEntries.listIterator(); iterator.hasNext();) - { - File additionalEntry = iterator.next(); - String entryPath = additionalEntry.getAbsolutePath(); - path.append(':').append(entryPath); - int index = iterator.previousIndex(); - extraPaths[index] = entryPath; - extraFiles[index] = additionalEntry; - extraZips[index] = new ZipFile(additionalEntry); - extraDexs[index] = DexFile.loadDex(entryPath, entryPath + ".dex", 0); - } - - pathField.set(loader, path.toString()); - expandFieldArray(loader, "mPaths", extraPaths); - expandFieldArray(loader, "mFiles", extraFiles); - expandFieldArray(loader, "mZips", extraZips); - expandFieldArray(loader, "mDexs", extraDexs); - } - } - -} diff --git a/src/src/com/tns/multidex/MultiDexExtractor.java b/src/src/com/tns/multidex/MultiDexExtractor.java deleted file mode 100644 index 7a8df1b34..000000000 --- a/src/src/com/tns/multidex/MultiDexExtractor.java +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.tns.multidex; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; -import android.os.Build; -import android.util.Log; - -import java.io.BufferedOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileFilter; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; -import java.util.zip.ZipOutputStream; - -/** - * Exposes application secondary dex files as files in the application data - * directory. - */ -final class MultiDexExtractor -{ - - private static final String TAG = MultiDex.TAG; - - /** - * We look for additional dex files named {@code classes2.dex}, - * {@code classes3.dex}, etc. - */ - private static final String DEX_PREFIX = "classes"; - private static final String DEX_SUFFIX = ".dex"; - - private static final String EXTRACTED_NAME_EXT = ".classes"; - private static final String EXTRACTED_SUFFIX = ".zip"; - private static final int MAX_EXTRACT_ATTEMPTS = 3; - - private static final String PREFS_FILE = "multidex.version"; - private static final String KEY_TIME_STAMP = "timestamp"; - private static final String KEY_CRC = "crc"; - private static final String KEY_DEX_NUMBER = "dex.number"; - - /** - * Size of reading buffers. - */ - private static final int BUFFER_SIZE = 0x4000; - /* Keep value away from 0 because it is a too probable time stamp value */ - private static final long NO_VALUE = -1L; - - /** - * Extracts application secondary dexes into files in the application data - * directory. - * - * @return a list of files that were created. The list may be empty if there - * are no secondary dex files. - * @throws IOException - * if encounters a problem while reading or writing secondary - * dex files - */ - static List load(Context context, ApplicationInfo applicationInfo, File dexDir, boolean forceReload) throws IOException - { - Log.i(TAG, "MultiDexExtractor.load(" + applicationInfo.sourceDir + ", " + forceReload + ")"); - final File sourceApk = new File(applicationInfo.sourceDir); - - long currentCrc = getZipCrc(sourceApk); - - List files; - if (!forceReload && !isModified(context, sourceApk, currentCrc)) - { - try - { - files = loadExistingExtractions(context, sourceApk, dexDir); - } - catch (IOException ioe) - { - Log.w(TAG, "Failed to reload existing extracted secondary dex files," + " falling back to fresh extraction", ioe); - files = performExtractions(sourceApk, dexDir); - putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1); - - } - } - else - { - Log.i(TAG, "Detected that extraction must be performed."); - files = performExtractions(sourceApk, dexDir); - putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1); - } - - Log.i(TAG, "load found " + files.size() + " secondary dex files"); - return files; - } - - private static List loadExistingExtractions(Context context, File sourceApk, File dexDir) throws IOException - { - Log.i(TAG, "loading existing secondary dex files"); - - final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; - int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1); - final List files = new ArrayList(totalDexNumber); - - for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) - { - String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; - File extractedFile = new File(dexDir, fileName); - if (extractedFile.isFile()) - { - files.add(extractedFile); - if (!verifyZipFile(extractedFile)) - { - Log.i(TAG, "Invalid zip file: " + extractedFile); - throw new IOException("Invalid ZIP file."); - } - } - else - { - throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'"); - } - } - - return files; - } - - private static boolean isModified(Context context, File archive, long currentCrc) - { - SharedPreferences prefs = getMultiDexPreferences(context); - return (prefs.getLong(KEY_TIME_STAMP, NO_VALUE) != getTimeStamp(archive)) || (prefs.getLong(KEY_CRC, NO_VALUE) != currentCrc); - } - - private static long getTimeStamp(File archive) - { - long timeStamp = archive.lastModified(); - if (timeStamp == NO_VALUE) - { - // never return NO_VALUE - timeStamp--; - } - return timeStamp; - } - - private static long getZipCrc(File archive) throws IOException - { - long computedValue = ZipUtil.getZipCrc(archive); - if (computedValue == NO_VALUE) - { - // never return NO_VALUE - computedValue--; - } - return computedValue; - } - - private static List performExtractions(File sourceApk, File dexDir) throws IOException - { - - final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; - - // Ensure that whatever deletions happen in prepareDexDir only happen if - // the zip that - // contains a secondary dex file in there is not consistent with the - // latest apk. Otherwise, - // multi-process race conditions can cause a crash loop where one - // process deletes the zip - // while another had created it. - prepareDexDir(dexDir, extractedFilePrefix); - - List files = new ArrayList(); - - final ZipFile apk = new ZipFile(sourceApk); - try - { - - int secondaryNumber = 2; - - String dexName = "assets/bindings/" + DEX_PREFIX + secondaryNumber + DEX_SUFFIX; - ZipEntry dexFile = apk.getEntry(dexName); - while (dexFile != null) - { - String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; - File extractedFile = new File(dexDir, fileName); - files.add(extractedFile); - - Log.i(TAG, "Extraction is needed for file " + extractedFile); - int numAttempts = 0; - boolean isExtractionSuccessful = false; - while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) - { - numAttempts++; - - // Create a zip file (extractedFile) containing only the - // secondary dex file - // (dexFile) from the apk. - extract(apk, dexFile, extractedFile, extractedFilePrefix); - - // Verify that the extracted file is indeed a zip file. - isExtractionSuccessful = verifyZipFile(extractedFile); - - // Log the sha1 of the extracted zip file - Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "success" : "failed") + " - length " + extractedFile.getAbsolutePath() + ": " + extractedFile.length()); - if (!isExtractionSuccessful) - { - // Delete the extracted file - extractedFile.delete(); - if (extractedFile.exists()) - { - Log.w(TAG, "Failed to delete corrupted secondary dex '" + extractedFile.getPath() + "'"); - } - } - } - if (!isExtractionSuccessful) - { - throw new IOException("Could not create zip file " + extractedFile.getAbsolutePath() + " for secondary dex (" + secondaryNumber + ")"); - } - secondaryNumber++; - dexName = "assets/bindings/" + DEX_PREFIX + secondaryNumber + DEX_SUFFIX; - // dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + - // DEX_SUFFIX); - dexFile = apk.getEntry(dexName); - } - } - finally - { - try - { - apk.close(); - } - catch (IOException e) - { - Log.w(TAG, "Failed to close resource", e); - } - } - - return files; - } - - private static void putStoredApkInfo(Context context, long timeStamp, long crc, int totalDexNumber) - { - SharedPreferences prefs = getMultiDexPreferences(context); - SharedPreferences.Editor edit = prefs.edit(); - edit.putLong(KEY_TIME_STAMP, timeStamp); - edit.putLong(KEY_CRC, crc); - /* - * SharedPreferences.Editor doc says that apply() and commit() - * "atomically performs the requested modifications" it should be OK to - * rely on saving the dex files number (getting old number value would - * go along with old crc and time stamp). - */ - edit.putInt(KEY_DEX_NUMBER, totalDexNumber); - apply(edit); - } - - private static SharedPreferences getMultiDexPreferences(Context context) - { - return context.getSharedPreferences(PREFS_FILE, Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ? Context.MODE_PRIVATE : Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS); - } - - /** - * This removes any files that do not have the correct prefix. - */ - private static void prepareDexDir(File dexDir, final String extractedFilePrefix) throws IOException - { - dexDir.mkdirs(); - if (!dexDir.isDirectory()) - { - throw new IOException("Failed to create dex directory " + dexDir.getPath()); - } - - // Clean possible old files - FileFilter filter = new FileFilter() - { - - @Override - public boolean accept(File pathname) - { - return !pathname.getName().startsWith(extractedFilePrefix); - } - }; - File[] files = dexDir.listFiles(filter); - if (files == null) - { - Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ")."); - return; - } - for (File oldFile : files) - { - Log.i(TAG, "Trying to delete old file " + oldFile.getPath() + " of size " + oldFile.length()); - if (!oldFile.delete()) - { - Log.w(TAG, "Failed to delete old file " + oldFile.getPath()); - } - else - { - Log.i(TAG, "Deleted old file " + oldFile.getPath()); - } - } - } - - private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo, String extractedFilePrefix) throws IOException, FileNotFoundException - { - - InputStream in = apk.getInputStream(dexFile); - ZipOutputStream out = null; - File tmp = File.createTempFile(extractedFilePrefix, EXTRACTED_SUFFIX, extractTo.getParentFile()); - Log.i(TAG, "Extracting " + tmp.getPath()); - try - { - out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp))); - try - { - ZipEntry classesDex = new ZipEntry("classes.dex"); - // keep zip entry time since it is the criteria used by Dalvik - classesDex.setTime(dexFile.getTime()); - out.putNextEntry(classesDex); - - byte[] buffer = new byte[BUFFER_SIZE]; - int length = in.read(buffer); - while (length != -1) - { - out.write(buffer, 0, length); - length = in.read(buffer); - } - out.closeEntry(); - } - finally - { - out.close(); - } - Log.i(TAG, "Renaming to " + extractTo.getPath()); - if (!tmp.renameTo(extractTo)) - { - throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + "\" to \"" + extractTo.getAbsolutePath() + "\""); - } - } - finally - { - closeQuietly(in); - tmp.delete(); // return status ignored - } - } - - /** - * Returns whether the file is a valid zip file. - */ - static boolean verifyZipFile(File file) - { - try - { - ZipFile zipFile = new ZipFile(file); - try - { - zipFile.close(); - return true; - } - catch (IOException e) - { - Log.w(TAG, "Failed to close zip file: " + file.getAbsolutePath()); - } - } - catch (ZipException ex) - { - Log.w(TAG, "File " + file.getAbsolutePath() + " is not a valid zip file.", ex); - } - catch (IOException ex) - { - Log.w(TAG, "Got an IOException trying to open zip file: " + file.getAbsolutePath(), ex); - } - return false; - } - - /** - * Closes the given {@code Closeable}. Suppresses any IO exceptions. - */ - private static void closeQuietly(Closeable closeable) - { - try - { - closeable.close(); - } - catch (IOException e) - { - Log.w(TAG, "Failed to close resource", e); - } - } - - // The following is taken from SharedPreferencesCompat to avoid having a - // dependency of the - // multidex support library on another support library. - private static Method sApplyMethod; // final - static - { - try - { - Class cls = SharedPreferences.Editor.class; - sApplyMethod = cls.getMethod("apply"); - } - catch (NoSuchMethodException unused) - { - sApplyMethod = null; - } - } - - private static void apply(SharedPreferences.Editor editor) - { - if (sApplyMethod != null) - { - try - { - sApplyMethod.invoke(editor); - return; - } - catch (InvocationTargetException unused) - { - // fall through - } - catch (IllegalAccessException unused) - { - // fall through - } - } - editor.commit(); - } -} diff --git a/src/src/com/tns/multidex/ZipUtil.java b/src/src/com/tns/multidex/ZipUtil.java deleted file mode 100644 index 5927b0b6f..000000000 --- a/src/src/com/tns/multidex/ZipUtil.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* Apache Harmony HEADER because the code in this class comes mostly from ZipFile, ZipEntry and - * ZipConstants from android libcore. - */ - -package com.tns.multidex; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.util.zip.CRC32; -import java.util.zip.ZipException; - -/** - * Tools to build a quick partial crc of zip files. - */ -final class ZipUtil -{ - static class CentralDirectory - { - long offset; - long size; - } - - /* - * redefine those constant here because of bug 13721174 preventing to - * compile using the constants defined in ZipFile - */ - private static final int ENDHDR = 22; - private static final int ENDSIG = 0x6054b50; - - /** - * Size of reading buffers. - */ - private static final int BUFFER_SIZE = 0x4000; - - /** - * Compute crc32 of the central directory of an apk. The central directory - * contains the crc32 of each entries in the zip so the computed result is - * considered valid for the whole zip file. Does not support zip64 nor - * multidisk but it should be OK for now since ZipFile does not either. - */ - static long getZipCrc(File apk) throws IOException - { - RandomAccessFile raf = new RandomAccessFile(apk, "r"); - try - { - CentralDirectory dir = findCentralDirectory(raf); - - return computeCrcOfCentralDir(raf, dir); - } - finally - { - raf.close(); - } - } - - /* Package visible for testing */ - static CentralDirectory findCentralDirectory(RandomAccessFile raf) throws IOException, ZipException - { - long scanOffset = raf.length() - ENDHDR; - if (scanOffset < 0) - { - throw new ZipException("File too short to be a zip file: " + raf.length()); - } - - long stopOffset = scanOffset - 0x10000 /* - * ".ZIP file comment"'s max - * length - */; - if (stopOffset < 0) - { - stopOffset = 0; - } - - int endSig = Integer.reverseBytes(ENDSIG); - while (true) - { - raf.seek(scanOffset); - if (raf.readInt() == endSig) - { - break; - } - - scanOffset--; - if (scanOffset < stopOffset) - { - throw new ZipException("End Of Central Directory signature not found"); - } - } - // Read the End Of Central Directory. ENDHDR includes the signature - // bytes, - // which we've already read. - - // Pull out the information we need. - raf.skipBytes(2); // diskNumber - raf.skipBytes(2); // diskWithCentralDir - raf.skipBytes(2); // numEntries - raf.skipBytes(2); // totalNumEntries - CentralDirectory dir = new CentralDirectory(); - dir.size = Integer.reverseBytes(raf.readInt()) & 0xFFFFFFFFL; - dir.offset = Integer.reverseBytes(raf.readInt()) & 0xFFFFFFFFL; - return dir; - } - - /* Package visible for testing */ - static long computeCrcOfCentralDir(RandomAccessFile raf, CentralDirectory dir) throws IOException - { - CRC32 crc = new CRC32(); - long stillToRead = dir.size; - raf.seek(dir.offset); - int length = (int) Math.min(BUFFER_SIZE, stillToRead); - byte[] buffer = new byte[BUFFER_SIZE]; - length = raf.read(buffer, 0, length); - while (length != -1) - { - crc.update(buffer, 0, length); - stillToRead -= length; - if (stillToRead == 0) - { - break; - } - length = (int) Math.min(BUFFER_SIZE, stillToRead); - length = raf.read(buffer, 0, length); - } - return crc.getValue(); - } -} From 89393ab7b5a04a0bfedd87ce6d702e6dcdf691cb Mon Sep 17 00:00:00 2001 From: atanasovg Date: Mon, 1 Jun 2015 08:57:10 +0300 Subject: [PATCH 2/2] Use the already created odex String. --- src/src/com/tns/DexFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src/com/tns/DexFactory.java b/src/src/com/tns/DexFactory.java index 6df72f85b..993e9f1f8 100644 --- a/src/src/com/tns/DexFactory.java +++ b/src/src/com/tns/DexFactory.java @@ -42,11 +42,11 @@ public DexFactory(Context context) ApplicationInfo applicationInfo = this.context.getApplicationInfo(); this.dexPath = applicationInfo.dataDir + File.separator + SECONDARY_DEX_FOLDER_NAME + File.separator; - this.odexPath = applicationInfo.dataDir + File.separator + SECONDARY_DEX_FOLDER_NAME + File.separator + "odex" + File.separator; + this.odexPath = this.dexPath + "odex" + File.separator; this.proxyGenerator = new ProxyGenerator(dexPath); ProxyGenerator.IsLogEnabled = Platform.IsLogEnabled; - File odexDir = new File(dexPath + File.separator + "odex" + File.separator); + File odexDir = new File(this.odexPath); odexDir.mkdirs(); this.updateDexThumbAndPurgeCache();