android进阶

本地资源加载方案

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,从而避免反射底层代码完美实现插件中资源加载,这也是此方案的优势所在

上一篇下一篇

猜你喜欢

热点阅读