package com.taobao.atlas.dexmerge; import android.util.Log; import com.taobao.atlas.dex.ClassDef; import com.taobao.atlas.dex.Dex; import com.taobao.atlas.dexmerge.dx.merge.CollisionPolicy; import com.taobao.atlas.dexmerge.dx.merge.DexMerger; import java.io.*; import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; /** * Created by lilong on 16/7/5. */ public class MergeTool { private static final int BUFFEREDSIZE = 1024; public static void mergePrepare(File oldBundle, List<ZipEntry> entryList,String patchName, File targetBundle, boolean isDiff, MergeExcutorServices.PrepareCallBack prepareCallBack) throws IOException, MergeException { if (oldBundle.exists() && entryList!= null) { ZipFile sourceZip = new ZipFile(oldBundle); try { File tempFile = File.createTempFile(patchName, null, targetBundle.getParentFile()); tempFile.deleteOnExit(); if (patchName.equals("com.taobao.maindex")){ createNewMainApkInternal(sourceZip,entryList,tempFile,isDiff,prepareCallBack); }else { createNewBundleInternal(patchName, sourceZip, entryList, tempFile, isDiff, prepareCallBack); } if (tempFile.exists()) { if (!tempFile.renameTo(targetBundle)) throw new IOException("merge failed!"); // deleteDir(patchBundle); } } catch (IOException e) { targetBundle.delete(); targetBundle = null; throw new IOException(e); } finally { try { sourceZip.close(); } catch (Throwable e) { } } } } private static void createNewMainApkInternal(ZipFile sourceZip, List<ZipEntry> entryList, File tempFile, boolean isDiff, MergeExcutorServices.PrepareCallBack prepareCallBack) throws IOException { byte[] buffer = new byte[BUFFEREDSIZE]; ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tempFile))); BufferedOutputStream bo = new BufferedOutputStream(out); InputStream in; //先写入source中未变的文件 java.util.Enumeration e = sourceZip.entries(); int i = 1; File mainDexFile = new File(tempFile.getParentFile(),"libcom_taobao_maindex.zip"); inputStreamToFile(MergeExcutorServices.sZipPatch.getInputStream(entryList.get(0)),mainDexFile); ZipFile zipFile = new ZipFile(mainDexFile); Dex patchDex = new Dex(zipFile.getInputStream(zipFile.getEntry("classes.dex"))); Iterator<ClassDef> iterators = patchDex.classDefs().iterator(); List<String>patchClassNames = new ArrayList<String>(); while (iterators.hasNext()){ ClassDef classDef = iterators.next(); int typeIndex = classDef.getTypeIndex(); Log.e("MergeTool","merge class:"+patchDex.typeNames().get(typeIndex)); patchClassNames.add(patchDex.typeNames().get(typeIndex)); } while (e.hasMoreElements()){ ZipEntry zipEnt = (ZipEntry) e.nextElement(); String name = zipEnt.getName(); if (isDiff && name.endsWith(".dex")) { i ++; InputStream inputStream = sourceZip.getInputStream(zipEnt); Dex dex = processDex(inputStream,patchClassNames); ZipEntry zipEntry = new ZipEntry(name); out.putNextEntry(zipEntry); write(new ByteArrayInputStream(dex.getBytes()), out, buffer); bo.flush(); } } Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry zipEnt = (ZipEntry) entries.nextElement(); String name = zipEnt.getName(); if (name.endsWith(".dex")){ ZipEntry zipEntry = new ZipEntry(String.format("%s%s%s","classes",i,".dex")); out.putNextEntry(zipEntry); write(zipFile.getInputStream(zipEnt),out,buffer); bo.flush(); i++; continue; } ZipEntry newEntry = new ZipEntry(name); if (name.contains("raw/") || name.contains("assets/")) { newEntry.setMethod(ZipEntry.STORED); newEntry.setCrc(zipEnt.getCrc()); newEntry.setSize(zipEnt.getSize()); } out.putNextEntry(newEntry); in = zipFile.getInputStream(zipEnt); write(in, out, buffer); bo.flush(); } mainDexFile.delete(); zipFile.close(); closeQuitely(out); closeQuitely(bo); } private static Dex processDex(InputStream inputStream, List<String> patchClassNames) throws IOException { Dex oringnalDex = new Dex(inputStream); DexMerger dexMerger = new DexMerger(new Dex[]{oringnalDex,new Dex(0)}, CollisionPolicy.FAIL); dexMerger.setRemoveTypeClasses(patchClassNames); dexMerger.setCompactWasteThreshold(1); Dex dex = dexMerger.merge(); return dex; } private static boolean isBundleFileUpdated(ZipEntry sourceEntry,List<ZipEntry> entryList){ for(ZipEntry entry : entryList){ if(entry.getName().contains(sourceEntry.getName())){ return true; } } return false; } public static void createNewBundleInternal(String patchBundleName,ZipFile source, List<ZipEntry> entryList, File target, boolean isDiff, MergeExcutorServices.PrepareCallBack prepareCallBack) throws IOException, MergeException { // get a temp file byte[] buffer = new byte[BUFFEREDSIZE]; ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(target))); BufferedOutputStream bo = new BufferedOutputStream(out); InputStream in; //先写入source中未变的文件 java.util.Enumeration e = source.entries(); Boolean isSourceHasDex = false; Boolean isPatchHasDex = false; ZipEntry originalDex = null; ZipEntry patchDex = null; File outDex; while (e.hasMoreElements()) { ZipEntry zipEnt = (ZipEntry) e.nextElement(); String name = zipEnt.getName(); /** * 差量更新需要做dex merge, 过滤出classes.dex */ if (isDiff && name.equals("classes.dex")) { // originalDex = zipEnt; isSourceHasDex = true; continue; } boolean toBeDeleted = isBundleFileUpdated(zipEnt,entryList); if (!toBeDeleted) { ZipEntry newEntry = new ZipEntry(name); if (MergeExcutorServices.os == MergeExcutorServices.OS.mac) { if (name.contains("raw/") || name.contains("assets/")) { newEntry.setMethod(ZipEntry.STORED); newEntry.setCrc(zipEnt.getCrc()); newEntry.setSize(zipEnt.getSize()); } }else { if (name.contains("raw\\") || name.contains("assets\\")) { newEntry.setMethod(ZipEntry.STORED); newEntry.setCrc(zipEnt.getCrc()); newEntry.setSize(zipEnt.getSize()); } } out.putNextEntry(newEntry); in = source.getInputStream(zipEnt); write(in, out, buffer); bo.flush(); } } if (!isSourceHasDex && isDiff) { throw new MergeException("Original bundle has no dex"); } //最后写入patch中的内容 // File[] patchFiles = patch.listFiles(); // for (File patchFile : patchFiles) { // /** // * 差量更新需要做dex merge, 过滤出classes.dex // */ // if (isDiff && patchFile.getName().equals("classes.dex")) { // patchDex = patchFile; // isPatchHasDex = true; // MergeExcutorServices.needMergeCount.incrementAndGet(); // continue; // } // zip(out, patchFile, patchFile.getName(), bo); // // } for(ZipEntry entry : entryList){ if(isDiff && (entry.getName().endsWith("classes.dex"))){ patchDex = entry; isPatchHasDex =true; MergeExcutorServices.needMergeCount.incrementAndGet(); continue; } ZipEntry newEntry = null; if (MergeExcutorServices.os == MergeExcutorServices.OS.mac) { newEntry = new ZipEntry(entry.getName().substring(entry.getName().indexOf("/") + 1)); if (newEntry.getName().contains("raw/") || newEntry.getName().contains("assets/")) { newEntry.setMethod(ZipEntry.STORED); newEntry.setCrc(entry.getCrc()); newEntry.setSize(entry.getSize()); } }else { newEntry = new ZipEntry(entry.getName().substring(entry.getName().indexOf("\\") + 1)); if (newEntry.getName().contains("raw\\") || newEntry.getName().contains("assets\\")) { newEntry.setMethod(ZipEntry.STORED); newEntry.setCrc(entry.getCrc()); newEntry.setSize(entry.getSize()); } } out.putNextEntry(newEntry); in = MergeExcutorServices.sZipPatch.getInputStream(entry); write(in, out, buffer); bo.flush(); } /** * 差量更新需要做dex merge */ if (isDiff) { // Merge patch dex with origin dex if (isPatchHasDex && isSourceHasDex) { //发出merge申请 // File outDexDir = new File(patch, "out"); ByteArrayOutputStream outDexStream=new ByteArrayOutputStream(); dexMerge(patchBundleName,source, patchDex, outDexStream, prepareCallBack); // if (outDexStream.exists()) { // /** // * caculate the merged dex md5 and report // */ // String md5 = Md5Utils.getFileMD5String(outDex); // MonitorReport.getInstance().trace(source.getName(), md5, "" + outDex.length()); // } // zip(out, outDex, outDex.getName(), bo); ByteArrayInputStream swapStream = new ByteArrayInputStream(outDexStream.toByteArray()); ZipEntry entry = new ZipEntry("classes.dex"); out.putNextEntry(entry); write(swapStream,out,buffer); bo.flush(); } else if (isSourceHasDex) { // Patch has no classes.dex, just use the original dex // outDex = new File(patch, "classes.dex"); // inputStreamToFile(source.getInputStream(originalDex), outDex); // zip(out, outDex, outDex.getName(), bo); ZipEntry entry = new ZipEntry("classes.dex"); out.putNextEntry(entry); in = source.getInputStream(source.getEntry("classes.dex")); write(in,out,buffer); bo.flush(); } }else { MergeExcutorServices.successCount.incrementAndGet(); } closeQuitely(out); closeQuitely(bo); } private static void dexMerge(String bundleName,ZipFile source, ZipEntry patchDex, OutputStream newDexStream, MergeExcutorServices.PrepareCallBack prepareCallBack) { try { prepareCallBack.prepareMerge(bundleName,source, patchDex, newDexStream); } catch (IOException e) { e.printStackTrace(); } } private static void inputStreamToFile(InputStream ins, File file) throws IOException { OutputStream os = new FileOutputStream(file); int bytesRead = 0; byte[] buffer = new byte[8192]; while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) { os.write(buffer, 0, bytesRead); } os.close(); ins.close(); } // private static void zip(ZipOutputStream out, File f, String base, // BufferedOutputStream bo) throws IOException { // if (f.isDirectory()) { // File[] fl = f.listFiles(); // if (fl.length == 0) { // out.putNextEntry(new ZipEntry(base + File.separator)); // } // for (int i = 0; i < fl.length; i++) { // zip(out, fl[i], base + File.separator + fl[i].getName(), bo); // } // } else { // CRC32 crc = new CRC32(); // ZipEntry newEntry = new ZipEntry(base); // int bytesRead; // byte[] buffer = new byte[BUFFEREDSIZE]; // if(base.contains("raw/") || base.contains("assets/")){ // BufferedInputStream bis = new BufferedInputStream( // new FileInputStream(f)); // crc.reset(); // while ((bytesRead = bis.read(buffer)) != -1) { // crc.update(buffer, 0, bytesRead); // } // closeQuitely(bis); // newEntry.setMethod(ZipEntry.STORED); // newEntry.setCrc(crc.getValue()); // newEntry.setSize(f.length()); // } // out.putNextEntry(newEntry); // FileInputStream in = new FileInputStream(f); // BufferedInputStream bi = new BufferedInputStream(in); // int b; // while ((b = bi.read()) != -1) { // bo.write(b); // } // bo.flush(); // closeQuitely(bi); // closeQuitely(in); // } // } private static void write(InputStream in, OutputStream out, byte[] buffer) throws IOException { int length = in.read(buffer); while (length != -1) { out.write(buffer, 0, length); length = in.read(buffer); } closeQuitely(in); } private static void closeQuitely(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (Throwable e) { } } } private static boolean deleteDir(File dir) { if (dir.isDirectory()) { String[] children = dir.list(); for (int i=0; i<children.length; i++) { boolean success = deleteDir(new File(dir, children[i])); if (!success) { return false; } } } return dir.delete(); } }