MultiDexExtractor.java revision edf0717d4203bd7e9c9435019e3c256d564b4583
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");
143edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel                // keep zip entry time since it is the criteria used by Dalvik
144edf0717d4203bd7e9c9435019e3c256d564b4583Yohann Roussel                classesDex.setTime(dexFile.getTime());
145667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                out.putNextEntry(classesDex);
146667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
147667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                byte[] buffer = new byte[BUFFER_SIZE];
148667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                int length = in.read(buffer);
149667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                while (length > 0) {
150667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                    out.write(buffer, 0, length);
151667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                    length = in.read(buffer);
152667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                }
153667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            } finally {
154667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                closeQuietly(out);
155667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
156667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            Log.i(TAG, "Renaming to " + extractTo.getPath());
157667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            if (!tmp.renameTo(extractTo)) {
158667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + "\" to \"" +
159667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu                        extractTo.getAbsolutePath() + "\"");
160667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            }
161667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        } finally {
162667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            closeQuietly(in);
163667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            tmp.delete(); // return status ignored
164667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
165667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
166667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu
167667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    /**
168667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     * Closes the given {@code Closeable}. Suppresses any IO exceptions.
169667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu     */
170667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    private static void closeQuietly(Closeable closeable) {
171667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        try {
172667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            closeable.close();
173667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        } catch (IOException e) {
174667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu            Log.w(TAG, "Failed to close resource", e);
175667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu        }
176667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu    }
177667f9a8a8155c41970a83be1414b57b5e37de336Maurice Chu}
178