VirtualApp - 独立式 Resource hook

基于之前对插件化的了解,大部分的插件对于资源都是合并式的,通过addAssetPath将插件的资源加入到assetManager中,这种方式会带来一个问题:resource id冲突。当然目前已经有方案去解决:aapt或者修改arsc文件。
VirtualApp中可以安装任意的第三方APK,如果采用合并式资源加载方案,那肯定也会有resource id冲突问题,但上面说的解决方案都需要第三方APK去配合修改,明显不能满足任意安装。
VirtualApp采用的是独立式资源加载方案,这样就不会有resource id冲突的问题。
在分析VirtualApp是怎么做到独立式资源加载前,需要先了解一下应用获取resources大概的流程。在自定义Application中,我们可以通过getResources去获取当前的资源,下面看看getResources调用流程:

最终会走到ContextWrapper中:
    public Resources getResources() {
        return mBase.getResources();
    }
发现是从mBase中拿到resources,那mBase从哪里来?也是在ContextWrapper类中:
 /**
     * Set the base context for this ContextWrapper.  All calls will then be
     * delegated to the base context.  Throws
     * IllegalStateException if a base context has already been set.
     * 
     * @param base The new base context for this wrapper.
     */
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
ContextWrapper的attachBaseContext方法是在哪里被调用的呢?base context几时被创建的?

具体调用流程分析参考: Android应用程序窗口(Activity)的运行上下文环境(Context)的创建过程分析
基于上面调用时序图,看看上述几个方法的实现
LoadedApk.makeApplication方法:
public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
			
			    ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
			
			}

Instrumentation newApplication方法:
 public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        return newApplication(cl.loadClass(className), context);
    }
    static public Application newApplication(Class<?> clazz, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = (Application)clazz.newInstance();
        app.attach(context);
        return app;
    }
Application attach方法:
/* package */ final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }
最终发现原来ContextWrapper 中的mBase是在LoadedApk.makeApplication创建的,
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
attachBaseContext方法中的base参数其实ContextImpl的实例。
现在转移到分析ContextImpl类
看看ContextImpl.createAppContext方法:
  static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
        return new ContextImpl(null, mainThread,
                packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
    }
    private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
            Display display, Configuration overrideConfiguration, int createDisplayWithId) {
        mOuterContext = this;
        Resources resources = packageInfo.getResources(mainThread);
		mResources = resources;
	}
	
    public Resources getResources() {
        return mResources;
    }
	
发现resources来自packageInfo,packageInfo是LoadedApk的一个实例,
packageInfo是在ActivityThread的handleBindApplication方法中被创建的
 private void handleBindApplication(AppBindData data) {
	data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
 }
  public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
            CompatibilityInfo compatInfo) {
        return getPackageInfo(ai, compatInfo, null, false, true, false);
   }
	
来看看LoadedApk中的getResources方法是怎么实现的:

    public Resources getResources(ActivityThread mainThread) {
        if (mResources == null) {
            mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
                    mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this);
        }
        return mResources;
    }
	
    public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
            CompatibilityInfo compatInfo, ClassLoader baseLoader,
            boolean securityViolation, boolean includeCode, boolean registerPackage) {

        setApplicationInfo(aInfo);

    }
	
   private void setApplicationInfo(ApplicationInfo aInfo) {
        final int myUid = Process.myUid();
        mAppDir = aInfo.sourceDir;
        mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
        mSplitAppDirs = aInfo.splitSourceDirs;
        mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
        mOverlayDirs = aInfo.resourceDirs;
        mSharedLibraries = aInfo.sharedLibraryFiles;
        mDataDir = aInfo.dataDir;
        mLibDir = aInfo.nativeLibraryDir;
    }

最终resources是调用ActivityThread.getTopLevelResources方法创建的:
    /**
     * Creates the top level resources for the given package. Will return an existing
     * Resources if one has already been created.
     */
    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());
    }

ResourceManager根据APK的相关路径信息,读取到资源,最终生成Resources。

小结一下:Application调用getResources方法,最终获取到的是LoadedApk对象中根据APK相关路径创建的Resources对象。LoadedApk对象最早的创建是在ActivityThread.handleBindApplication时。
handleBindApplication是在绑定自定义Application时被调用。

分析了半天和VirtualApp的独立式资源加载方案有啥关系???
看段VirtualApp的代码
private AppBindData mBoundApplication;

    private final class AppBindData {
        String processName;
        ApplicationInfo appInfo;
        List<ProviderInfo> providers;
        Object info;
    }

private void bindApplicationNoCheck(String packageName, String processName, ConditionVariable lock) {
 AppBindData data = new AppBindData();
 
  mBoundApplication = data;
  Context context = createPackageContext(data.appInfo.packageName);
  mBoundApplication.info = ContextImpl.mPackageInfo.get(context);
  mInitialApplication = LoadedApk.makeApplication.call(data.info, false, null);
  mirror.android.app.ActivityThread.mInitialApplication.set(mainThread, mInitialApplication);
 }
 
 
VirtualApp在运行第三方App时,会执行bindApplicationNoCheck去将第三方自定义Application绑定起来,

mInitialApplication = LoadedApk.makeApplication.call(data.info, false, null);
dataInfo.makeApplication,这里就是开始调用创建Application, 那data.info几时被创建的?继续看上面贴出来的bindApplicationNoCheck, 
 mBoundApplication = data;
  Context context = createPackageContext(data.appInfo.packageName);
  mBoundApplication.info = ContextImpl.mPackageInfo.get(context);
