Alter install logic to build single giant apk
This commit is contained in:
parent
a6c21a83a8
commit
02ab9856f4
|
@ -9,7 +9,7 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "com.zane.smapiinstaller"
|
||||
minSdkVersion 19
|
||||
minSdkVersion 21
|
||||
//noinspection ExpiringTargetSdkVersion
|
||||
targetSdkVersion 30
|
||||
versionCode 73
|
||||
|
|
|
@ -13,13 +13,11 @@ import android.util.Log;
|
|||
import com.android.apksig.ApkSigner;
|
||||
import com.android.apksig.ApkVerifier;
|
||||
import com.android.apksig.DefaultApkSignerEngine;
|
||||
import com.android.apksig.util.DataSinks;
|
||||
import com.android.apksig.util.DataSources;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.google.common.base.Ticker;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import com.zane.smapiinstaller.BuildConfig;
|
||||
import com.zane.smapiinstaller.MainActivity;
|
||||
import com.zane.smapiinstaller.R;
|
||||
|
@ -31,6 +29,7 @@ import com.zane.smapiinstaller.entity.ApkFilesManifest;
|
|||
import com.zane.smapiinstaller.entity.ManifestEntry;
|
||||
import com.zane.smapiinstaller.utils.DialogUtils;
|
||||
import com.zane.smapiinstaller.utils.FileUtils;
|
||||
import com.zane.smapiinstaller.utils.PackageInstallUtil;
|
||||
import com.zane.smapiinstaller.utils.StringUtils;
|
||||
import com.zane.smapiinstaller.utils.ZipUtils;
|
||||
|
||||
|
@ -49,6 +48,7 @@ import java.security.KeyStore;
|
|||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -173,11 +173,12 @@ public class ApkPatcher {
|
|||
* 将指定APK文件重新打包,添加SMAPI,修改AndroidManifest.xml,同时验证版本是否正确
|
||||
*
|
||||
* @param apkPath APK文件路径
|
||||
* @param second
|
||||
* @param targetFile 目标文件
|
||||
* @param isAdvanced 是否高级模式
|
||||
* @return 是否成功打包
|
||||
*/
|
||||
public boolean patch(String apkPath, File targetFile, boolean isAdvanced, boolean isResourcePack) {
|
||||
public boolean patch(String apkPath, String[] resourcePacks, File targetFile, boolean isAdvanced, boolean isResourcePack) {
|
||||
if (apkPath == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -210,7 +211,7 @@ public class ApkPatcher {
|
|||
int baseProgress = 10;
|
||||
stopwatch.reset();
|
||||
stopwatch.start();
|
||||
originSignInfo = ZipUtils.addOrReplaceEntries(apkPath, entries, targetFile.getAbsolutePath(),
|
||||
originSignInfo = ZipUtils.addOrReplaceEntries(apkPath, resourcePacks, entries, targetFile.getAbsolutePath(),
|
||||
(entryName) -> entryName.startsWith("assemblies/assemblies.") || (isAdvanced && entryName.startsWith("assets/Content")),
|
||||
(progress) -> emitProgress((int) (baseProgress + (progress / 100.0) * 35)));
|
||||
stopwatch.stop();
|
||||
|
@ -462,7 +463,7 @@ public class ApkPatcher {
|
|||
if (thread.isAlive() && !thread.isInterrupted()) {
|
||||
thread.interrupt();
|
||||
}
|
||||
if (result.containsErrors()) {
|
||||
if (result.containsErrors() && result.getErrors().size() > 0) {
|
||||
errorMessage.set(result.getErrors().stream().map(ApkVerifier.IssueWithParams::toString).collect(Collectors.joining(",")));
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Files;
|
||||
import com.zane.smapiinstaller.MainApplication;
|
||||
import com.zane.smapiinstaller.R;
|
||||
|
@ -16,10 +15,8 @@ import com.zane.smapiinstaller.constant.Constants;
|
|||
import com.zane.smapiinstaller.constant.DialogAction;
|
||||
import com.zane.smapiinstaller.databinding.FragmentInstallBinding;
|
||||
import com.zane.smapiinstaller.dto.Tuple2;
|
||||
import com.zane.smapiinstaller.logic.ActivityResultHandler;
|
||||
import com.zane.smapiinstaller.logic.ApkPatcher;
|
||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||
import com.zane.smapiinstaller.logic.ModAssetsManager;
|
||||
import com.zane.smapiinstaller.ui.main.MainTabsFragmentDirections;
|
||||
import com.zane.smapiinstaller.utils.ConfigUtils;
|
||||
import com.zane.smapiinstaller.utils.DialogUtils;
|
||||
|
@ -151,7 +148,7 @@ public class InstallFragment extends Fragment {
|
|||
// modAssetsManager.installDefaultMods();
|
||||
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.patching_package, 8);
|
||||
File targetApk = new File(dest, "base.apk");
|
||||
if (!patcher.patch(paths.getFirst(), targetApk, isAdv, false)) {
|
||||
if (!patcher.patch(paths.getFirst(), paths.getSecond(), targetApk, isAdv, false)) {
|
||||
int target = patcher.getSwitchAction().getAndSet(0);
|
||||
if (target == R.string.menu_download) {
|
||||
DialogUtils.showConfirmDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_patch_game)), R.string.menu_download, R.string.cancel, (d, which) -> {
|
||||
|
@ -165,21 +162,21 @@ public class InstallFragment extends Fragment {
|
|||
}
|
||||
return;
|
||||
}
|
||||
List<String> resourcePacks = new ArrayList<>();
|
||||
if (paths.getSecond() != null) {
|
||||
for (String resourcePack : paths.getSecond()) {
|
||||
File targetResourcePack = new File(dest, FilenameUtils.getName(resourcePack));
|
||||
patcher.patch(resourcePack, targetResourcePack, false, true);
|
||||
resourcePacks.add(targetResourcePack.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
// List<String> resourcePacks = new ArrayList<>();
|
||||
// if (paths.getSecond() != null) {
|
||||
// for (String resourcePack : paths.getSecond()) {
|
||||
// File targetResourcePack = new File(dest, FilenameUtils.getName(resourcePack));
|
||||
// patcher.patch(resourcePack, paths.getSecond(), targetResourcePack, false, true);
|
||||
// resourcePacks.add(targetResourcePack.getAbsolutePath());
|
||||
// }
|
||||
// }
|
||||
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.signing_package, null);
|
||||
String signPath = patcher.sign(targetApk.getAbsolutePath());
|
||||
if (signPath == null) {
|
||||
DialogUtils.showAlertDialog(binding.getRoot(), R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game)));
|
||||
return;
|
||||
}
|
||||
List<String> signedResourcePacks = resourcePacks.stream().map(patcher::sign).collect(Collectors.toList());
|
||||
// List<String> signedResourcePacks = resourcePacks.stream().map(patcher::sign).collect(Collectors.toList());
|
||||
DialogUtils.setProgressDialogState(binding.getRoot(), dialog, R.string.installing_package, null);
|
||||
patcher.install(signPath);
|
||||
}));
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package com.zane.smapiinstaller.utils;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.pm.PackageInstaller;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
public class PackageInstallUtil {
|
||||
public static final String ACTION_INSTALL_COMPLETE = "com.zane.smapiinstaller.INSTALL_COMPLETE";
|
||||
|
||||
public static boolean installPackage(Context context, String packageName, String apkPath, List<String> signedResourcePacks) {
|
||||
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
|
||||
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_INHERIT_EXISTING);
|
||||
params.setAppPackageName(packageName);
|
||||
try {
|
||||
// set params
|
||||
int sessionId = packageInstaller.createSession(params);
|
||||
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
|
||||
writePackage(session, apkPath);
|
||||
for (String pack : signedResourcePacks) {
|
||||
writePackage(session, pack);
|
||||
}
|
||||
session.commit(createIntentSender(context, sessionId));
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writePackage(PackageInstaller.Session session, String apkPath) throws IOException {
|
||||
File file = new File(apkPath);
|
||||
try(FileInputStream in = new FileInputStream(file)) {
|
||||
OutputStream out = session.openWrite(file.getName(), 0, -1);
|
||||
byte[] buffer = new byte[65536];
|
||||
int c;
|
||||
while ((c = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, c);
|
||||
}
|
||||
session.fsync(out);
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static IntentSender createIntentSender(Context context, int sessionId) {
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
sessionId,
|
||||
new Intent(ACTION_INSTALL_COMPLETE),
|
||||
0);
|
||||
return pendingIntent.getIntentSender();
|
||||
}
|
||||
}
|
|
@ -118,7 +118,7 @@ public class ZipUtils {
|
|||
return result;
|
||||
}
|
||||
|
||||
public static Tuple2<byte[], Set<String>> addOrReplaceEntries(String inputZipFilename, List<ZipEntrySource> entrySources, String outputZipFilename, Function<String, Boolean> removePredict, Consumer<Integer> progressCallback) throws IOException {
|
||||
public static Tuple2<byte[], Set<String>> addOrReplaceEntries(String inputZipFilename, String[] resourcePacks, List<ZipEntrySource> entrySources, String outputZipFilename, Function<String, Boolean> removePredict, Consumer<Integer> progressCallback) throws IOException {
|
||||
File inFile = new File(inputZipFilename).getCanonicalFile();
|
||||
File outFile = new File(outputZipFilename).getCanonicalFile();
|
||||
if (inFile.equals(outFile)) {
|
||||
|
@ -127,11 +127,11 @@ public class ZipUtils {
|
|||
ImmutableMap<String, ZipEntrySource> entryMap = Maps.uniqueIndex(entrySources, ZipEntrySource::getPath);
|
||||
byte[] originManifest = null;
|
||||
ConcurrentHashMap<String, Boolean> originEntryName = new ConcurrentHashMap<>();
|
||||
try (ZipOutput zipOutput = new ZipOutput(new FileOutputStream(outputZipFilename))) {
|
||||
try (ZipInput input = new ZipInput(inputZipFilename)) {
|
||||
int size = input.entries.values().size();
|
||||
AtomicLong count = new AtomicLong();
|
||||
int reportInterval = size / 100;
|
||||
try (ZipOutput zipOutput = new ZipOutput(new FileOutputStream(outputZipFilename))) {
|
||||
ConcurrentHashMap<String, Boolean> replacedFileSet = new ConcurrentHashMap<>(entryMap.size());
|
||||
MultiprocessingUtil.TaskBundle<ZioEntry> taskBundle = MultiprocessingUtil.newTaskBundle((zioEntry) -> {
|
||||
try {
|
||||
|
@ -200,6 +200,22 @@ public class ZipUtils {
|
|||
taskBundle.join();
|
||||
progressCallback.accept(100);
|
||||
}
|
||||
for (String resourcePack : resourcePacks) {
|
||||
try (ZipInput input = new ZipInput(resourcePack)) {
|
||||
for (ZioEntry inEntry : input.entries.values()) {
|
||||
if(inEntry.getName().startsWith("assets/Content")) {
|
||||
ZioEntry zioEntry = new ZioEntry(inEntry.getName());
|
||||
zioEntry.setCompression(inEntry.getCompression());
|
||||
try (InputStream inputStream = inEntry.getInputStream()) {
|
||||
ByteStreams.copy(inputStream, zioEntry.getOutputStream());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
zipOutput.write(zioEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
if (e.getCause() != null && e.getCause() instanceof IOException) {
|
||||
throw (IOException) e.getCause();
|
||||
|
|
Loading…
Reference in New Issue