本地资源加载方案
2019-12-19 本文已影响0人
Liuqc
replugin资源加载方案
基本原理:
通过调用PackageManager中getPackageArchiveInfo方法,获取PackageInfo对象,资源路径设置到PackageInfo.applicationInfo.sourceDir和PackageInfo.applicationInfo.publicSourceDir,从PackageInfo中Resource然后赋值到PluginContext中,在每个加载的插件中,所有的activity会继承相关的PluginActivity,在PluginActivity中attachBaseContext中替换成PluginContext
final boolean loadDex(ClassLoader parent, int load) {
try {
PackageManager pm = mContext.getPackageManager();
mPackageInfo = Plugin.queryCachedPackageInfo(mPath);
if (mPackageInfo == null) {
// PackageInfo
mPackageInfo = pm.getPackageArchiveInfo(mPath,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);
if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "get package archive info null");
}
mPackageInfo = null;
return false;
}
//设置资源路径
mPackageInfo.applicationInfo.sourceDir = mPath;
mPackageInfo.applicationInfo.publicSourceDir = mPath;
if (TextUtils.isEmpty(mPackageInfo.applicationInfo.processName)) {
mPackageInfo.applicationInfo.processName = mPackageInfo.applicationInfo.packageName;
}
// 添加针对SO库的加载
// 此属性最终用于ApplicationLoaders.getClassLoader,在创建PathClassLoader时成为其参数
// 这样findLibrary可不用覆写,即可直接实现SO的加载
// Added by Jiongxuan Zhang
PluginInfo pi = mPluginObj.mInfo;
File ld = pi.getNativeLibsDir();
mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath();
// // 若PluginInfo.getFrameworkVersion为FRAMEWORK_VERSION_UNKNOWN(p-n才会有),则这里需要读取并修改
// if (pi.getFrameworkVersion() == PluginInfo.FRAMEWORK_VERSION_UNKNOWN) {
// pi.setFrameworkVersionByMeta(mPackageInfo.applicationInfo.metaData);
// }
...
}
// TODO preload预加载虽然通知到常驻了(但pluginInfo是通过MP.getPlugin(name, true)完全clone出来的),本进程的PluginInfo并没有得到更新
// TODO 因此preload会造成某些插件真正生效时由于cache,造成插件版本号2.0或者以上无法生效。
// TODO 这里是临时做法,避免发版前出现重大问题,后面可以修过修改preload的流程来优化
// 若PluginInfo.getFrameworkVersion为FRAMEWORK_VERSION_UNKNOWN(p-n才会有),则这里需要读取并修改
if (mPluginObj.mInfo.getFrameworkVersion() == PluginInfo.FRAMEWORK_VERSION_UNKNOWN) {
mPluginObj.mInfo.setFrameworkVersionByMeta(mPackageInfo.applicationInfo.metaData);
// 只有“P-n”插件才会到这里,故无需调用“纯APK”的保存功能
// PluginInfoList.save();
}
// 创建或获取ComponentList表
// Added by Jiongxuan Zhang
mComponents = Plugin.queryCachedComponentList(mPath);
if (mComponents == null) {
// ComponentList
mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo);
// 动态注册插件中声明的 receiver
regReceivers();
// 缓存表:ComponentList
synchronized (Plugin.FILENAME_2_COMPONENT_LIST) {
Plugin.FILENAME_2_COMPONENT_LIST.put(mPath, new WeakReference<>(mComponents));
}
/* 只调整一次 */
// 调整插件中组件的进程名称
adjustPluginProcess(mPackageInfo.applicationInfo);
// 调整插件中 Activity 的 TaskAffinity
adjustPluginTaskAffinity(mPluginName, mPackageInfo.applicationInfo);
}
if (load == Plugin.LOAD_INFO) {
return isPackageInfoLoaded();
}
mPkgResources = Plugin.queryCachedResources(mPath);
// LOAD_RESOURCES和LOAD_ALL都会获取资源,但LOAD_INFO不可以(只允许获取PackageInfo)
if (mPkgResources == null) {
// Resources
try {
if (BuildConfig.DEBUG) {
// 如果是Debug模式的话,防止与Instant Run冲突,资源重新New一个
Resources r = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
mPkgResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration());
} else {
mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
}
} catch (NameNotFoundException e) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, e.getMessage(), e);
}
return false;
}
...
}
if (load == Plugin.LOAD_RESOURCES) {
return isResourcesLoaded();
}
mClassLoader = Plugin.queryCachedClassLoader(mPath);
if (mClassLoader == null) {
// ClassLoader
String out = mPluginObj.mInfo.getDexParentDir().getPath();
//changeDexMode(out);
//
Log.i("dex", "load " + mPath + " ...");
if (BuildConfig.DEBUG) {
// 因为Instant Run会替换parent为IncrementalClassLoader,所以在DEBUG环境里
// 需要替换为BootClassLoader才行
// Added by yangchao-xy & Jiongxuan Zhang
parent = ClassLoader.getSystemClassLoader();
} else {
// 线上环境保持不变
parent = getClass().getClassLoader().getParent(); // TODO: 这里直接用父类加载器
}
String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
long begin = 0;
boolean isDexExist = false;
if (LOG) {
begin = System.currentTimeMillis();
File dexFile = mPluginObj.mInfo.getDexFile();
if (dexFile.exists() && dexFile.length() > 0) {
isDexExist = true;
}
}
mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent);
Log.i("dex", "load " + mPath + " = " + mClassLoader);
if (mClassLoader == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "get dex null");
}
return false;
}
...
}
if (load == Plugin.LOAD_DEX) {
return isDexLoaded();
}
// Context
mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "pkg context=" + mPkgContext);
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p=" + mPath + " m=" + e.getMessage(), e);
}
return false;
}
return true;
}
PluginActivity中,替换包装类代码
@Override
protected void attachBaseContext(Context newBase) {
newBase = RePluginInternal.createActivityContext(this, newBase);
super.attachBaseContext(newBase);
}
相关系统源码分析
PackageManager 中getPackageArchiveInfo
public PackageInfo getPackageArchiveInfo(String archiveFilePath, @PackageInfoFlags int flags) {
final PackageParser parser = new PackageParser();
final File apkFile = new File(archiveFilePath);
try {
PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
if ((flags & GET_SIGNATURES) != 0) {
PackageParser.collectCertificates(pkg, 0);
}
//PackageParser 中generatePackageInfo 返回PackageInfo
return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
} catch (PackageParserException e) {
return null;
}
}
PackageParser中parseMonolithicPackage方法
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
...
final AssetManager assets = new AssetManager();
try {
//解析APK
final Package pkg = parseBaseApk(apkFile, assets, flags);
pkg.setCodePath(apkFile.getAbsolutePath());
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
} finally {
IoUtils.closeQuietly(assets);
}
}
PackageParser中parseBaseApk方法
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
...
//添加资源路径
final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
Resources res = null;
XmlResourceParser parser = null;
try {
res = new Resources(assets, mMetrics, null);
....
}
PackageParser中loadApkIntoAssetManager方法
private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
throws PackageParserException {
...
//核心逻辑添加资源路径,所以才能解析到资源包中资源
int cookie = assets.addAssetPath(apkPath);
...
return cookie;
}
调用PackageManager中getResourcesForApplication获取Recource,具体实现类ApplicationPackageManager
public Resources getResourcesForApplication(@NonNull ApplicationInfo app)
throws NameNotFoundException {
//先判断是否是系统app
if (app.packageName.equals("system")) {
return mContext.mMainThread.getSystemContext().getResources();
}
//判断
final boolean sameUid = (app.uid == Process.myUid());
//第三方APP,activitythread中获取,需要设置sourceDirh或publicSourceDir
mContext.mMainThread.getTopLevelResources(
sameUid ? app.sourceDir : app.publicSourceDir,
sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
mContext.mPackageInfo);
...
}
ActivityThread中getTopLevelResources ,调用mResourcesManager获取recourse
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
String[] libDirs, int displayId, LoadedApk pkgInfo) {
return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader());
}
ResourcesManager中getResources
public @Nullable Resources getResources(@Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader) {
...
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
return getOrCreateResources(activityToken, key, classLoader);
...
}
ResourcesManager中getOrCreateResources
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
//如果是activity
if (activityToken != null) {
...
//首先从缓存mResourceImpls取
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
} else {
...
}
}
//创建ResourcesImpl
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
synchronized (this) {
//从缓存中查找,如果存在就直接使用缓存的
ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
if (existingResourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
+ " new impl=" + resourcesImpl);
}
resourcesImpl.getAssets().close();
resourcesImpl = existingResourcesImpl;
} else {
// Add this ResourcesImpl to the cache.
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
}
final Resources resources;
if (activityToken != null) {
//activity 相关资源
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl);
} else {
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
}
//从getOrCreateResourcesLocked和getOrCreateResourcesForActivityLocked源码可知,mResourceReferences用于非activity中resource缓存,mActivityResourceReferences用于activity中resoure中缓存,如果把其中的recourse中assertmanager替换或者添加,也是可以修复资源的(api19只需要mActiveResources api>24 需要适配mResourcesImpl)
return resources;
}
}
ResourcesManager中createResourcesImpl
private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
daj.setCompatibilityInfo(key.mCompatInfo);
//创建AssetManager,并设置资源路径
final AssetManager assets = createAssetManager(key);
if (assets == null) {
return null;
}
final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
final Configuration config = generateConfig(key, dm);
final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
if (DEBUG) {
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
}
return impl;
}
总结
此方案需要在activity的attachBaseContext中,把原来的context替换成自己包装的context,从而避免反射底层代码完美实现插件中资源加载,这也是此方案的优势所在