MultiDexExtractor.java revision 667f9a8a8155c41970a83be1414b57b5e37de336
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 android.content.Context; 20667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport android.content.pm.ApplicationInfo; 21667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport android.util.Log; 22667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 23667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.Closeable; 24667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.File; 25667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileNotFoundException; 26667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FileOutputStream; 27667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.FilenameFilter; 28667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.IOException; 29667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.io.InputStream; 30667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.ArrayList; 31667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.List; 32667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipEntry; 33667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipFile; 34667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chuimport java.util.zip.ZipOutputStream; 35667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 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 70667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String extractedFilePrefix = new File(applicationInfo.sourceDir).getName() 71667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu + EXTRACTED_NAME_EXT; 72667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 73667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu prepareDexDir(dexDir, extractedFilePrefix); 74667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 75667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu final List<File> files = new ArrayList<File>(); 76667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipFile apk = new ZipFile(applicationInfo.sourceDir); 77667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 78667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 79667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int secondaryNumber = 2; 80667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 81667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); 82667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu while (dexFile != null) { 83667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; 84667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File extractedFile = new File(dexDir, fileName); 85667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu files.add(extractedFile); 86667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 87667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!extractedFile.isFile()) { 88667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extract(context, apk, dexFile, extractedFile, extractedFilePrefix); 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 104667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void prepareDexDir(File dexDir, final String extractedFilePrefix) 105667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 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 112667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu FilenameFilter filter = new FilenameFilter() { 113667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu @Override 114667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu public boolean accept(File dir, String name) { 115667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return !name.startsWith(extractedFilePrefix); 116667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 117667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu }; 118667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File[] files = dexDir.listFiles(filter); 119667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (files == null) { 120667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ")."); 121667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu return; 122667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 123667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu for (File oldFile : files) { 124667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!oldFile.delete()) { 125667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to delete old file " + oldFile.getPath()); 126667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 127667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 128667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 129667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 130667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void extract( 131667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Context context, ZipFile apk, ZipEntry dexFile, File extractTo, 132667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu String extractedFilePrefix) throws IOException, FileNotFoundException { 133667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 134667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu InputStream in = apk.getInputStream(dexFile); 135667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipOutputStream out = null; 136667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu File tmp = File.createTempFile(extractedFilePrefix, EXTRACTED_SUFFIX, 137667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extractTo.getParentFile()); 138667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.i(TAG, "Extracting " + tmp.getPath()); 139667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 140667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu out = new ZipOutputStream(new FileOutputStream(tmp)); 141667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 142667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu ZipEntry classesDex = new ZipEntry("classes.dex"); 143667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu out.putNextEntry(classesDex); 144667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 145667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu byte[] buffer = new byte[BUFFER_SIZE]; 146667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu int length = in.read(buffer); 147667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu while (length > 0) { 148667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu out.write(buffer, 0, length); 149667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu length = in.read(buffer); 150667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 151667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 152667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu closeQuietly(out); 153667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 154667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.i(TAG, "Renaming to " + extractTo.getPath()); 155667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu if (!tmp.renameTo(extractTo)) { 156667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + "\" to \"" + 157667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu extractTo.getAbsolutePath() + "\""); 158667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 159667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } finally { 160667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu closeQuietly(in); 161667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu tmp.delete(); // return status ignored 162667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 163667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 164667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu 165667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu /** 166667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu * Closes the given {@code Closeable}. Suppresses any IO exceptions. 167667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu */ 168667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu private static void closeQuietly(Closeable closeable) { 169667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu try { 170667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu closeable.close(); 171667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } catch (IOException e) { 172667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu Log.w(TAG, "Failed to close resource", e); 173667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 174667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu } 175667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu} 176