data.info 是 mBoundApplication.info,  mBoundApplication.info是从context读取出来的,而context是调用createPackageContext方法,这个方法参数是packageName->第三方应用包名。
   @Override
    public Context createPackageContext(String packageName, int flags)
            throws NameNotFoundException {
        return createPackageContextAsUser(packageName, flags,
                mUser != null ? mUser : Process.myUserHandle());
    }

    @Override
    public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
            throws NameNotFoundException {
        LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
                flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
        if (pi != null) {
            ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
                    user, flags, mDisplay, null, Display.INVALID_DISPLAY);
            if (c.mResources != null) {
                return c;
            }
        }
    }
context其实是ContextImpl的一个实例,在创建context时,LoadedApk对象也被创建出来。

小结:VirtualApp在bindApplication时会先根据第三方应用包名创建context,在创建context时,LoadedApk对象也被创建出来,然后基于创建的LoadedApk对象执行makeApplication方法,又回到前文分析如和attachBaseContext。


总结: VirtualApp通过调用createPackageContext创建ConextImpl实例,在创建ConextImpl实例时创建LoadedApk实例,LoadedApk.getResources根据第三方应用相关信息生成Resources实例。自定义Application通过loadedApk.getResources去查找资源。





  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
简介VirtualApp是一个App虚拟化引擎(简称VA)。VirtualApp已兼容Android 0(8.0 Preview)。VirtualApp在你的App内创建一个虚拟空间,你可以在虚拟空间内任意的安装、启动和卸载APK,这一切都与外部隔离,如同一个沙盒。运行在VA中的APK无需在外部安装,即VA支持免安装运行APK。VA目前被广泛应用于双开/多开、应用市场、模拟定位、一键改机、隐私保护、游戏修改、自动化测试、无感知热更新等技术领域,但它决不仅限于此,Android本身就是一个极其开放的平台,免安装运行APK这一Feature打开了无限可能--------这都取决于您的想象力。申明当您需要将VirtualApp用于商业用途时,请务必联系QQ:10890 购买商业授权。您如果未经授权将VirtualAppApp模块作为您自己的App用于牟利或上传软件市场,我们取证后将直接报警(侵犯著作权罪)。购买商业授权是对我们最大的支持和认可,我们将投入更多精力和时间来不断完善优化VirtualApp,作为购买商业授权的回报,您可以获得未开放的商业版本和1vs1的支持(技术、运营、预警)!同时我们也支持基于VirtualAppAPP订制开发,请联系:QQ:10890 洽谈。请注意VirtualApp代码的更新频率非常快(以小时为单位),每一次代码的更新都有可能修复重大BUG,所以请 watch 本项目,并注意随时更新代码,以免给您带来损失!已支持的加固(不断更新)360加固腾讯加固梆梆加固梆梆企业版(12306客户端 Pass)爱加密百度加固娜迦加固乐变加固网易易盾通付盾(已支持的加固均可通过VA来脱壳,本技术不公开)在VA使用Google服务VA支持运行官方的Google服务套件,同时我们也提供了对MicroG的支持。您可以通过在VA中安装MicroG来支持Google服务,这样,即使外部没有Google服务,用户也可以在VA中享受Google服务。MicroG套件可在此下载:Download MicroGMicroG的必要模块:Services CoreServices Framework ProxyStore如果您需要在VA中使用官方的Google服务套件(外部已安装的前提下),则可以通过 GmsSupport.installGms(userId) 来安装。注意,您不能同时安装MicroGms和官方的Gms。使用说明前往你的Application并添加如下代码:@Override     protected void attachBaseContext(Context base) {         super.attachBaseContext(base);         try {             VirtualCore.getCore().startup(base);         } catch (Throwable e) {             e.printStackTrace();         }     }安装App:VirtualCore.getCore().installApp({APK PATH}, flags);启动App:VirtualCore.getCore().uninstallApp({PackageName});该App的基本信息:VirtualCore.getCore().findApp({PackageName});
根据提供的引用内容,"husky - commit-msg hook exited with code 1 (error)" 是一个错误消息,它表示在执行 "commit-msg" 钩子时出现了错误。这通常是由于提交消息不符合预定义的规则或格式所导致的。 为了解决这个问题,你可以尝试以下几个步骤: 1. 检查提交消息的格式:首先,你需要确保你的提交消息符合预定义的规则和格式。通常,提交消息应该包含一个简短的描述和一个更详细的说明。你可以参考项目的提交消息指南或规范来了解正确的提交消息格式。 2. 检查 "commit-msg" 钩子脚本:"commit-msg" 钩子是一个自定义的脚本,用于在提交消息被保存之前进行验证或修改。你可以检查该钩子脚本是否存在错误或逻辑问题。确保脚本能够正确地执行验证并返回正确的退出代码。 3. 检查 husky 配置:husky 是一个用于管理 Git 钩子的工具。你可以检查 husky 的配置文件,确保 "commit-msg" 钩子被正确地配置和启用。如果配置有误,你可以尝试重新配置或更新 husky。 4. 检查 Git 配置:最后,你可以检查 Git 的全局或项目级配置,确保没有其他配置或钩子脚本与 "commit-msg" 钩子冲突或干扰。你可以使用 `git config --global --list` 命令来查看全局配置,使用 `git config --local --list` 命令来查看项目级配置。 如果以上步骤都没有解决问题,你可以尝试搜索相关错误消息或在项目的文档或社区中寻求帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值