MultiDexExtractor.java revision d9eda5550540306f2037e2db2aba2919fda90057
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 19667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.Closeable; 20667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.File; 21d9eda5550540306f2037e2db2aba2919fda90057Yohann Rousselimport java.io.FileFilter; 22667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileNotFoundException; 23667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileOutputStream; 24667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.IOException; 25667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.InputStream; 26667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.ArrayList; 27667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.List; 28667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipEntry; 29667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipFile; 30667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipOutputStream; 31667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 32d9eda5550540306f2037e2db2aba2919fda90057Yohann Rousselimport android.content.Context; 33d9eda5550540306f2037e2db2aba2919fda90057Yohann Rousselimport android.content.pm.ApplicationInfo; 34d9eda5550540306f2037e2db2aba2919fda90057Yohann Rousselimport android.util.Log; 35d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel 36667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu/** 37667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Exposes application secondary dex files as files in the application data 38667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * directory. 39667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 40667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chufinal class MultiDexExtractor { 41667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 42667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String TAG = MultiDex.TAG; 43667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 44667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 45667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * We look for additional dex files named {@code classes2.dex}, 46667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * {@code classes3.dex}, etc. 47667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 48667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String DEX_PREFIX = "classes"; 49667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String DEX_SUFFIX = ".dex"; 50667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 51667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String EXTRACTED_NAME_EXT = ".classes"; 52667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final String EXTRACTED_SUFFIX = ".zip"; 53667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 54667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static final int BUFFER_SIZE = 0x4000; 55667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 56667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 57667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Extracts application secondary dexes into files in the application data 58667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * directory. 59667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 60667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @param dexDir 61667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * 62667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @return a list of files that were created. The list may be empty if there 63667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * are no secondary dex files. 64667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * @throws IOException if encounters a problem while reading or writing 65667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * secondary dex files 66667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 67667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu static List<File> load(Context context, ApplicationInfo applicationInfo, File dexDir) 68667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throws IOException { 69667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 70d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel File sourceApk = new File(applicationInfo.sourceDir); 71d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel long lastModified = sourceApk.lastModified(); 72d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel String extractedFilePrefix = sourceApk.getName() 73667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu + EXTRACTED_NAME_EXT; 74667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 75d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel prepareDexDir(dexDir, extractedFilePrefix, lastModified); 76667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 77667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu final List<File> files = new ArrayList<File>(); 78667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipFile apk = new ZipFile(applicationInfo.sourceDir); 79667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 80667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 81667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int secondaryNumber = 2; 82667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 83667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); 84667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu while (dexFile != null) { 85667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; 86667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File extractedFile = new File(dexDir, fileName); 87667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu files.add(extractedFile); 88667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 89667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!extractedFile.isFile()) { 90d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel extract(context, apk, dexFile, extractedFile, extractedFilePrefix, 91d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel lastModified); 92667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 93667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu secondaryNumber++; 94667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); 95667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 96667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 97667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 98667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu apk.close(); 99667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (IOException e) { 100667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to close resource", e); 101667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 102667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 103667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 104667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return files; 105667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 106667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 107d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel private static void prepareDexDir(File dexDir, final String extractedFilePrefix, 108d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel final long sourceLastModified) throws IOException { 109667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu dexDir.mkdir(); 110667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!dexDir.isDirectory()) { 111667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new IOException("Failed to create dex directory " + dexDir.getPath()); 112667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 113667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 114667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu // Clean possible old files 115d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel FileFilter filter = new FileFilter() { 116d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel 117667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu @Override 118d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel public boolean accept(File pathname) { 119d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel return (!pathname.getName().startsWith(extractedFilePrefix)) 120d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel || (pathname.lastModified() < sourceLastModified); 121667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 122667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu }; 123667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File[] files = dexDir.listFiles(filter); 124667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (files == null) { 125667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ")."); 126667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 127667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 128667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu for (File oldFile : files) { 129667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!oldFile.delete()) { 130667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to delete old file " + oldFile.getPath()); 131667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 132667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 133667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 134667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 135667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void extract( 136667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Context context, ZipFile apk, ZipEntry dexFile, File extractTo, 137d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel String extractedFilePrefix, long sourceLastModified) 138d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel throws IOException, FileNotFoundException { 139667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 140667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu InputStream in = apk.getInputStream(dexFile); 141667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipOutputStream out = null; 142667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File tmp = File.createTempFile(extractedFilePrefix, EXTRACTED_SUFFIX, 143667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extractTo.getParentFile()); 144667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.i(TAG, "Extracting " + tmp.getPath()); 145667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 146667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu out = new ZipOutputStream(new FileOutputStream(tmp)); 147667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 148667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipEntry classesDex = new ZipEntry("classes.dex"); 149667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu out.putNextEntry(classesDex); 150667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 151667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu byte[] buffer = new byte[BUFFER_SIZE]; 152667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int length = in.read(buffer); 153667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu while (length > 0) { 154667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu out.write(buffer, 0, length); 155667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu length = in.read(buffer); 156667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 157667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 158667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu closeQuietly(out); 159667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 160d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel if (!tmp.setLastModified(sourceLastModified)) { 161d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel Log.e(TAG, "Failed to set time of \"" + tmp.getAbsolutePath() + "\"." + 162d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel " This may cause problems with later updates of the apk."); 163d9eda5550540306f2037e2db2aba2919fda90057Yohann Roussel } 164667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.i(TAG, "Renaming to " + extractTo.getPath()); 165667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!tmp.renameTo(extractTo)) { 166667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + "\" to \"" + 167667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extractTo.getAbsolutePath() + "\""); 168667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 169667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 170667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu closeQuietly(in); 171667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu tmp.delete(); // return status ignored 172667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 173667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 174667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 175667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 176667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Closes the given {@code Closeable}. Suppresses any IO exceptions. 177667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 178667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void closeQuietly(Closeable closeable) { 179667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 180667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu closeable.close(); 181667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (IOException e) { 182667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to close resource", e); 183667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 184667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 185667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu} 186