Android Dex 分包+熱修復(QQ空間技術方案)
阿新 • • 發佈:2019-02-02
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashSet; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import android.content.Context; import android.os.Build; import android.util.Log; import dalvik.system.DexClassLoader; import dalvik.system.PathClassLoader; public class SecondaryDexUtils { public static final boolean ON = true; //use classes2.dex.lzma in apk from android 4.x+ //use classes2.dex in apk for android 5.x+ 4.x- private static final String TAG = "TAG_注入Dex"; /***************************************/ private static int SUB_DEX_NUM = 10; private static final String CLASSES_PREFIX = "classes"; private static final String DEX_POSTFIX = ".dex"; private static final HashSet msLoadedDexList = new HashSet(); /***************************************/ private static final int BUF_SIZE = 1024 * 32; private static String mSubdexExt = DEX_POSTFIX; private static class LoadedDex{ private File dexFile; private ZipEntry zipEntry; private LoadedDex(File dir,String name){ dexFile = new File(dir,name); } private LoadedDex(File dir,String name,ZipEntry zipE){ dexFile = new File(dir,name); zipEntry = zipE; } } static{ msLoadedDexList.clear(); } /* public static final File getCodeCacheDir(Context context) { ApplicationInfo appInfo = context.getApplicationInfo(); return createFilesDir(new File(appInfo.dataDir, "dex_cache")); } */ /* private synchronized static File createFilesDir(File file) { if (!file.exists()) { if (!file.mkdirs()) { if (file.exists()) { return file; } Log.e(TAG, "建立資料夾失敗:" + file.getPath()); return null; } } return file; } */ /** * 複製子dex * @param inputStream * @param outputFile * @return */ public static boolean copydexFile(InputStream inputStream,File outputFile) { BufferedInputStream bis = null; OutputStream dexWriter = null; try { bis = new BufferedInputStream(inputStream); assert bis != null; dexWriter = new BufferedOutputStream(new FileOutputStream(outputFile)); byte[] buf = new byte[BUF_SIZE]; int len; while ((len = bis.read(buf, 0, BUF_SIZE)) > 0) { dexWriter.write(buf, 0, len); } } catch (IOException e) { return false; } finally { if (null != dexWriter) try { dexWriter.close(); } catch (IOException e) { e.printStackTrace(); } if (bis != null) try { bis.close(); } catch (IOException e) { e.printStackTrace(); } } return true; } /** * 載入子dex * @param appContext */ public static void loadSecondaryDex(Context appContext) { if(appContext == null){ return; } ZipFile apkFile = null; try { apkFile = new ZipFile(appContext.getApplicationInfo().sourceDir); } catch (Exception e) { Log.i(TAG, "create zipfile error:"+Log.getStackTraceString(e)); return; } Log.i(TAG, "APK-zipfile:"+apkFile.getName()); File filesDir = appContext.getDir("odex", Context.MODE_PRIVATE); Log.i(TAG, "APK-複製子dex的目標路徑:"+filesDir.getAbsolutePath()); for(int i = 0 ; i < SUB_DEX_NUM; i ++){ String possibleDexName = buildDexFullName(i); ZipEntry zipEntry = apkFile.getEntry(possibleDexName); Log.i(TAG, "APK下的entry:"+zipEntry); if(zipEntry == null) { break; } msLoadedDexList.add(new LoadedDex(filesDir,possibleDexName,zipEntry)); } Log.i(TAG, "子dex總數:"+msLoadedDexList.size()); //判斷 目標目錄下是否已經有dex檔案 boolean isOpted = false; File[] listFiles = filesDir.listFiles(); for (int i = 0; i < listFiles.length; i++) { File file = listFiles[i]; if(file.isFile() && file.getName().endsWith(".dex")){ isOpted = true; break; } } // data/data//app_odex 目錄下存在.dex檔案 就不再從APK解壓,否則從APK解壓 if(!isOpted){ for (LoadedDex loadedDex : msLoadedDexList) { File dexFile = loadedDex.dexFile; try { boolean result = copydexFile(apkFile.getInputStream(loadedDex.zipEntry), dexFile); Log.i(TAG, "複製子dex結果:"+result); } catch (Exception e) { Log.i(TAG, "複製子dex錯誤:"+Log.getStackTraceString(e)); } } if (apkFile != null) { try { apkFile.close(); } catch (Exception e) { } } } doDexInject(appContext, filesDir, msLoadedDexList); } private static String buildDexFullName(int index){ return CLASSES_PREFIX + (index + 2) + mSubdexExt; } private static void doDexInject(final Context appContext, File filesDir,HashSet loadedDex) { if(Build.VERSION.SDK_INT >= 23){ Log.w(TAG,"無法注入dex,SDK版本太高;版本=" + Build.VERSION.SDK_INT); } String optimizeDir = filesDir.getAbsolutePath() + File.separator + "opt_dex"; File fopt = new File(optimizeDir); if (fopt.exists()) fopt.delete(); fopt.mkdirs(); try { ArrayList dexFiles = new ArrayList(); for(LoadedDex dex : loadedDex){ dexFiles.add(dex.dexFile); DexClassLoader classLoader = new DexClassLoader( dex.dexFile.getAbsolutePath(), fopt.getAbsolutePath(),null, appContext.getClassLoader()); inject(classLoader, appContext); } } catch (Exception e) { Log.i(TAG, "install dex error:"+Log.getStackTraceString(e)); } } /** * @param loader */ private static void inject(DexClassLoader loader, Context ctx){ PathClassLoader pathLoader = (PathClassLoader) ctx.getClassLoader(); try { Object dexElements = combineArray( getDexElements(getPathList(pathLoader)), getDexElements(getPathList(loader))); Object pathList = getPathList(pathLoader); setField(pathList, pathList.getClass(), "dexElements", dexElements); } catch (Exception e) { Log.i(TAG, "inject dexclassloader error:" + Log.getStackTraceString(e)); } } private static Object getPathList(Object baseDexClassLoader) throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException { return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList"); } private static Object getField(Object obj, Class<?> cl, String field) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field localField = cl.getDeclaredField(field); localField.setAccessible(true); return localField.get(obj); } private static Object getDexElements(Object paramObject) throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException { return getField(paramObject, paramObject.getClass(), "dexElements"); } private static void setField(Object obj, Class<?> cl, String field, Object value) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field localField = cl.getDeclaredField(field); localField.setAccessible(true); localField.set(obj, value); } private static Object combineArray(Object arrayLhs, Object arrayRhs) { Class<?> localClass = arrayLhs.getClass().getComponentType(); int i = Array.getLength(arrayLhs); int j = i + Array.getLength(arrayRhs); Object result = Array.newInstance(localClass, j); for (int k = 0; k < j; ++k) { if (k < i) { Array.set(result, k, Array.get(arrayLhs, k)); } else { Array.set(result, k, Array.get(arrayRhs, k - i)); } } return result; } /**刪除檔案*/ public static boolean deleteFile(String path){ File file = new File(path); if(file.exists()) return file.delete(); return true; } }