4、ContextImpl
在 ActivityThread 中调用了
ContextImpl appContext = ContextImpl.createActivityContext( this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
方法 , 创建了 ContextImpl ;
下面分析 ContextImpl 的 createActivityContext 方法 ;
class ContextImpl extends Context { static ContextImpl createActivityContext(ActivityThread mainThread, LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration) { if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); String[] splitDirs = packageInfo.getSplitResDirs(); ClassLoader classLoader = packageInfo.getClassLoader(); if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies"); try { classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName); splitDirs = packageInfo.getSplitPaths(activityInfo.splitName); } catch (NameNotFoundException e) { // Nothing above us can handle a NameNotFoundException, better crash. throw new RuntimeException(e); } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } } ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName, activityToken, null, 0, classLoader); // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY. displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY; final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY) ? packageInfo.getCompatibilityInfo() : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; final ResourcesManager resourcesManager = ResourcesManager.getInstance(); // Create the base resources for which all configuration contexts for this Activity // will be rebased upon. context.setResources(resourcesManager.createBaseActivityResources(activityToken, packageInfo.getResDir(), splitDirs, packageInfo.getOverlayDirs(), packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfiguration, compatInfo, classLoader)); context.mDisplay = resourcesManager.getAdjustedDisplay(displayId, context.getResources()); return context; } }
源码路径 : /frameworks/base/core/java/android/app/ContextImpl.java
在上述方法中 , 就有创建 Resources 资源的方法 :
context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
classLoader));
1
2
3
4
5
6
7
8
9
二、Hook 点选择
修改应用 Resources 的 Hook 点有很多 , 通过改变 Activity 的 Resources , 甚至修改 ActivityThread 中创建 Resources 的流程 都可以实现 ;
在插件包中 , 使用了 AppCompatActivity , 可以直接替换 AppCompatActivity 中的 private Resources mResources 成员 ;
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback, TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider { private static final String DELEGATE_TAG = "androidx:appcompat"; private AppCompatDelegate mDelegate; private Resources mResources; }
只要可以拿到 AppCompatActivity 实例 , 就可以通过反射 , 替换掉 private Resources mResources 成员 ;
在 Instrumentation 中 , 调用了 newActivity 创建 Activity 实例 ;
public Activity newActivity(Class<?> clazz, Context context, IBinder token, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, Object lastNonConfigurationInstance) throws InstantiationException, IllegalAccessException { Activity activity = (Activity)clazz.newInstance(); ActivityThread aThread = null; // Activity.attach expects a non-null Application Object. if (application == null) { application = new Application(); } activity.attach(context, aThread, this, token, 0 /* ident */, application, intent, info, title, parent, id, (Activity.NonConfigurationInstances)lastNonConfigurationInstance, new Configuration(), null /* referrer */, null /* voiceInteractor */, null /* window */, null /* activityConfigCallback */); return activity; }
源码路径 : /frameworks/base/core/java/android/app/Instrumentation.java
在 ActivityThread 中 , 有 Instrumentation mInstrumentation 成员变量 , ActivityThread 本身就是单例 , 通过获取其静态成员 private static volatile ActivityThread sCurrentActivityThread , 就可以获取到 ActivityThread ;
/** Reference to singleton {@link ActivityThread} */ private static volatile ActivityThread sCurrentActivityThread; Instrumentation mInstrumentation;
三、资源冲突解决方案
资源的 ID 在 AAPT 编译资源阶段就确定了 ;
固定类型的资源 , 编号是从一定的编号段开始的 , 如 layout 布局资源 , 第一个布局资源总是 2131361820 ;
不同类型的资源 , 布局 , 字符串 , 数值 , 主题 , 图片 , drawable 等 , 都有对应的资源编号范围 , 同时也要兼容系统的资源编号范围 ;
如果宿主应用启动 , 加载第一个布局资源 , 那么编号是 2131361820 ;
如果插件应用启动 , 加载第一个布局资源 , 那么编号也是 2131361820 ;
这样就出现了资源冲突 ;
使用不同的 AssetManager 加载不同的资源 , 可以解决资源冲突问题 ;