1,前置条件
path:apk路径
packageName:apk包名
2,如何获取?
可以通过绑定服务时传参;
或者进程间主动通信获取;
3,核心注入类
(1)核心思想是把ContextImpl中mResources替换成咱们自定义的拦截类(ResourcesProxy),就可以实现拦截操作了。而ContextImpl位于ContextWrapper中mBase成员,因为Application是ContextWrapper子类,因此可以通过反射拿到ContextImpl,进而实现依赖注入,完成代理操作。
(2)其次,需要把其它apk路径传给AssetManager,即AssetManager#addAssetPath(String)方法,将路径传入。咱们可以看下源码。
跟进到#addAssetPathInternal,传入overlay参数为false,继续跟进#ApkAssets.loadFromPath()
private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
Objects.requireNonNull(path, "path");
synchronized (this) {
ensureOpenLocked();
final int count = mApkAssets.length;
// See if we already have it loaded.
for (int i = 0; i < count; i++) {
if (mApkAssets[i].getAssetPath().equals(path)) {
return i + 1;
}
}
final ApkAssets assets;
try {
if (overlay) {
// TODO(b/70343104): This hardcoded path will be removed once
// addAssetPathInternal is deleted.
final String idmapPath = "/data/resource-cache/"
+ path.substring(1).replace('/', '@')
+ "@idmap";
assets = ApkAssets.loadOverlayFromPath(idmapPath, 0 /* flags */);
} else {
assets = ApkAssets.loadFromPath(path,
appAsLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
}
} catch (IOException e) {
return 0;
}
mApkAssets = Arrays.copyOf(mApkAssets, count + 1);
mApkAssets[count] = assets;
nativeSetApkAssets(mObject, mApkAssets, true);
invalidateCachesLocked(-1);
return count + 1;
}
}
(3)跟进#ApkAssets.loadFromPath(),创建ApkAssets类,进而在native层加载path路径中apk资源,
private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
Objects.requireNonNull(path, "path");
mFlags = flags;
mNativePtr = nativeLoad(format, path, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
mAssets = assets;
}
4,资源工具类
package com.zjw.demo.util;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
public final class ResourcesHelper {
private static final String TAG = "ResourcesUtils";
private static Resources sOtherResources = null;
private static AssetManager sOtherAssetManager = null;
private static final int INVALID_ID = -1;
private static String sOtherPackageName;
public static void inject(Context context, String path, String otherPackageName) {
try {
//创建assetManager
sOtherAssetManager = AssetManager.class.newInstance();R
Method addAssetPath = sOtherAssetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(sOtherAssetManager, path);
//创建resources
Context app = context.getApplicationContext();
sOtherResources = new Resources(sOtherAssetManager, app.getResources().getDisplayMetrics(), app.getResources().getConfiguration());
//获取包名
sOtherPackageName = otherPackageName;
//注入代理
injectResourcesProxy(app);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
private static void injectResourcesProxy(Context app) {
try {
Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
Class<?> contextWrapperClass = Class.forName("android.content.ContextWrapper");
if (contextImplClass.isInstance(app)) {
Field mResources = app.getClass().getDeclaredField("mResources");
mResources.setAccessible(true);
Resources proxyResources = new ProxyResources(app);
mResources.set(app, proxyResources);
Log.d(TAG, "inject resources success");
} else if (contextWrapperClass.isInstance(app)) {
//这个是ContextImpl
Field contextImpl = contextWrapperClass.getDeclaredField("mBase");
contextImpl.setAccessible(true);
Object contextImplObj = contextImpl.get(app);
if (contextImplObj == null) {
Log.e(TAG, "check android version :contextImplObj == null");
return;
}
Field mResources = contextImplObj.getClass().getDeclaredField("mResources");
mResources.setAccessible(true);
Resources proxyResources = new ProxyResources(app);
mResources.set(contextImplObj, proxyResources);
Log.d(TAG, "inject resources success");
}
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
private static int getResourcesId(String resName, String defType) {
if (sOtherResources == null) {
return INVALID_ID;
}
return sOtherResources.getIdentifier(resName, defType, sOtherPackageName);
}
public static AssetManager getOtherAssetManager() {
return sOtherAssetManager;
}
public static Drawable getDrawable(String resName, String defType) {
int resourcesId = getResourcesId(resName, defType);
if (resourcesId == INVALID_ID) {
return null;
}
return sOtherResources.getDrawable(resourcesId);
}
public static int getColor(String resName, String defType) {
int resourcesId = getResourcesId(resName, defType);
if (resourcesId == INVALID_ID) {
return 0;
}
return sOtherResources.getColor(resourcesId);
}
/**
* 资源文件静态代理对象
*/
private static class ProxyResources extends Resources {
private final Set<Integer> ids = new HashSet<>();
public ProxyResources(Context context) {
super(context.getAssets(), context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
}
@Override
public Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException {
if (sOtherResources != null && ids.contains(id)) {
try {
Drawable drawable = sOtherResources.getDrawable(id, theme);
if (drawable != null) {
return drawable;
}
} catch (NotFoundException e) {
Log.d(TAG, e.getMessage());
}
}
return super.getDrawable(id, theme);
}
@Override
public int getColor(int id, @Nullable Theme theme) throws NotFoundException {
if (sOtherResources != null && ids.contains(id)) {
try {
int color = sOtherResources.getColor(id, theme);
if (color != 0) {
return color;
}
} catch (NotFoundException e) {
Log.d(TAG, e.getMessage());
}
}
return super.getColor(id, theme);
}
@Override
public int getColor(int id) throws NotFoundException {
if (sOtherResources != null && ids.contains(id)) {
try {
int color = sOtherResources.getColor(id);
if (color != 0) {
return color;
}
} catch (NotFoundException e) {
Log.d(TAG, e.getMessage());
}
}
return super.getColor(id);
}
@Override
public Drawable getDrawable(int id) throws NotFoundException {
if (sOtherResources != null && ids.contains(id)) {
try {
Drawable drawable = sOtherResources.getDrawable(id);
if (drawable != null) {
return drawable;
}
} catch (NotFoundException e) {
Log.d(TAG, e.getMessage());
}
}
return super.getDrawable(id);
}
@Override
public int getIdentifier(String name, String defType, String defPackage) {
if (sOtherResources != null && ids.contains(id)) {
int resId = sOtherResources.getIdentifier(name, defType, sOtherPackageName);
if (resId != 0) {
ids.add(resId);
return resId;
}
}
return super.getIdentifier(name, defType, defPackage);
}
@Override
public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
if (sOtherResources != null && ids.contains(id)) {
return sOtherResources.openRawResourceFd(id);
}
return super.openRawResourceFd(id);
}
@NonNull
@Override
public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
if (sOtherResources != null && ids.contains(id)) {
return sOtherResources.openRawResource(id, value);
}
return super.openRawResource(id, value);
}
}
}