解决插件资源生效问题,首先需要看看系统创建资源
1.资源创建流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 private ContextImpl (ContextImpl container, ActivityThread mainThread, LoadedApk packageInfo, ...) { Resources resources = packageInfo.getResources(mainThread); mResources = resources; } public Resources getResources () { return mResources; } ``` 继续跟进 ```java public final class LoadedApk { private final String mResDir; public LoadedApk (ActivityThread activityThread, ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { final int myUid = Process.myUid(); aInfo = adjustNativeLibraryPaths(aInfo); mActivityThread = activityThread; mApplicationInfo = aInfo; mPackageName = aInfo.packageName; mAppDir = aInfo.sourceDir; mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir; } public Resources getResources (ActivityThread mainThread) { if (mResources == null ) { mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null , this ); } return mResources; } } ``` ```java public final class ActivityThread { Resources getTopLevelResources (String resDir, CompatibilityInfo compInfo) { AssetManager assets = new AssetManager(); if (assets.addAssetPath(resDir) == 0 ) { return null ; } r = new Resources(assets, metrics, getConfiguration(), compInfo); } } ``` 2 . 上面分析完Resources的创建流程,现在继续看如何使加载插件资源```java private static Resources createResources (Context context, File apk) { if (Constants.COMBINE_RESOURCES) { Resources resources = new ResourcesManager().createResources(context, apk.getAbsolutePath()); ResourcesManager.hookResources(context, resources); return resources; } else { Resources hostResources = context.getResources(); AssetManager assetManager = createAssetManager(context, apk); return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); } }
(1)如果是独立的资源, 只需要重新创建新的资源,不用管宿主资源 (2)如果是插件资源需要使用宿主资源,就需要添加宿主资源和所有的插件资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class ResourcesManager { public static synchronized Resources createResources (Context hostContext, String apk) { Resources hostResources = hostContext.getResources(); Resources newResources = null ; AssetManager assetManager; try { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { assetManager = AssetManager.class .newInstance () ; ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", hostContext.getApplicationInfo().sourceDir); } else { assetManager = hostResources.getAssets(); } ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", apk); List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins(); for (LoadedPlugin plugin : pluginList) { ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", plugin.getLocation()); } if (isMiUi(hostResources)) { newResources = MiUiResourcesCompat.createResources(hostResources, assetManager); } else if (isVivo(hostResources)) { newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager); } else if (isNubia(hostResources)) { newResources = NubiaResourcesCompat.createResources(hostResources, assetManager); } else if (isNotRawResources(hostResources)) { newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager); } else { newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); } } catch (Exception e) { e.printStackTrace(); } return newResources; }
(1)这里针对21一下版本和21以上版本做了区分,问题在于21以下版本在addPath之前,AssetManager中的资源路径已经初始化,再次获取的时候将不会改变。 (2)这里还有个疑问,针对api21以上,AssetManager使用的同一个引用,为什么需要每次重新添加之前的所有插件资源?像官方提过问题,一直没回复。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const ResTable* AssetManager::getResTable(bool required) const { ResTable* rt = mResources; if (rt) { return rt; } AutoMutex _l (mLock) ; if (mResources != NULL) { return mResources; } }
mResources指向的是一个ResTable对象,如果它的值不等于NULL,那么就说明当前应用程序已经解析过它使用的资源包里面的resources.arsc文件,因此,这时候AssetManager类的成员函数getResources就可以直接将该ResTable对象返回给调用者。如果还没有初始化 mResources 则按照一定步骤遍历当前应用所使用的每个资源包进而生成 mResources 。 所以在 Android L 之前是需要想办法构造一个新的AssetManager里的 mResources 才行,这里有两种方案,VirtualAPK 用的是类似 InstantRun 的那种方案,构造一个新的AssetManager,将宿主和加载过的插件的所有 apk 全都添加一遍,然后再调用hookResources方法将新的 Resources 替换回原来的,这样会引起两个问题,一个是每次加载新的插件都会重新构造一个 AssetManger 和 Resources,然后重新添加所有资源,这样涉及到很多机型的兼容(因为部分厂商自己修改了 Resources 的类名),一个是需要有一个替换原来Resources的过程,这样就需要涉及到很多地方,从hookResources的实现里看,替换了四处地方,在尽量少的 hook 原则下这样的情况还是尽量避免的。 另外还有一种方案,可以看下VirtualAPK 资源篇
回到上面createResources, 解决完AssetManager的问题然后创建Resources, 然后针对国产手机做兼容性处理
3.使资源生效,通俗点就是替换Context中的Resource
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override public Activity newActivity (ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { try { cl.loadClass(className); } catch (ClassNotFoundException e) { LoadedPlugin plugin = this .mPluginManager.getLoadedPlugin(intent); String targetClassName = PluginUtil.getTargetActivity(intent); Log.i(TAG, String.format("newActivity[%s : %s]" , className, targetClassName)); if (targetClassName != null ) { Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent); activity.setIntent(intent); try { ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());//注意此处的代码 } catch (Exception ignored) { } return activity; } } return mBase.newActivity(cl, className, intent); }
这部分的处理在很多插件框架中都有存在,也就是之前对于21版本以下。这里对资源做了一次赋值的缘由需要探讨一下。 跟踪到新建Activity对象的地方,也就是出现问题的地方,这里通过4.4的代码解释一下,后面版本的代码虽然变化大但也会出现问题,追溯到底的原因是一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 private Activity performLaunchActivity (ActivityClientRecord r, Intent customIntent) { Activity activity = null ; try { java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); if (r.state != null ) { r.state.setClassLoader(cl); } } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to instantiate activity " + component + ": " + e.toString(), e); } } try { Application app = r.packageInfo.makeApplication(false , mInstrumentation); if (localLOGV) Slog.v(TAG, "Performing launch of " + r); if (localLOGV) Slog.v( TAG, r + ": app=" + app + ", appName=" + app.getPackageName() + ", pkg=" + r.packageInfo.getPackageName() + ", comp=" + r.intent.getComponent().toShortString() + ", dir=" + r.packageInfo.getAppDir()); if (activity != null ) { Context appContext = createBaseContextForActivity(r, activity); CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + r.activityInfo.name + " with config " + config); activity.attach(appContext, this , getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config); if (customIntent != null ) { activity.mIntent = customIntent; } r.lastNonConfigurationInstances = null ; activity.mStartedActivity = false ; int theme = r.activityInfo.getThemeResource(); if (theme != 0 ) { activity.setTheme(theme); } activity.mCalled = false ; mInstrumentation.callActivityOnCreate(activity, r.state); if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onCreate()" ); } r.activity = activity; r.stopped = true ; if (!r.activity.mFinished) { activity.performStart(); r.stopped = false ; } if (!r.activity.mFinished) { if (r.state != null ) { mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); } } if (!r.activity.mFinished) { activity.mCalled = false ; mInstrumentation.callActivityOnPostCreate(activity, r.state); if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onPostCreate()" ); } } } r.paused = true ; mActivities.put(r.token, r); } catch (SuperNotCalledException e) { throw e; } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to start activity " + component + ": " + e.toString(), e); } } return activity; } private Context createBaseContextForActivity (ActivityClientRecord r, final Activity activity) { ContextImpl appContext = new ContextImpl(); appContext.init(r.packageInfo, r.token, this ); appContext.setOuterContext(activity); Context baseContext = appContext; return baseContext; } ``` 系统在创建完Activity对象后,紧接着创建Activity所附着的Context,从最上面的创建Resources 一部分内容可知,在createBaseContextForActivity 方法中创建出来的ContextImpl appContext 使用的是宿主的Resources,如果不进行处理紧接着Activity会走入 onCreate的生命周期中,此时插件加载资源的时候还是使用的宿主的资源,而不是我们特意为插件所创建出来的Resources对象,则会发生找不到资源的问题,这里用了一个很机智的方式解决这个问题。 @Override ```java public Resources getResources () { if (mResources != null ) { return mResources; } if (mOverrideConfiguration == null ) { mResources = super .getResources(); return mResources; } else { Context resc = createConfigurationContext(mOverrideConfiguration); mResources = resc.getResources(); return mResources; } }
其实在于Resource中是通过AssetManager获取资源的,对于Api21以下,每次添加插件资源的时候都会创建一个新的AssetManager,而且保存在一个新的Resource中。在API21以上,AssetManager使用的是同一个引用,在添加插件资源的时候,是往同一个AssetManager中添加,所以并不会有这个问题。
4.Service中的资源处理 LocalService.onStartCommand(Intent intent, int flags, int startId)中(为什么在这里后面分析四大组件的时候会讲)
1 2 3 4 5 6 7 8 Application app = plugin.getApplication(); IBinder token = appThread.asBinder(); Method attach = service.getClass().getMethod("attach" , Context.class , ActivityThread .class , String .class , IBinder .class , Application .class , Object .class ) ; IActivityManager am = mPluginManager.getActivityManager(); IActivityManager am = mPluginManager.getActivityManager(); attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);
这里将之前创建插件Context放置到Service中,下面继续看看插件Context的创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 class PluginContext extends ContextWrapper { private final LoadedPlugin mPlugin; public PluginContext (LoadedPlugin plugin) { super (plugin.getPluginManager().getHostContext()); this .mPlugin = plugin; } @Override public Context getApplicationContext () { return this .mPlugin.getApplication(); } @Override public ApplicationInfo getApplicationInfo () { return this .mPlugin.getApplicationInfo(); } private Context getHostContext () { return getBaseContext(); } @Override public ContentResolver getContentResolver () { return new PluginContentResolver(getHostContext()); } @Override public ClassLoader getClassLoader () { return this .mPlugin.getClassLoader(); } @Override public String getPackageName () { return this .mPlugin.getPackageName(); } @Override public String getPackageResourcePath () { return this .mPlugin.getPackageResourcePath(); } @Override public String getPackageCodePath () { return this .mPlugin.getCodePath(); } @Override public PackageManager getPackageManager () { return this .mPlugin.getPackageManager(); } @Override public Object getSystemService (String name) { if (name.equals(Context.CLIPBOARD_SERVICE)) { return getHostContext().getSystemService(name); } else if (name.equals(Context.NOTIFICATION_SERVICE)) { return getHostContext().getSystemService(name); } return super .getSystemService(name); } @Override public Resources getResources () { return this .mPlugin.getResources(); } @Override public AssetManager getAssets () { return this .mPlugin.getAssets(); } @Override public Resources.Theme getTheme () { return this .mPlugin.getTheme(); } @Override public void startActivity (Intent intent) { ComponentsHandler componentsHandler = mPlugin.getPluginManager().getComponentsHandler(); componentsHandler.transformIntentToExplicitAsNeeded(intent); super .startActivity(intent); } }
其实就是创建了一个代理Context, 获取的资源等其他都是从插件Resource中获取。
5.BroadcastReceiver 这一点比较奇怪,BroadcastReceiver中的资源并未替换,那在Api24以下是否存在问题,这个我已经向官方反馈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>(); for (PackageParser.Activity receiver : this .mPackage.receivers) { receivers.put(receiver.getComponentName(), receiver.info); try { BroadcastReceiver br = BroadcastReceiver.class .cast (getClassLoader ().loadClass (receiver .getComponentName ().getClassName ()).newInstance ()) ; for (PackageParser.ActivityIntentInfo aii : receiver.intents) { this .mHostContext.registerReceiver(br, aii); } } catch (Exception e) { e.printStackTrace(); } } Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>(); for (PackageParser.Activity receiver : this .mPackage.receivers) { receivers.put(receiver.getComponentName(), receiver.info); try { final BroadcastReceiver br = BroadcastReceiver.class .cast (getClassLoader ().loadClass (receiver .getComponentName ().getClassName ()).newInstance ()) ; BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive (Context context, Intent intent) { br.onReceive(getPluginContext(), intent); } }; for (PackageParser.ActivityIntentInfo aii : receiver.intents) { this .mHostContext.registerReceiver(broadcastReceiver, aii); } } catch (Exception e) { e.printStackTrace(); } }