/*
* Tencent is pleased to support the open source community by making Tinker available.
*
* Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.tinker.loader;
import android.util.Log;
import com.tencent.tinker.loader.shareutil.SharePatchFileUtil;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import dalvik.system.DexFile;
/**
* Created by tangyinsheng on 2016/11/15.
*/
public final class TinkerParallelDexOptimizer {
private static final String TAG = "Tinker.ParallelDex";
private static final int DEFAULT_THREAD_COUNT = 2;
/**
* Optimize (trigger dexopt or dex2oat) dexes.
*
* @param dexFiles
* @param optimizedDir
* @param cb
* @return If all dexes are optimized successfully, return true. Otherwise return false.
*/
public static boolean optimizeAll(Collection<File> dexFiles, File optimizedDir, ResultCallback cb) {
return optimizeAll(dexFiles, optimizedDir, false, null, cb);
}
public static boolean optimizeAll(Collection<File> dexFiles, File optimizedDir, boolean useInterpretMode, String targetISA, ResultCallback cb) {
final AtomicInteger successCount = new AtomicInteger(0);
return optimizeAllLocked(dexFiles, optimizedDir, useInterpretMode, targetISA, successCount, cb, DEFAULT_THREAD_COUNT);
}
private synchronized static boolean optimizeAllLocked(Collection<File> dexFiles, File optimizedDir,
boolean useInterpretMode, String targetISA, AtomicInteger successCount, ResultCallback cb, int threadCount) {
final CountDownLatch latch = new CountDownLatch(dexFiles.size());
final ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
long startTick = System.nanoTime();
ArrayList<File> sortList = new ArrayList<>(dexFiles);
// sort input dexFiles with its file length
Collections.sort(sortList, new Comparator<File>() {
@Override
public int compare(File lhs, File rhs) {
long diffSize = lhs.length() - rhs.length();
if (diffSize > 0) {
return 1;
} else if (diffSize == 0) {
return 0;
} else {
return -1;
}
}
});
Collections.reverse(sortList);
for (File dexFile : sortList) {
OptimizeWorker worker = new OptimizeWorker(dexFile, optimizedDir, useInterpretMode, targetISA, successCount, latch, cb);
threadPool.submit(worker);
}
try {
latch.await();
long timeCost = (System.nanoTime() - startTick) / 1000000;
if (successCount.get() == dexFiles.size()) {
Log.i(TAG, "All dexes are optimized successfully, cost: " + timeCost + " ms.");
return true;
} else {
Log.e(TAG, "Dexes optimizing failed, some dexes are not optimized.");
return false;
}
} catch (InterruptedException e) {
Log.w(TAG, "Dex optimizing was interrupted.", e);
return false;
} finally {
threadPool.shutdown();
}
}
public interface ResultCallback {
void onStart(File dexFile, File optimizedDir);
void onSuccess(File dexFile, File optimizedDir, File optimizedFile);
void onFailed(File dexFile, File optimizedDir, Throwable thr);
}
private static class OptimizeWorker implements Runnable {
private static String targetISA = null;
private final File dexFile;
private final File optimizedDir;
private final boolean useInterpretMode;
private final AtomicInteger successCount;
private final CountDownLatch waitingLatch;
private final ResultCallback callback;
OptimizeWorker(File dexFile, File optimizedDir, boolean useInterpretMode, String targetISA, AtomicInteger successCount, CountDownLatch latch, ResultCallback cb) {
this.dexFile = dexFile;
this.optimizedDir = optimizedDir;
this.useInterpretMode = useInterpretMode;
this.successCount = successCount;
this.waitingLatch = latch;
this.callback = cb;
this.targetISA = targetISA;
}
@Override
public void run() {
try {
if (!SharePatchFileUtil.isLegalFile(dexFile)) {
if (callback != null) {
callback.onFailed(dexFile, optimizedDir,
new IOException("dex file " + dexFile.getAbsolutePath() + " is not exist!"));
}
}
if (callback != null) {
callback.onStart(dexFile, optimizedDir);
}
String optimizedPath = SharePatchFileUtil.optimizedPathFor(this.dexFile, this.optimizedDir);
if (useInterpretMode) {
interpretDex2Oat(dexFile.getAbsolutePath(), optimizedPath);
} else {
DexFile.loadDex(dexFile.getAbsolutePath(), optimizedPath, 0);
}
successCount.incrementAndGet();
if (callback != null) {
callback.onSuccess(dexFile, optimizedDir, new File(optimizedPath));
}
} catch (final Throwable e) {
Log.e(TAG, "Failed to optimize dex: " + dexFile.getAbsolutePath(), e);
if (callback != null) {
callback.onFailed(dexFile, optimizedDir, e);
}
} finally {
this.waitingLatch.countDown();
}
}
private void interpretDex2Oat(String dexFilePath, String oatFilePath) throws IOException {
final File oatFile = new File(oatFilePath);
if (!oatFile.exists()) {
oatFile.getParentFile().mkdirs();
}
final List<String> commandAndParams = new ArrayList<>();
commandAndParams.add("dex2oat");
commandAndParams.add("--dex-file=" + dexFilePath);
commandAndParams.add("--oat-file=" + oatFilePath);
commandAndParams.add("--instruction-set=" + targetISA);
commandAndParams.add("--compiler-filter=interpret-only");
final ProcessBuilder pb = new ProcessBuilder(commandAndParams);
pb.redirectErrorStream(true);
final Process dex2oatProcess = pb.start();
StreamConsumer.consumeInputStream(dex2oatProcess.getInputStream());
StreamConsumer.consumeInputStream(dex2oatProcess.getErrorStream());
try {
final int ret = dex2oatProcess.waitFor();
if (ret != 0) {
throw new IOException("dex2oat works unsuccessfully, exit code: " + ret);
}
} catch (InterruptedException e) {
throw new IOException("dex2oat is interrupted, msg: " + e.getMessage(), e);
}
}
}
private static class StreamConsumer {
static final Executor STREAM_CONSUMER = Executors.newSingleThreadExecutor();
static void consumeInputStream(final InputStream is) {
STREAM_CONSUMER.execute(new Runnable() {
@Override
public void run() {
if (is == null) {
return;
}
final byte[] buffer = new byte[256];
try {
while ((is.read(buffer)) > 0) {
// To satisfy checkstyle rules.
}
} catch (IOException ignored) {
// Ignored.
} finally {
try {
is.close();
} catch (Exception ignored) {
// Ignored.
}
}
}
});
}
}
}