1. 程式人生 > >Android Dex 分包+熱修復(QQ空間技術方案)

Android Dex 分包+熱修復(QQ空間技術方案)

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;
    }
}