From 664994fe03aba58105cd74576be7d9c756bc0d28 Mon Sep 17 00:00:00 2001 From: galenlin Date: Fri, 6 Jul 2018 08:19:37 +0800 Subject: [PATCH] Support Android 9.0, API 28 --- .../net/wequick/small/ApkBundleLauncher.java | 26 +++++++- .../small/util/ReflectAccelerator.java | 62 ++++++++++++++++--- 2 files changed, 78 insertions(+), 10 deletions(-) diff --git a/Android/DevSample/small/src/main/java/net/wequick/small/ApkBundleLauncher.java b/Android/DevSample/small/src/main/java/net/wequick/small/ApkBundleLauncher.java index 12a58a47..040dc014 100644 --- a/Android/DevSample/small/src/main/java/net/wequick/small/ApkBundleLauncher.java +++ b/Android/DevSample/small/src/main/java/net/wequick/small/ApkBundleLauncher.java @@ -163,6 +163,11 @@ public boolean handleMessage(Message msg) { } private void redirectActivityForP(Message msg) { + if (Build.VERSION.SDK_INT >= 28) { + // Following APIs cannot be called again since android 9.0. + return; + } + Object/*android.app.servertransaction.ClientTransaction*/ t = msg.obj; List callbacks = ReflectAccelerator.getLaunchActivityItems(t); if (callbacks == null) return; @@ -189,7 +194,7 @@ public void replace(ActivityInfo targetInfo) { }); } - private void tryReplaceActivityInfo(Intent intent, ActivityInfoReplacer replacer) { + static void tryReplaceActivityInfo(Intent intent, ActivityInfoReplacer replacer) { if (intent == null) return; String targetClass = unwrapIntent(intent); @@ -338,6 +343,20 @@ public ActivityResult execStartActivity( who, contextThread, token, target, intent, requestCode); } + @Override + public Activity newActivity(ClassLoader cl, final String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { + final String[] targetClassName = {className}; + if (Build.VERSION.SDK_INT >= 28) { + ActivityThreadHandlerCallback.tryReplaceActivityInfo(intent, new ActivityThreadHandlerCallback.ActivityInfoReplacer() { + @Override + public void replace(ActivityInfo info) { + targetClassName[0] = info.targetActivity; // Redirect to the plugin activity + } + }); + } + return mBase.newActivity(cl, targetClassName[0], intent); + } + @Override /** Prepare resources for REAL */ public void callActivityOnCreate(Activity activity, android.os.Bundle icicle) { @@ -1046,6 +1065,11 @@ public T createObject(Bundle bundle, Context context, String type) { * @param ai */ private static void applyActivityInfo(Activity activity, ActivityInfo ai) { + // Apply theme (9.0 only) + if (Build.VERSION.SDK_INT >= 28) { + ReflectAccelerator.resetResourcesAndTheme(activity, ai.getThemeResource()); + } + // Apply window attributes Window window = activity.getWindow(); window.setSoftInputMode(ai.softInputMode); diff --git a/Android/DevSample/small/src/main/java/net/wequick/small/util/ReflectAccelerator.java b/Android/DevSample/small/src/main/java/net/wequick/small/util/ReflectAccelerator.java index b19b1f6d..a461dc90 100644 --- a/Android/DevSample/small/src/main/java/net/wequick/small/util/ReflectAccelerator.java +++ b/Android/DevSample/small/src/main/java/net/wequick/small/util/ReflectAccelerator.java @@ -30,6 +30,7 @@ import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; +import android.view.ContextThemeWrapper; import java.io.File; import java.io.IOException; @@ -187,7 +188,7 @@ private static Object makeDexElement(File pkg, boolean isDirectory, DexFile dexF } if (sDexElementConstructor == null) { if (Build.VERSION.SDK_INT >= 26) { - sDexElementConstructor = sDexElementClass.getConstructors()[1]; // (DexFile, File) + sDexElementConstructor = sDexElementClass.getConstructor(new Class[]{DexFile.class, File.class}); } else { sDexElementConstructor = sDexElementClass.getConstructors()[0]; } @@ -465,12 +466,23 @@ public static int addAssetPath(AssetManager assets, String path) { } public static int[] addAssetPaths(AssetManager assets, String[] paths) { - if (sAssetManager_addAssetPaths_method == null) { - sAssetManager_addAssetPaths_method = getMethod(AssetManager.class, - "addAssetPaths", new Class[]{String[].class}); + if (Build.VERSION.SDK_INT < 28) { + if (sAssetManager_addAssetPaths_method == null) { + sAssetManager_addAssetPaths_method = getMethod(AssetManager.class, + "addAssetPaths", new Class[]{String[].class}); + } + if (sAssetManager_addAssetPaths_method == null) return null; + return invoke(sAssetManager_addAssetPaths_method, assets, new Object[]{paths}); + } else { + // `AssetManager#addAssetPaths` becomes unavailable since android 9.0, + // use recursively `addAssetPath` instead. + int N = paths.length; + int[] ids = new int[N]; + for (int i = 0; i < N; i++) { + ids[i] = addAssetPath(assets, paths[i]); + } + return ids; } - if (sAssetManager_addAssetPaths_method == null) return null; - return invoke(sAssetManager_addAssetPaths_method, assets, new Object[]{paths}); } public static void mergeResources(Application app, Object activityThread, String[] assetPaths) { @@ -484,9 +496,13 @@ public static void mergeResources(Application app, Object activityThread, String addAssetPaths(newAssetManager, assetPaths); try { - Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks", new Class[0]); - mEnsureStringBlocks.setAccessible(true); - mEnsureStringBlocks.invoke(newAssetManager, new Object[0]); + if (Build.VERSION.SDK_INT < 28) { + Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks", new Class[0]); + mEnsureStringBlocks.setAccessible(true); + mEnsureStringBlocks.invoke(newAssetManager, new Object[0]); + } else { + // `AssetManager#ensureStringBlocks` becomes unavailable since android 9.0 + } Collection> references; @@ -737,6 +753,34 @@ public static Intent getIntentOfLaunchActivityItem(Object item) { return getValue(f, item); } + public static void resetResourcesAndTheme(Activity activity, int themeId) { + AssetManager newAssetManager = activity.getApplication().getAssets(); + Resources resources = activity.getResources(); + + // Set the activity resources assets to the application one + try { + Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl"); + mResourcesImpl.setAccessible(true); + Object resourceImpl = mResourcesImpl.get(resources); + Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets"); + implAssets.setAccessible(true); + implAssets.set(resourceImpl, newAssetManager); + } catch (Throwable e) { + android.util.Log.e("Small", "Failed to update resources for activity " + activity, e); + } + + // Reset the theme + try { + Field mt = ContextThemeWrapper.class.getDeclaredField("mTheme"); + mt.setAccessible(true); + mt.set(activity, null); + } catch (Throwable e) { + android.util.Log.e("Small", "Failed to update existing theme for activity " + activity, e); + } + + activity.setTheme(themeId); + } + //______________________________________________________________________________________________ // Private