MultiDexExtractor.java revision 52eafa01b1e61e410cb4c5609eacee93c2a3e853
1667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu/* 2667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Copyright (C) 2013 The Android Open Source Project 3667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 4667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Licensed under the Apache License, Version 2.0 (the "License"); 5667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * you may not use this file except in compliance with the License. 6667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * You may obtain a copy of the License at 7667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 8667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * http://www.apache.org/licenses/LICENSE-2.0 9667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 10667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Unless required by applicable law or agreed to in writing, software 11667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * distributed under the License is distributed on an "AS IS" BASIS, 12667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * See the License for the specific language governing permissions and 14667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * limitations under the License. 15667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 16667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 17667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chupackage android.support.multidex; 18667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 191f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chuimport android.content.pm.ApplicationInfo; 201f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chuimport android.util.Log; 211f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chu 22667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.Closeable; 23667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.File; 24d9eda5550540306f2037e2db2aba2919fda90057Yohann Rousselimport java.io.FileFilter; 25667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileNotFoundException; 26667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileOutputStream; 27667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.IOException; 28667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.InputStream; 29667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.ArrayList; 30667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.List; 31667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipEntry; 32667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipFile; 33667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipOutputStream; 34667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 35667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu/** 36667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Exposes application secondary dex files as files in the application data 37667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * directory. 38667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 39667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chufinal class MultiDexExtractor { 40667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 41667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String TAG = MultiDex.TAG; 42667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 43667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 44667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * We look for additional dex files named {@code classes2.dex}, 45667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@code classes3.dex}, etc. 46667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 47667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String DEX_PREFIX = "classes"; 48667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String DEX_SUFFIX = ".dex"; 49667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 50667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String EXTRACTED_NAME_EXT = ".classes"; 51667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String EXTRACTED_SUFFIX = ".zip"; 52667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 53667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final int BUFFER_SIZE = 0x4000; 54667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 55667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 56667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Extracts application secondary dexes into files in the application data 57667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * directory. 58667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 59667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @return a list of files that were created. The list may be empty if there 60667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * are no secondary dex files. 61667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @throws IOException if encounters a problem while reading or writing 62667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * secondary dex files 63667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 6452eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel static List<File> load(ApplicationInfo applicationInfo, File dexDir) 65667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws IOException { 66667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 67d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel File sourceApk = new File(applicationInfo.sourceDir); 68d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel long lastModified = sourceApk.lastModified(); 69d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel String extractedFilePrefix = sourceApk.getName() 70667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu + EXTRACTED_NAME_EXT; 71667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 72d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel prepareDexDir(dexDir, extractedFilePrefix, lastModified); 73667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 74667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu final List<File> files = new ArrayList<File>(); 75667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipFile apk = new ZipFile(applicationInfo.sourceDir); 76667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 77667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 78667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int secondaryNumber = 2; 79667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 80667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); 81667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu while (dexFile != null) { 82667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; 83667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File extractedFile = new File(dexDir, fileName); 84667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu files.add(extractedFile); 85667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 86667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!extractedFile.isFile()) { 8752eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel extract(apk, dexFile, extractedFile, extractedFilePrefix, 88d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel lastModified); 89667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 90667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu secondaryNumber++; 91667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); 92667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 93667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 94667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 95667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu apk.close(); 96667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (IOException e) { 97667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to close resource", e); 98667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 99667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 100667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 101667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return files; 102667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 103667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 104d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel private static void prepareDexDir(File dexDir, final String extractedFilePrefix, 105d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel final long sourceLastModified) throws IOException { 106667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu dexDir.mkdir(); 107667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!dexDir.isDirectory()) { 108667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new IOException("Failed to create dex directory " + dexDir.getPath()); 109667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 110667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 111667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu // Clean possible old files 112d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel FileFilter filter = new FileFilter() { 113d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel 114667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu @Override 115d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel public boolean accept(File pathname) { 116d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel return (!pathname.getName().startsWith(extractedFilePrefix)) 117d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel || (pathname.lastModified() < sourceLastModified); 118667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 119667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu }; 120667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File[] files = dexDir.listFiles(filter); 121667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (files == null) { 122667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ")."); 123667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 124667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 125667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu for (File oldFile : files) { 126667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!oldFile.delete()) { 127667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to delete old file " + oldFile.getPath()); 128667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 129667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 130667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 131667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 13252eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo, 133d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel String extractedFilePrefix, long sourceLastModified) 134d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel throws IOException, FileNotFoundException { 135667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 136667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu InputStream in = apk.getInputStream(dexFile); 137667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipOutputStream out = null; 138667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File tmp = File.createTempFile(extractedFilePrefix, EXTRACTED_SUFFIX, 139667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extractTo.getParentFile()); 140667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.i(TAG, "Extracting " + tmp.getPath()); 141667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 142667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu out = new ZipOutputStream(new FileOutputStream(tmp)); 143667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 144667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipEntry classesDex = new ZipEntry("classes.dex"); 145edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel // keep zip entry time since it is the criteria used by Dalvik 146edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel classesDex.setTime(dexFile.getTime()); 147667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu out.putNextEntry(classesDex); 148667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 149667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu byte[] buffer = new byte[BUFFER_SIZE]; 150667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int length = in.read(buffer); 1511f8c349b6524aa39a10a570115ce0afb039bd06fMaurice Chu while (length != -1) { 15252eafa01b1e61e410cb4c5609eacee93c2a3e853Yohann Roussel out.write(buffer, 0, length); 153667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu length = in.read(buffer); 154667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 155667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 156667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu closeQuietly(out); 157667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 158d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel if (!tmp.setLastModified(sourceLastModified)) { 159d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel Log.e(TAG, "Failed to set time of \"" + tmp.getAbsolutePath() + "\"." + 160d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel " This may cause problems with later updates of the apk."); 161d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel } 162667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.i(TAG, "Renaming to " + extractTo.getPath()); 163667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!tmp.renameTo(extractTo)) { 164667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + "\" to \"" + 165667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extractTo.getAbsolutePath() + "\""); 166667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 167667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 168667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu closeQuietly(in); 169667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu tmp.delete(); // return status ignored 170667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 171667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 172667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 173667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 174667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Closes the given {@code Closeable}. Suppresses any IO exceptions. 175667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 176667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void closeQuietly(Closeable closeable) { 177667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 178667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu closeable.close(); 179667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (IOException e) { 180667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to close resource", e); 181667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 182667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 183667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu} 184