Skip to content

Commit

Permalink
1. hold the issue Tencent#302, I will use ResourcesKey to fix it later
Browse files Browse the repository at this point in the history
2.  fix MiuiTypedArrary problem
  • Loading branch information
shwenzhang committed Jan 12, 2017
1 parent d012753 commit 8cd5282
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 550 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

/**
* Created by zhangshaowen on 17/1/5.
* Thanks for Android Fragmentation
*/

public class TinkerLoadLibrary {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@

package com.tencent.tinker.loader;

import android.app.Instrumentation;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Build;
import android.util.ArrayMap;
import android.util.Log;

import com.tencent.tinker.loader.shareutil.ActivityResFixInstrumentation;
import com.tencent.tinker.loader.shareutil.ShareConstants;
import com.tencent.tinker.loader.shareutil.ShareReflectUtil;

Expand All @@ -39,28 +36,35 @@
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.KITKAT;

/**
* Created by zhangshaowen on 16/9/21.
* Thanks for Android Fragmentation
*/
class TinkerResourcePatcher {
private static final String TAG = "Tinker.ResourcePatcher";
private static final String TEST_ASSETS_VALUE = "only_use_to_test_tinker_resource.txt";
private static final String MIUI_RESOURCE_CLASSNAME = "android.content.res.MiuiResources";

// original value
private static Collection<WeakReference<Resources>> references;
private static AssetManager newAssetManager = null;
private static Method addAssetPathMethod = null;
private static Method ensureStringBlocksMethod = null;
private static Field assetsFiled = null;
private static Field resourcesImplFiled = null;
private static Field typedArrayPoolField = null;
private static Field poolField = null;
private static Field resDir = null;
private static Field packagesFiled = null;
private static Field resourcePackagesFiled = null;

private static Field instrumentationField = null;
// original object
private static Collection<WeakReference<Resources>> references = null;
private static Object currentActivityThread = null;
private static AssetManager newAssetManager = null;
private static ArrayMap<?, WeakReference<Resources>> activeResources19 = null;
// private static ArrayMap<?, WeakReference<?>> resourceImpls = null;
private static HashMap<?, WeakReference<Resources>> activeResources7 = null;
// method
private static Method addAssetPathMethod = null;
private static Method ensureStringBlocksMethod = null;

// field
private static Field assetsFiled = null;
private static Field resourcesImplFiled = null;
private static Field resDir = null;
private static Field packagesFiled = null;
private static Field resourcePackagesFiled = null;
// private static Field publicSourceDirField = null;

private static boolean isMiuiSystem = false;
private static boolean isMiuiSystem = false;

public static void isResourceCanPatch(Context context) throws Throwable {
// - Replace mResDir to point to the external resource file instead of the .apk. This is
Expand All @@ -69,55 +73,26 @@ public static void isResourceCanPatch(Context context) throws Throwable {

// Find the ActivityThread instance for the current thread
Class<?> activityThread = Class.forName("android.app.ActivityThread");
currentActivityThread = ShareReflectUtil.getActivityThread(context, activityThread);

// API version 8 has PackageInfo, 10 has LoadedApk. 9, I don't know.
Class<?> loadedApkClass;
try {
loadedApkClass = Class.forName("android.app.LoadedApk");
} catch (ClassNotFoundException e) {
loadedApkClass = Class.forName("android.app.ActivityThread$PackageInfo");
}
Field mApplication = loadedApkClass.getDeclaredField("mApplication");
mApplication.setAccessible(true);


resDir = loadedApkClass.getDeclaredField("mResDir");
resDir.setAccessible(true);
packagesFiled = activityThread.getDeclaredField("mPackages");
packagesFiled.setAccessible(true);

resourcePackagesFiled = activityThread.getDeclaredField("mResourcePackages");
resourcePackagesFiled.setAccessible(true);
/*
(Note: the resource directory is *also* inserted into the loadedApk in
monkeyPatchApplication)
The code seems to perform this:
File externalResourceFile = <path to resources.ap_ or extracted directory>
AssetManager newAssetManager = new AssetManager();
newAssetManager.addAssetPath(externalResourceFile)
// Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm
// in L, so we do it unconditionally.
newAssetManager.ensureStringBlocks();
// Find the singleton instance of ResourcesManager
ResourcesManager resourcesManager = ResourcesManager.getInstance();
// Iterate over all known Resources objects
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
for (WeakReference<Resources> wr : resourcesManager.mActiveResources.values()) {
Resources resources = wr.get();
// Set the AssetManager of the Resources instance to our brand new one
resources.mAssets = newAssetManager;
resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
}
}

// Also, for each context, call getTheme() to get the current theme; null out its
// mTheme field, then invoke initializeTheme() to force it to be recreated (with the
// new asset manager!)
*/
// Create a new AssetManager instance and point it to the resources installed under
// /sdcard
// Create a new AssetManager instance and point it to the resources
AssetManager assets = context.getAssets();
// Baidu os
if (assets.getClass().getName().equals("android.content.res.BaiduAssetManager")) {
Expand Down Expand Up @@ -146,24 +121,23 @@ public static void isResourceCanPatch(Context context) throws Throwable {
try {
Field fMActiveResources = resourcesManagerClass.getDeclaredField("mActiveResources");
fMActiveResources.setAccessible(true);
ArrayMap<?, WeakReference<Resources>> arrayMap =
activeResources19 =
(ArrayMap<?, WeakReference<Resources>>) fMActiveResources.get(resourcesManager);
references = arrayMap.values();
references = activeResources19.values();
} catch (NoSuchFieldException ignore) {
// N moved the resources to mResourceReferences
Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences");
mResourceReferences.setAccessible(true);
//noinspection unchecked
// resourceImpls = (ArrayMap<?, WeakReference<?>>) mResourceReferences.get("mResourceImpls");

references = (Collection<WeakReference<Resources>>) mResourceReferences.get(resourcesManager);
}
} else {
Field fMActiveResources = activityThread.getDeclaredField("mActiveResources");
fMActiveResources.setAccessible(true);
Object thread = ShareReflectUtil.getActivityThread(context, activityThread);
@SuppressWarnings("unchecked")
HashMap<?, WeakReference<Resources>> map =
(HashMap<?, WeakReference<Resources>>) fMActiveResources.get(thread);
references = map.values();
activeResources7 =
(HashMap<?, WeakReference<Resources>>) fMActiveResources.get(currentActivityThread);
references = activeResources7.values();
}
// check resource
if (references == null) {
Expand All @@ -181,44 +155,22 @@ public static void isResourceCanPatch(Context context) throws Throwable {
final Resources resources = context.getResources();
isMiuiSystem = resources != null && MIUI_RESOURCE_CLASSNAME.equals(resources.getClass().getName());

if (isMiuiSystem) {
Log.w(TAG, "Miui system found, collect some additional fields.");
try {
typedArrayPoolField = Resources.class.getDeclaredField("mTypedArrayPool");
typedArrayPoolField.setAccessible(true);
final Class<?> simplePoolClazz = Class.forName("android.util.Pools$SimplePool");
poolField = simplePoolClazz.getDeclaredField("mPool");
poolField.setAccessible(true);
} catch (Throwable ignored) {
}
}

try {
typedArrayPoolField = Resources.class.getDeclaredField("mTypedArrayPool");
typedArrayPoolField.setAccessible(true);
} catch (Throwable ignored) {
}

try {
instrumentationField = ShareReflectUtil.findField(activityThread, "mInstrumentation");
} catch (NoSuchFieldException e) {
throw new IllegalStateException("cannot find 'mInstrumentation' field");
}

// try {
// publicSourceDirField = ShareReflectUtil.findField(ApplicationInfo.class, "publicSourceDir");
// } catch (NoSuchFieldException e) {
// throw new IllegalStateException("cannot find 'mInstrumentation' field");
// }
}

/**
* @param context
* @param externalResourceFile
* @throws Throwable
*/
public static void monkeyPatchExistingResources(Context context, String externalResourceFile) throws Throwable {
if (externalResourceFile == null) {
return;
}
// Find the ActivityThread instance for the current thread
Class<?> activityThread = Class.forName("android.app.ActivityThread");
Object currentActivityThread = ShareReflectUtil.getActivityThread(context, activityThread);

for (Field field : new Field[]{packagesFiled, resourcePackagesFiled}) {
Object value = field.get(currentActivityThread);
Expand All @@ -235,7 +187,6 @@ public static void monkeyPatchExistingResources(Context context, String external
}
}
// Create a new AssetManager instance and point it to the resources installed under
// /sdcard
if (((Integer) addAssetPathMethod.invoke(newAssetManager, externalResourceFile)) == 0) {
throw new IllegalStateException("Could not create new AssetManager");
}
Expand All @@ -259,50 +210,15 @@ public static void monkeyPatchExistingResources(Context context, String external
implAssets.setAccessible(true);
implAssets.set(resourceImpl, newAssetManager);
}
// Clear typedArray cache.
try {
final Object origTypedArrayPool = typedArrayPoolField.get(resources);
final Constructor<?> ctor = origTypedArrayPool.getClass().getConstructor(int.class);
ctor.setAccessible(true);
// 5 is the harcoded value in AOSP
final Object newTypedArrayPool = ctor.newInstance(5);
typedArrayPoolField.set(resources, newTypedArrayPool);
} catch (Throwable ignored) {
}

if (isMiuiSystem) {
Log.w(TAG, "Miui system found, do additional tricks.");

// Clear typedArray cache.
try {
final Object origTypedArrayPool = typedArrayPoolField.get(resources);
final Constructor<?> ctor = origTypedArrayPool.getClass().getConstructor(int.class);
ctor.setAccessible(true);
final int poolSize = ((Object[]) poolField.get(origTypedArrayPool)).length;
final Object newTypedArrayPool = ctor.newInstance(poolSize);
typedArrayPoolField.set(resources, newTypedArrayPool);
} catch (Throwable ignored) {
}
}
fixMiuiTypedArrayIssue(resources);

resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
}
}

try {
final Instrumentation origInstrumentation = (Instrumentation) instrumentationField.get(currentActivityThread);
final Instrumentation activityResFixInstrumentation = new ActivityResFixInstrumentation(context, origInstrumentation);
instrumentationField.set(currentActivityThread, activityResFixInstrumentation);
} catch (NoSuchFieldException e) {
// Version below api 17 has no mResources field in ContextThemeWrapper,
// so just ignore this exception. Otherwise we should rethrow it.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
throw e;
}
}

// Handle issues caused by WebView on Android N.
// Issue: On Andorid N, if an activity contains a webview, when screen rotates
// Issue: On Android N, if an activity contains a webview, when screen rotates
// our resource patch may lost effects.
// publicSourceDirField.set(context.getApplicationInfo(), externalResourceFile);

Expand All @@ -311,6 +227,33 @@ public static void monkeyPatchExistingResources(Context context, String external
}
}

/**
* Why must I do these?
* Resource has mTypedArrayPool field, which just like Message Poll to reduce gc
* MiuiResource change TypedArray to MiuiTypedArray, but it get string block from offset instead of assetManager
*/
private static void fixMiuiTypedArrayIssue(Resources resources) {
if (!isMiuiSystem) {
return;
}
Log.w(TAG, "Miui system found, try to clear MiuiTypedArray cache!");
// Clear typedArray cache.
try {
Field typedArrayPoolField = ShareReflectUtil.findField(Resources.class, "mTypedArrayPool");

final Object origTypedArrayPool = typedArrayPoolField.get(resources);

Field poolField = ShareReflectUtil.findField(origTypedArrayPool, "mPool");

final Constructor<?> typedArrayConstructor = origTypedArrayPool.getClass().getConstructor(int.class);
typedArrayConstructor.setAccessible(true);
final int poolSize = ((Object[]) poolField.get(origTypedArrayPool)).length;
final Object newTypedArrayPool = typedArrayConstructor.newInstance(poolSize);
typedArrayPoolField.set(resources, newTypedArrayPool);
} catch (Throwable ignored) {
}
}

private static boolean checkResUpdate(Context context) {
try {
Log.e(TAG, "checkResUpdate success, found test resource assets file " + TEST_ASSETS_VALUE);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Tencent is pleased to support the open source community by making Tinker available.
*
* Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.tencent.tinker.loader;

/**
* Created by zhangshaowen on 17/1/12.
* Thanks for Android Fragmentation
* hold the issue https://github.com/Tencent/tinker/issues/302
*/

public class TinkerResourcesKey {

private static final class V24 {


}

private static final class V19 {

}

private static final class V17 {

}

private static final class V7 {

}
}
Loading

0 comments on commit 8cd5282

Please sign in to comment.