package me.ele.amigo.release;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import dalvik.system.DexFile;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import me.ele.amigo.AmigoDirs;
import me.ele.amigo.AmigoService;
import me.ele.amigo.PatchApks;
import me.ele.amigo.PatchInfoUtil;
import me.ele.amigo.compat.NativeLibraryHelperCompat;
import me.ele.amigo.utils.DexExtractor;
import static me.ele.amigo.utils.CrcUtils.getCrc;
public class ApkReleaser {
private static final String TAG = ApkReleaser.class.getSimpleName();
private static final String DEX_SUFFIX = ".dex";
private volatile boolean isReleasing = false;
private Context context;
private ExecutorService service;
private AmigoDirs amigoDirs;
private PatchApks patchApks;
public ApkReleaser(final Context appContext) {
this.context = appContext;
int processorCount = Runtime.getRuntime().availableProcessors();
final int dexCountInApkCommonly = 3;
this.service =
Executors.newFixedThreadPool(Math.min(dexCountInApkCommonly, processorCount));
}
private void handleDexOptSuccess(String checksum, Handler msgHandler) {
saveDexAndSoChecksum(checksum);
PatchInfoUtil.updateDexFileOptStatus(context, checksum, true);
PatchInfoUtil.setWorkingChecksum(context, checksum);
if (msgHandler != null) {
msgHandler.sendEmptyMessage(AmigoService.MSG_ID_DEX_OPT_SUCCESS);
}
}
private void handleDexOptFailure(String checksum, Handler msgHandler) {
PatchInfoUtil.setWorkingChecksum(context, "");
PatchInfoUtil.updateDexFileOptStatus(context, checksum, true);
if (msgHandler != null) {
msgHandler.sendEmptyMessage(AmigoService.MSG_ID_DEX_OPT_FAILURE);
}
}
public void release(final String checksum, final Handler msgHandler) {
if (isReleasing) {
Log.w(TAG, "release : been busy now, skip release " + checksum);
return;
}
Log.d(TAG, "release: start release " + checksum);
try {
this.amigoDirs = AmigoDirs.getInstance(context);
this.patchApks = PatchApks.getInstance(context);
} catch (Exception e) {
Log.e(TAG,
"release: unable to create amigo dir and patch apk dir, abort release dex files",
e);
handleDexOptFailure(checksum, msgHandler);
return;
}
isReleasing = true;
service.submit(new Runnable() {
@Override
public void run() {
if (!new DexExtractor(context, checksum).extractDexFiles()) {
Log.e(TAG, "releasing dex failed");
handleDexOptFailure(checksum, msgHandler);
isReleasing = false;
return;
}
int errorCode;
if ((errorCode =
NativeLibraryHelperCompat.copyNativeBinaries(patchApks.patchFile(checksum),
amigoDirs.libDir(checksum))) < 0) {
Log.e(TAG, "coping native binaries failed, errorCode = " + errorCode);
handleDexOptFailure(checksum, msgHandler);
isReleasing = false;
return;
}
if (Build.VERSION.SDK_INT >= 21 ? dexOptimizationOnArt(checksum)
: dexOptimizationOnDalvik(checksum)) {
Log.e(TAG, "optimize dex succeed");
handleDexOptSuccess(checksum, msgHandler);
isReleasing = false;
return;
}
Log.e(TAG, "optimize dex failed");
handleDexOptFailure(checksum, msgHandler);
isReleasing = false;
}
});
}
private boolean dexOptimizationOnArt(final String checksum) {
Log.e(TAG, "dexOptimizationOnArt");
String apk = patchApks.patchPath(checksum);
String optimizedPath = optimizedPathFor(apk, amigoDirs.dexOptDir(checksum));
DexFile dexFile = null;
boolean success = true;
try {
dexFile = DexFile.loadDex(apk, optimizedPath, 0);
} catch (IOException e) {
e.printStackTrace();
success = false;
} finally {
if (dexFile != null) {
try {
dexFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return success;
}
private boolean dexOptimizationOnDalvik(final String checksum) {
Log.e(TAG, "dexOptimizationOnDalvik");
final ArrayList<String> validDexPaths = new ArrayList<String>();
amigoDirs.dexDir(checksum).listFiles(new FileFilter() {
@Override public boolean accept(File pathname) {
if (pathname.getName().endsWith(".zip")) {
validDexPaths.add(pathname.getAbsolutePath());
}
return false;
}
});
if (validDexPaths.isEmpty()) {
Log.e(TAG, "dexOptimizationOnDalvik: no dex to optmize");
return false;
}
final CountDownLatch countDownLatch = new CountDownLatch(validDexPaths.size());
final AtomicBoolean allDexOptimized = new AtomicBoolean(true);
final AtomicInteger optimizedDexCount = new AtomicInteger(validDexPaths.size());
for (final String dexPath : validDexPaths) {
service.submit(new Runnable() {
@Override
public void run() {
if (!allDexOptimized.get()) {
Log.e(TAG, "abort optimizing dex["
+ dexPath
+ "] because an error occurred before");
countDownLatch.countDown();
return;
}
String optimizedPath = optimizedPathFor(dexPath, amigoDirs.dexOptDir(checksum));
boolean success = doOptimizeDex(dexPath, optimizedPath);
if (!success) {
allDexOptimized.compareAndSet(true, false);
} else {
optimizedDexCount.decrementAndGet();
}
countDownLatch.countDown();
}
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "dex opt job finished");
return allDexOptimized.get() && optimizedDexCount.get() == 0;
}
private boolean doOptimizeDex(String dexPath, String odexPath) {
long startTime = System.currentTimeMillis();
DexFile dexFile = null;
IOException exception = null;
try {
dexFile = DexFile.loadDex(dexPath, odexPath, 0);
} catch (IOException e) {
exception = e;
} finally {
if (dexFile != null) {
try {
dexFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
boolean success = exception == null;
Log.e(TAG, String.format("optimizing dex %s %s, consumed %d mills",
dexPath, (success ? "succeed" : "failed"),
System.currentTimeMillis() - startTime), exception);
return success;
}
private String optimizedPathFor(String path, File optimizedDirectory) {
File dexFile = new File(path);
String fileName = dexFile.getName();
if (!fileName.endsWith(DEX_SUFFIX)) {
int lastDot = fileName.lastIndexOf(".");
if (lastDot < 0) {
fileName += DEX_SUFFIX;
} else {
StringBuilder sb = new StringBuilder(lastDot + 4);
sb.append(fileName, 0, lastDot);
sb.append(DEX_SUFFIX);
fileName = sb.toString();
}
}
File result = new File(optimizedDirectory, fileName);
return result.getPath();
}
private void saveDexAndSoChecksum(String apkChecksum) {
HashMap<String, String> checksumMap = new HashMap<>();
File[] dexFiles = amigoDirs.dexDir(apkChecksum).listFiles();
for (File dexFile : dexFiles) {
String checksum = getCrc(dexFile);
checksumMap.put(dexFile.getAbsolutePath(), checksum);
}
File[] dexOptFiles = amigoDirs.dexOptDir(apkChecksum).listFiles();
for (File dexOptFile : dexOptFiles) {
String checksum = getCrc(dexOptFile);
checksumMap.put(dexOptFile.getAbsolutePath(), checksum);
}
File[] nativeFiles = amigoDirs.libDir(apkChecksum).listFiles();
if (nativeFiles != null && nativeFiles.length > 0) {
for (File nativeFile : nativeFiles) {
String checksum = getCrc(nativeFile);
checksumMap.put(nativeFile.getAbsolutePath(), checksum);
}
}
PatchInfoUtil.updatePatchFileChecksum(context, apkChecksum, checksumMap);
}
}