Performance improvements and cache invalidation fix

* Combining assets.zip and res.zip for better performance
* Fix apk path on cache invalidation
This commit is contained in:
Vologhat
2023-02-21 11:31:49 +03:00
parent a45e6a7f2a
commit b74cbe54ed
6 changed files with 134 additions and 81 deletions
@@ -2,14 +2,23 @@ package ru.vtosters.lite.hooks;
import android.app.Activity;
import android.os.Build;
import b.h.g.k.VKProgressDialog;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vtosters.lite.R;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
import ru.vtosters.lite.ssfs.UsersList;
import ru.vtosters.lite.themes.ThemesManager;
import ru.vtosters.lite.ui.dialogs.DisableBattery;
import ru.vtosters.lite.ui.dialogs.InstallGMS;
import ru.vtosters.lite.ui.dialogs.OTADialog;
import ru.vtosters.lite.ui.dialogs.Start;
import ru.vtosters.lite.utils.DeletedMessagesHandler;
import ru.vtosters.lite.utils.LifecycleUtils;
import ru.vtosters.lite.utils.Preferences;
import ru.vtosters.lite.utils.ThemesUtils;
import java.io.IOException;
import static ru.vtosters.lite.ui.dialogs.ServerDialog.sendRequest;
import static ru.vtosters.lite.utils.CacheUtils.getInstance;
@@ -39,5 +48,64 @@ public class MainActivityInjector {
}
// VKIDProtection.alert(activity);
if(Preferences.isNewBuild() && !ThemesUtils.isMonetTheme() && ThemesManager.hasTmpArchive()) {
Preferences.updateBuildNumber();
updateBinsAndTmpArchive(activity, false);
}
}
private static void updateBinsAndTmpArchive(Activity activity, boolean applyAccent) {
final VKProgressDialog dialog = new VKProgressDialog(activity);
String msg;
Runnable task;
if(ThemesManager.hasBins() && !applyAccent) {
msg = "Инвалидация кэша";
task = () -> {
try {
ThemesManager.deleteBins();
ThemesManager.generateBins();
activity.runOnUiThread(() -> {
dialog.cancel();
updateBinsAndTmpArchive(activity, true);
});
} catch(IOException e) {
e.printStackTrace();
activity.runOnUiThread(() -> {
dialog.cancel();
showErrorDialog(activity, "Ошибка при инвалидации кэша");
});
}
};
} else {
msg = "Применение акцента";
task = () -> {
try {
if(ThemesManager.hasTmpArchive()) ThemesManager.deleteTmpArchive();
ThemesManager.generateTempResArchive(ThemesUtils.getReservedAccent());
activity.runOnUiThread(LifecycleUtils::restartApplication);
} catch(IOException e) {
e.printStackTrace();
activity.runOnUiThread(() -> {
dialog.cancel();
showErrorDialog(activity, "Ошибка при применении акцента");
});
}
};
}
dialog.setMessage(msg);
dialog.show();
VTExecutors.getIoExecutor().execute(task);
}
static void showErrorDialog(Activity activity, String msg) {
new VkAlertDialog.Builder(activity)
.setTitle("Ошикба")
.setMessage(msg)
.setPositiveButton(R.string.ok, null)
.show();
}
}
@@ -8,12 +8,10 @@ import com.vtosters.lite.R;
import ru.vtosters.lite.themes.loaders.ResourcesLoader;
import ru.vtosters.lite.themes.utils.ArscEditor;
import ru.vtosters.lite.utils.IOUtils;
import ru.vtosters.lite.utils.Preferences;
import ru.vtosters.lite.utils.ThemesUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
@@ -95,11 +93,8 @@ public class ThemesManager {
R.color.darker_blue,
R.color.header_blue_opacity40
};
private final static String[] NAMES = {
"resources.arsc",
"res",
"assets"
};
private static String baseApkPath;
// Directories
private static File mainDir;
@@ -111,10 +106,7 @@ public class ThemesManager {
// Temp archive with resources.arsc and res.zip content for loading resources
private static File resTmpZipFile = null;
// Archive with assets folder content for loading resources
private static File assetsZipFile = null;
// Archive with res folder content
// Archive with res and assets folders content
private static File resZipFile = null;
// Resources table file
@@ -125,9 +117,9 @@ public class ThemesManager {
Log.d("ThemesManager", "init paths");
initPaths(app);
if (hasTmpArchive() && !ThemesUtils.isMonetTheme()) {
if (!Preferences.isNewBuild() && !ThemesUtils.isMonetTheme() && hasBins() && hasTmpArchive()) {
ResourcesLoader.init(app);
ResourcesLoader.load(app, resTmpZipFile.getAbsolutePath(), assetsZipFile.getAbsolutePath(), false);
ResourcesLoader.load(app, resTmpZipFile.getAbsolutePath(), false);
}
} catch (Exception e) {
Log.e("ThemesManager", String.valueOf(e));
@@ -135,62 +127,46 @@ public class ThemesManager {
}
private static void initPaths(Context context) {
baseApkPath = context.getApplicationInfo().sourceDir;
mainDir = new File(context.getFilesDir(), "VTLThemes");
if (!mainDir.exists()) mainDir.mkdir();
binDir = new File(mainDir, "bin");
if (!binDir.exists()) mainDir.mkdir();
if (!binDir.exists()) binDir.mkdir();
palettesDir = new File(mainDir, "palettes");
if (!palettesDir.exists()) palettesDir.mkdir();
assetsZipFile = new File(binDir, "assets.zip");
resZipFile = new File(binDir, "res.zip");
resourcesArscFile = new File(binDir, "resources.arsc");
resTmpZipFile = new File(binDir, "res.tmp.zip");
}
public static void generateBins(Context context)
public static void generateBins()
throws IOException {
try (final var apk = new ZipFile(context.getApplicationInfo().sourceDir)) {
for (var filename : NAMES) {
final var bin = new File(binDir, filename);
if (!bin.exists())
if (filename.contains(".")) extractFile(apk, filename, bin);
else extractAndCompressDirectory(apk, filename, new File(binDir, filename + ".zip"));
}
}
}
private static void extractFile(ZipFile apk, String name, File out)
throws IOException {
final var is = apk.getInputStream(apk.getEntry(name));
if (!out.exists()) out.getParentFile().mkdirs();
IOUtils.copy(is, out);
}
private static void extractAndCompressDirectory(ZipFile apk, String dirName, File out)
throws IOException {
final var zos = new ZipOutputStream(new FileOutputStream(out));
try (zos) {
final var apk = new ZipFile(baseApkPath);
final var zos = new ZipOutputStream(new FileOutputStream(resZipFile));
try(apk;zos) {
final var entries = apk.entries();
while (entries.hasMoreElements()) {
while(entries.hasMoreElements()) {
final var entry = entries.nextElement();
if (!entry.getName().startsWith(dirName + "/")) continue;
zos.putNextEntry(new ZipEntry(entry.getName()));
final var zis = apk.getInputStream(entry);
IOUtils.copy(zis, zos);
zis.close();
zos.closeEntry();
final var name = entry.getName();
if(name.equals("resources.arsc")) {
if (!resourcesArscFile.exists()) resourcesArscFile.getParentFile().mkdirs();
IOUtils.copy(apk.getInputStream(entry), resourcesArscFile);
} else if(name.startsWith("assets/") || name.startsWith("res/")) {
zos.putNextEntry(new ZipEntry(name));
IOUtils.copy(apk.getInputStream(entry), zos);
zos.closeEntry();
}
}
}
}
public static boolean hasBins() {
return assetsZipFile.exists() && resZipFile.exists() && resourcesArscFile.exists();
return resZipFile.exists() && resourcesArscFile.exists();
}
public static boolean hasTmpArchive() {
@@ -227,7 +203,6 @@ public class ThemesManager {
}
public static void deleteBins() {
assetsZipFile.delete();
resZipFile.delete();
resourcesArscFile.delete();
}
@@ -108,7 +108,7 @@ public class ResourcesLoader {
}
}
public static void load(Context context, String resPath, String assetsPath, boolean reinject)
public static void load(Context context, String resPath, boolean reinject)
throws Exception {
final ApplicationInfo appInfo = context.getApplicationInfo();
@@ -136,8 +136,7 @@ public class ResourcesLoader {
sAssetManager = AssetManager.class.newInstance();
// Create a new AssetManager instance and point it to the resources installed under
if (AssetManagerHelper.addAssetPath(sAssetManager, resPath) == 0
|| AssetManagerHelper.addAssetPath(sAssetManager, assetsPath) == 0)
if (AssetManagerHelper.addAssetPath(sAssetManager, resPath) == 0)
throw new IllegalStateException("Could not create new AssetManager");
// Add SharedLibraries to AssetManager for resolve system resources not found issue
@@ -192,10 +191,10 @@ public class ResourcesLoader {
}
}
installResourceInsuranceHacks(context, resPath, assetsPath);
installResourceInsuranceHacks(context, resPath);
}
private static void installResourceInsuranceHacks(Context context, String patchedResApkPath, String patchedAssetsApkPath) {
private static void installResourceInsuranceHacks(Context context, String patchedResApkPath) {
try {
final Object activityThread = ReflectionUtils.getActivityThread(context, null);
final Field mHField = ReflectionUtils.findField(activityThread, "mH");
@@ -204,7 +203,7 @@ public class ResourcesLoader {
final Handler.Callback originCallback = (Handler.Callback) mCallbackField.get(mH);
if (!(originCallback instanceof ResourceInsuranceHandlerCallback)) {
final ResourceInsuranceHandlerCallback hackCallback = new ResourceInsuranceHandlerCallback(
context, patchedResApkPath, patchedAssetsApkPath, originCallback, mH.getClass());
context, patchedResApkPath, originCallback, mH.getClass());
mCallbackField.set(mH, hackCallback);
} else {
Log.w("ResourcesLoader", "installResourceInsuranceHacks: already installed, skip rest logic.");
@@ -274,7 +273,6 @@ public class ResourcesLoader {
private final Context mContext;
private final String mPatchResApkPath;
private final String mPatchedAssetsApkPath;
private final Handler.Callback mOriginalCallback;
private final int LAUNCH_ACTIVITY;
@@ -284,11 +282,10 @@ public class ResourcesLoader {
private Method mGetCallbacksMethod = null;
private boolean mSkipInterceptExecuteTransaction = false;
ResourceInsuranceHandlerCallback(Context context, String patchResApkPath, String patchedAssetsApkPath, Handler.Callback original, Class<?> hClazz) {
ResourceInsuranceHandlerCallback(Context context, String patchResApkPath, Handler.Callback original, Class<?> hClazz) {
Context appContext = context.getApplicationContext();
mContext = (appContext != null ? appContext : context);
mPatchResApkPath = patchResApkPath;
mPatchedAssetsApkPath = patchedAssetsApkPath;
mOriginalCallback = original;
LAUNCH_ACTIVITY = fetchMessageId(hClazz, "LAUNCH_ACTIVITY", 100);
RELAUNCH_ACTIVITY = fetchMessageId(hClazz, "RELAUNCH_ACTIVITY", 126);
@@ -365,7 +362,7 @@ public class ResourcesLoader {
}
if (shouldReInjectPatchedResources) {
try {
load(mContext, mPatchResApkPath, mPatchedAssetsApkPath, true);
load(mContext, mPatchResApkPath, true);
} catch (Throwable thr) {
thr.printStackTrace();
}
@@ -58,17 +58,8 @@ public class ThemesFragment extends MaterialPreferenceToolbarFragment {
var invalidateThemeCache = findPreference("invalidate_theme_cache");
invalidateThemeCache.setOnPreferenceClickListener(preference -> {
mProgressDialog.setMessage("Инвалидация кэша...");
mProgressDialog.show();
VTExecutors.getIoExecutor().execute(() -> {
ThemesManager.deleteBins();
try {
ThemesManager.generateBins(requireContext());
requireActivity().runOnUiThread(this::restart);
} catch(IOException e) {
requireActivity().runOnUiThread(() -> showErrorDialog("Ошибка при инвалидации кэша"));
}
});
ThemesManager.deleteBins();
setAccentColor(ThemesUtils.getReservedAccent(), true);
return true;
});
@@ -193,7 +184,7 @@ public class ThemesFragment extends MaterialPreferenceToolbarFragment {
alertDialog.setButton(
DialogInterface.BUTTON_POSITIVE,
requireContext().getString(R.string.select),
(dialog, which) -> setAccentColor(colorPickerView.getColor())
(dialog, which) -> setAccentColor(colorPickerView.getColor(), false)
);
alertDialog.setView(colorPickerView);
alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
@@ -210,38 +201,39 @@ public class ThemesFragment extends MaterialPreferenceToolbarFragment {
.setTitle(AndroidUtils.getString("select_palette"))
.setItems(titles, (dialog, which) ->
PalettesBottomSheetDialog.create(requireActivity(), manager.getPalette(which),
(adapter, vtlcolor) -> setAccentColor(vtlcolor.color))
(adapter, vtlcolor) -> setAccentColor(vtlcolor.color, false))
)
.setPositiveButton(R.string.cancel, null)
.show();
}
}
void setAccentColor(int color) {
void setAccentColor(int color, boolean invalidate) {
String message;
Runnable task;
if(!ThemesManager.hasBins()) {
message = "Генерация файлов...";
message = invalidate ? "Инвалидация кэша" : "Генерация файлов...";
task = () -> {
try {
ThemesManager.generateBins(requireContext());
requireActivity().runOnUiThread(() -> setAccentColor(color));
ThemesManager.generateBins();
requireActivity().runOnUiThread(() -> setAccentColor(color, invalidate));
} catch(IOException e) {
Log.e("ThemesFragment", e+"");
ThemesManager.deleteBins();
requireActivity().runOnUiThread(() -> showErrorDialog("Ошибка при генерации файлов"));
requireActivity().runOnUiThread(() -> showErrorDialog("Ошибка при " + (invalidate ? "инвалидации кэша" : "генерации файлов")));
}
};
} else {
message = "Применение акцента...";
message = invalidate ? "Инвалидация кэша" : "Применение акцента...";
task = () -> {
try {
if(!invalidate) ThemesUtils.reserveAccentColor(color);
ThemesManager.generateTempResArchive(color);
requireActivity().runOnUiThread(this::restart);
} catch(IOException e) {
Log.e("ThemesFragment", e+"");
ThemesManager.deleteTmpArchive();
requireActivity().runOnUiThread(() -> showErrorDialog("Ошибка при применении акцента"));
requireActivity().runOnUiThread(() -> showErrorDialog("Ошибка при " + (invalidate ? "инвалидации кэша" : "применении акцента")));
}
};
}
@@ -291,6 +291,15 @@ public class Preferences {
return !getBoolValue("isRoamingState", false) && isValidSignature() && (!dev() || getBoolValue("autoupdates", true));
}
public static boolean isNewBuild() {
return !preferences.getString("build_number", About.getBuildNumber())
.equals(About.getBuildNumber());
}
public static void updateBuildNumber() {
preferences.edit().putString("build_number", About.getBuildNumber()).apply();
}
public static boolean isValidSignature() {
try {
return validateAppSignature();
@@ -101,6 +101,18 @@ public class ThemesUtils {
return getColorFromAttr(R.attr.accent);
} // Color accent
//region Used for migrating accent color to new version
public static int getReservedAccent() {
return AndroidUtils.getPreferences().getInt("reserved_accent_color", getAccentColor());
}
public static void reserveAccentColor(int accent) {
AndroidUtils.getPreferences().edit().putInt("reserved_accent_color", accent).apply();
}
//endregion
public static int getMutedAccentColor() {
return getMutedColor(getAccentColor());
}