diff --git a/tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/listener/DefaultPatchListener.java b/tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/listener/DefaultPatchListener.java index 42eabbb0c..52752ce0e 100644 --- a/tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/listener/DefaultPatchListener.java +++ b/tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/listener/DefaultPatchListener.java @@ -35,6 +35,10 @@ public DefaultPatchListener(Context context) { @Override public int onPatchReceived(String path) { + return checkPackageAndRunPatchService(path, false); + } + + protected int checkPackageAndRunPatchService(String path, boolean useEmergencyMode) { final int returnCode = patchCheck(path, null); Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode); return returnCode; diff --git a/tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/patch/AbstractPatch.java b/tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/patch/AbstractPatch.java index 1cbe40257..c28ed4225 100644 --- a/tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/patch/AbstractPatch.java +++ b/tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/patch/AbstractPatch.java @@ -25,5 +25,5 @@ */ public abstract class AbstractPatch { - public abstract boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult); + public abstract boolean tryPatch(Context context, String tempPatchPath, boolean useEmergencyMode, PatchResult patchResult); } diff --git a/tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/patch/UpgradePatch.java b/tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/patch/UpgradePatch.java index 7dfa2f9bb..7e2b2a8f5 100644 --- a/tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/patch/UpgradePatch.java +++ b/tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/patch/UpgradePatch.java @@ -31,7 +31,7 @@ public class UpgradePatch extends AbstractPatch { private static final String TAG = "Tinker.UpgradePatch"; @Override - public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) { + public boolean tryPatch(Context context, String tempPatchPath, boolean useEmergencyMode, PatchResult patchResult) { ShareTinkerLog.e(TAG, "[-] Ignore this invocation since I'm no-op version."); return false; } diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/listener/DefaultPatchListener.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/listener/DefaultPatchListener.java index e48317a9a..cf8bb2283 100644 --- a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/listener/DefaultPatchListener.java +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/listener/DefaultPatchListener.java @@ -58,19 +58,34 @@ public DefaultPatchListener(Context context) { */ @Override public int onPatchReceived(String path) { + return checkPackageAndRunPatchService(path, false); + } + + /** + * Check patch package then start patch service to generate patched artifacts. + * @param path + * Path to your patch package. + * @param useEmergencyMode + * true for using emergency mode, otherwise false. + * + * By using emergency mode, dex2oat triggering procedure will be done asynchronously on Android Q and newer + * system to save costs. If your app lives too short to wait for generating patch artifacts, this mode should + * help. **However, the performance of your patched app will become terribly worse since odex of patched dex(es) + * may not be generated before loading patched artifacts in this mode.** + */ + protected int checkPackageAndRunPatchService(String path, boolean useEmergencyMode) { final File patchFile = new File(path); final String patchMD5 = SharePatchFileUtil.getMD5(patchFile); final int returnCode = patchCheck(path, patchMD5); if (returnCode == ShareConstants.ERROR_PATCH_OK) { runForgService(); - TinkerPatchService.runPatchService(context, path); + TinkerPatchService.runPatchService(context, path, useEmergencyMode); } else { Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode); } return returnCode; } - private void runForgService() { try { connection = new ServiceConnection() { @@ -156,5 +171,4 @@ protected int patchCheck(String path, String patchMd5) { return ShareConstants.ERROR_PATCH_OK; } - } diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/AbstractPatch.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/AbstractPatch.java index 1cbe40257..c28ed4225 100644 --- a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/AbstractPatch.java +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/AbstractPatch.java @@ -25,5 +25,5 @@ */ public abstract class AbstractPatch { - public abstract boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult); + public abstract boolean tryPatch(Context context, String tempPatchPath, boolean useEmergencyMode, PatchResult patchResult); } diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/DexDiffPatchInternal.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/DexDiffPatchInternal.java index 49623e778..210551aaa 100644 --- a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/DexDiffPatchInternal.java +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/DexDiffPatchInternal.java @@ -71,7 +71,8 @@ public class DexDiffPatchInternal extends BasePatchInternal { protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck checker, Context context, - String patchVersionDirectory, File patchFile, PatchResult patchResult) { + String patchVersionDirectory, File patchFile, boolean useEmergencyMode, + PatchResult patchResult) { if (!manager.isEnabledForDex()) { ShareTinkerLog.w(TAG, "patch recover, dex is not enabled"); return true; @@ -84,7 +85,8 @@ protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck c } long begin = SystemClock.elapsedRealtime(); - boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile, patchResult); + boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile, + useEmergencyMode, patchResult); long cost = SystemClock.elapsedRealtime() - begin; patchResult.dexCostTime = cost; ShareTinkerLog.i(TAG, "recover dex result:%b, cost:%d", result, cost); @@ -166,7 +168,9 @@ protected static boolean waitAndCheckDexOptFile(File patchFile, Tinker manager) return true; } - private static boolean patchDexExtractViaDexDiff(Context context, String patchVersionDirectory, String meta, final File patchFile, PatchResult patchResult) { + private static boolean patchDexExtractViaDexDiff(Context context, String patchVersionDirectory, String meta, + final File patchFile, boolean useEmergencyMode, + PatchResult patchResult) { String dir = patchVersionDirectory + "/" + DEX_PATH + "/"; if (!extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)) { @@ -194,7 +198,7 @@ private static boolean patchDexExtractViaDexDiff(Context context, String patchVe ShareTinkerLog.i(TAG, "legal files to do dexopt: " + legalFiles); final String optimizeDexDirectory = patchVersionDirectory + "/" + DEX_OPTIMIZE_PATH + "/"; - return dexOptimizeDexFiles(context, legalFiles, optimizeDexDirectory, patchFile, patchResult); + return dexOptimizeDexFiles(context, legalFiles, optimizeDexDirectory, patchFile, useEmergencyMode, patchResult); } @@ -347,7 +351,9 @@ private static boolean mergeClassNDexFiles(final Context context, final File pat return result; } - private static boolean dexOptimizeDexFiles(Context context, List dexFiles, String optimizeDexDirectory, final File patchFile, final PatchResult patchResult) { + private static boolean dexOptimizeDexFiles(Context context, List dexFiles, String optimizeDexDirectory, + final File patchFile, boolean useEmergencyMode, + final PatchResult patchResult) { final Tinker manager = Tinker.with(context); optFiles.clear(); @@ -381,7 +387,7 @@ private static boolean dexOptimizeDexFiles(Context context, List dexFiles, // try parallel dex optimizer TinkerDexOptimizer.optimizeAll( context, dexFiles, optimizeDexDirectoryFile, - useDLC, + useDLC, useEmergencyMode, new TinkerDexOptimizer.ResultCallback() { long startTime; diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/UpgradePatch.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/UpgradePatch.java index e46dad174..c492098ee 100644 --- a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/UpgradePatch.java +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/patch/UpgradePatch.java @@ -42,7 +42,7 @@ public class UpgradePatch extends AbstractPatch { private static final String TAG = "Tinker.UpgradePatch"; @Override - public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) { + public boolean tryPatch(Context context, String tempPatchPath, boolean useEmergencyMode, PatchResult patchResult) { Tinker manager = Tinker.with(context); final File patchFile = new File(tempPatchPath); @@ -169,7 +169,7 @@ public boolean tryPatch(Context context, String tempPatchPath, PatchResult patch } //we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process - if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile, patchResult)) { + if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile, useEmergencyMode, patchResult)) { ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed"); return false; } diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/PatchResult.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/PatchResult.java index 74f91d2ac..933f54c4b 100644 --- a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/PatchResult.java +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/PatchResult.java @@ -30,6 +30,8 @@ public class PatchResult implements Serializable { public String rawPatchFilePath; + public boolean useEmergencyMode; + public long totalCostTime; public long dexCostTime; @@ -55,6 +57,7 @@ public String toString() { sb.append("\nPatchResult: \n"); sb.append("isSuccess:" + isSuccess + "\n"); sb.append("rawPatchFilePath:" + rawPatchFilePath + "\n"); + sb.append("useEmergencyMode:" + useEmergencyMode + "\n"); sb.append("costTime:" + totalCostTime + "\n"); sb.append("dexoptTriggerTime:" + dexoptTriggerTime + "\n"); sb.append("isOatGenerated:" + isOatGenerated + "\n"); diff --git a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/TinkerPatchService.java b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/TinkerPatchService.java index f311782a4..b44c04cd3 100644 --- a/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/TinkerPatchService.java +++ b/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/service/TinkerPatchService.java @@ -47,6 +47,7 @@ public class TinkerPatchService extends IntentService { private static final String TAG = "Tinker.TinkerPatchService"; private static final String PATCH_PATH_EXTRA = "patch_path_extra"; + private static final String PATCH_USE_EMERGENCY_MODE = "patch_use_emergency_mode"; private static final String RESULT_CLASS_EXTRA = "patch_result_class"; private static AbstractPatch upgradePatchProcessor = null; @@ -59,9 +60,14 @@ public TinkerPatchService() { } public static void runPatchService(final Context context, final String path) { + runPatchService(context, path, false); + } + + public static void runPatchService(final Context context, final String path, boolean useEmergencyMode) { ShareTinkerLog.i(TAG, "run patch service..."); Intent intent = new Intent(context, TinkerPatchService.class); intent.putExtra(PATCH_PATH_EXTRA, path); + intent.putExtra(PATCH_USE_EMERGENCY_MODE, useEmergencyMode); intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName()); try { context.startService(intent); @@ -88,6 +94,13 @@ public static String getPatchPathExtra(Intent intent) { return ShareIntentUtil.getStringExtra(intent, PATCH_PATH_EXTRA); } + public static boolean getPatchUseEmergencyMode(Intent intent) { + if (intent == null) { + throw new TinkerRuntimeException("getPatchUseEmergencyMode, but intent is null"); + } + return ShareIntentUtil.getBooleanExtra(intent, PATCH_USE_EMERGENCY_MODE, false); + } + public static String getPatchResultExtra(Intent intent) { if (intent == null) { throw new TinkerRuntimeException("getPatchResultExtra, but intent is null"); @@ -210,6 +223,8 @@ private static void doApplyPatch(Context context, Intent intent) { } File patchFile = new File(path); + final boolean useEmergencyMode = getPatchUseEmergencyMode(intent); + long begin = SystemClock.elapsedRealtime(); boolean result; long cost; @@ -220,7 +235,7 @@ private static void doApplyPatch(Context context, Intent intent) { if (upgradePatchProcessor == null) { throw new TinkerRuntimeException("upgradePatchProcessor is null."); } - result = upgradePatchProcessor.tryPatch(context, path, patchResult); + result = upgradePatchProcessor.tryPatch(context, path, useEmergencyMode, patchResult); } catch (Throwable throwable) { e = throwable; result = false; @@ -233,6 +248,7 @@ private static void doApplyPatch(Context context, Intent intent) { patchResult.isSuccess = result; patchResult.rawPatchFilePath = path; + patchResult.useEmergencyMode = useEmergencyMode; patchResult.totalCostTime = cost; patchResult.type = tinker.getCustomPatcher() == null ? PatchResult.PATCH_TYPE_BSDIFF : PatchResult.PATCH_TYPE_CUSTOM; patchResult.e = e; diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/NewClassLoaderInjector.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/NewClassLoaderInjector.java index b5b975f0d..db9cdb193 100644 --- a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/NewClassLoaderInjector.java +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/NewClassLoaderInjector.java @@ -139,18 +139,19 @@ private static void doInject(Application app, ClassLoader classLoader) throws Th final Object basePackageInfo = findField(baseContext.getClass(), "mPackageInfo").get(baseContext); findField(basePackageInfo.getClass(), "mClassLoader").set(basePackageInfo, classLoader); - if (Build.VERSION.SDK_INT < 27) { - final Resources res = app.getResources(); - try { - findField(res.getClass(), "mClassLoader").set(res, classLoader); - - final Object drawableInflater = findField(res.getClass(), "mDrawableInflater").get(res); - if (drawableInflater != null) { - findField(drawableInflater.getClass(), "mClassLoader").set(drawableInflater, classLoader); - } - } catch (Throwable ignored) { - // Ignored. + final Resources res = app.getResources(); + try { + findField(res.getClass(), "mClassLoader").set(res, classLoader); + } catch (Throwable ignored) { + // Ignored. + } + try { + final Object drawableInflater = findField(res.getClass(), "mDrawableInflater").get(res); + if (drawableInflater != null) { + findField(drawableInflater.getClass(), "mClassLoader").set(drawableInflater, classLoader); } + } catch (Throwable ignored) { + // Ignored. } } diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexLoader.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexLoader.java index 7d2d4f1d1..58214a037 100644 --- a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexLoader.java +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexLoader.java @@ -153,7 +153,7 @@ public static boolean loadTinkerJars(final TinkerApplication application, String TinkerDexOptimizer.optimizeAll( application, legalFiles, optimizeDir, true, - application.isUseDelegateLastClassLoader(), targetISA, + application.isUseDelegateLastClassLoader(), targetISA, false, new TinkerDexOptimizer.ResultCallback() { long start; diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexOptimizer.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexOptimizer.java index 299c864b2..2f764d939 100644 --- a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexOptimizer.java +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerDexOptimizer.java @@ -79,14 +79,14 @@ public final class TinkerDexOptimizer { * @return If all dexes are optimized successfully, return true. Otherwise return false. */ public static boolean optimizeAll(Context context, Collection dexFiles, File optimizedDir, - boolean useDLC, ResultCallback cb) { + boolean useDLC, boolean useEmergencyMode, ResultCallback cb) { final String targetISA = ShareTinkerInternals.getCurrentInstructionSet(); - return optimizeAll(context, dexFiles, optimizedDir, false, useDLC, targetISA, cb); + return optimizeAll(context, dexFiles, optimizedDir, false, useDLC, targetISA, useEmergencyMode, cb); } public static boolean optimizeAll(Context context, Collection dexFiles, File optimizedDir, boolean useInterpretMode, boolean useDLC, - String targetISA, ResultCallback cb) { + String targetISA, boolean useEmergencyMode, ResultCallback cb) { ArrayList sortList = new ArrayList<>(dexFiles); // sort input dexFiles with its file length in reverse order. Collections.sort(sortList, new Comparator() { @@ -105,7 +105,7 @@ public int compare(File lhs, File rhs) { }); for (File dexFile : sortList) { OptimizeWorker worker = new OptimizeWorker(context, dexFile, optimizedDir, useInterpretMode, - useDLC, targetISA, cb); + useDLC, targetISA, useEmergencyMode, cb); if (!worker.run()) { return false; } @@ -129,10 +129,11 @@ private static class OptimizeWorker { private final File optimizedDir; private final boolean useInterpretMode; private final boolean useDLC; + private final boolean useEmergencyMode; private final ResultCallback callback; OptimizeWorker(Context context, File dexFile, File optimizedDir, boolean useInterpretMode, - boolean useDLC, String targetISA, ResultCallback cb) { + boolean useDLC, String targetISA, boolean useEmergencyMode, ResultCallback cb) { this.context = context; this.dexFile = dexFile; this.optimizedDir = optimizedDir; @@ -140,6 +141,7 @@ private static class OptimizeWorker { this.useDLC = useDLC; this.callback = cb; this.targetISA = targetISA; + this.useEmergencyMode = useEmergencyMode; } boolean run() { @@ -177,15 +179,27 @@ boolean run() { createFakeODexPathStructureOnDemand(optimizedPath); patchClassLoaderStrongRef = NewClassLoaderInjector.triggerDex2Oat(context, optimizedDir, useDLC, dexFile.getAbsolutePath()); - try { - triggerPMDexOptOnDemand(context, dexFile.getAbsolutePath(), optimizedPath); - } catch (Throwable thr) { - ShareTinkerLog.printErrStackTrace(TAG, thr, - "Fail to call triggerPMDexOptAsyncOnDemand."); - } finally { - final String vdexPath = optimizedPath.substring(0, - optimizedPath.lastIndexOf(ODEX_SUFFIX)) + VDEX_SUFFIX; - waitUntilFileGeneratedOrTimeout(context, vdexPath); + final Runnable task = new Runnable() { + @Override + public void run() { + try { + triggerPMDexOptOnDemand(context, dexFile.getAbsolutePath(), optimizedPath); + } catch (Throwable thr) { + ShareTinkerLog.printErrStackTrace(TAG, thr, + "Fail to call triggerPMDexOptAsyncOnDemand."); + } finally { + if (!useEmergencyMode) { + final String vdexPath = optimizedPath.substring(0, + optimizedPath.lastIndexOf(ODEX_SUFFIX)) + VDEX_SUFFIX; + waitUntilFileGeneratedOrTimeout(context, vdexPath); + } + } + } + }; + if (useEmergencyMode) { + new Thread(task, "TinkerDex2oatTrigger").start(); + } else { + task.run(); } } else { patchClassLoaderStrongRef = NewClassLoaderInjector.triggerDex2Oat(context, optimizedDir, diff --git a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerResourcePatcher.java b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerResourcePatcher.java index 0cc310f09..32f695543 100644 --- a/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerResourcePatcher.java +++ b/tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/TinkerResourcePatcher.java @@ -58,6 +58,8 @@ class TinkerResourcePatcher { // original object private static Collection> references = null; + + private static Map> resourceImpls = null; private static Object currentActivityThread = null; private static AssetManager newAssetManager = null; @@ -71,6 +73,7 @@ class TinkerResourcePatcher { private static Field assetsFiled = null; private static Field resourcesImplFiled = null; private static Field resDir = null; + private static Field resources = null; private static Field packagesFiled = null; private static Field resourcePackagesFiled = null; private static Field publicSourceDirField = null; @@ -78,6 +81,10 @@ class TinkerResourcePatcher { private static long storedPatchedResModifiedTime = 0L; + private static Context packageContext = null; + + private static Context packageResContext = null; + @SuppressWarnings("unchecked") public static void isResourceCanPatch(Context context) throws Throwable { // - Replace mResDir to point to the external resource file instead of the .apk. This is @@ -97,9 +104,18 @@ public static void isResourceCanPatch(Context context) throws Throwable { } resDir = findField(loadedApkClass, "mResDir"); + try { + resources = findField(loadedApkClass, "mResources"); + } catch (Throwable thr) { + ShareTinkerLog.printErrStackTrace(TAG, thr, "Fail to get LoadedApk.mResources field."); + resources = null; + } packagesFiled = findField(activityThread, "mPackages"); - if (Build.VERSION.SDK_INT < 27) { + try { resourcePackagesFiled = findField(activityThread, "mResourcePackages"); + } catch (Throwable thr) { + ShareTinkerLog.printErrStackTrace(TAG, thr, "Fail to get mResourcePackages field."); + resourcePackagesFiled = null; } // Create a new AssetManager instance and point it to the resources @@ -139,6 +155,13 @@ public static void isResourceCanPatch(Context context) throws Throwable { // N moved the resources to mResourceReferences final Field mResourceReferences = findField(resourcesManagerClass, "mResourceReferences"); references = (Collection>) mResourceReferences.get(resourcesManager); + + try { + final Field mResourceImplsField = findField(resourcesManagerClass, "mResourceImpls"); + resourceImpls = (Map>) mResourceImplsField.get(resourcesManager); + } catch (Throwable ignored) { + resourceImpls = null; + } } } else { final Field fMActiveResources = findField(activityThread, "mActiveResources"); @@ -186,13 +209,15 @@ public static void monkeyPatchExistingResources(Context context, String external final ApplicationInfo appInfo = context.getApplicationInfo(); - final Field[] packagesFields; - if (Build.VERSION.SDK_INT < 27) { - packagesFields = new Field[]{packagesFiled, resourcePackagesFiled}; - } else { - packagesFields = new Field[]{packagesFiled}; - } + // Prevent cached LoadedApk being recycled. + packageContext = context.createPackageContext(context.getPackageName(), Context.CONTEXT_INCLUDE_CODE); + packageResContext = context.createPackageContext(context.getPackageName(), 0); + + final Field[] packagesFields = new Field[]{packagesFiled, resourcePackagesFiled}; for (Field field : packagesFields) { + if (field == null) { + continue; + } final Object value = field.get(currentActivityThread); for (Map.Entry> entry @@ -204,6 +229,9 @@ public static void monkeyPatchExistingResources(Context context, String external final String resDirPath = (String) resDir.get(loadedApk); if (appInfo.sourceDir.equals(resDirPath)) { resDir.set(loadedApk, externalResourceFile); + if (resources != null) { + resources.set(loadedApk, null); + } } } } @@ -264,6 +292,20 @@ public static void monkeyPatchExistingResources(Context context, String external resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics()); } + try { + if (resourceImpls != null) { + for (WeakReference wr : resourceImpls.values()) { + final Object resourceImpl = wr.get(); + if (resourceImpl != null) { + final Field implAssets = findField(resourceImpl, "mAssets"); + implAssets.set(resourceImpl, newAssetManager); + } + } + } + } catch (Throwable ignored) { + // Ignored. + } + // Handle issues caused by WebView on Android N. // Issue: On Android N, if an activity contains a webview, when screen rotates // our resource patch may lost effects.