Direct-Run-Apk apk免安装运行原理与实现(一)

源码:https://github.com/Yichou/apkrunner

 

想解决任何问题之前都得追溯根源

那么我们来看看 apk 是如何启动的,

首先你得安装这个apk,完了在Launcher点击图标,然后apk就启动了,

辣么:点击apk图标系统做了工作?

我们从logcat观察, 新建一个过滤器以 ActivityManager 或者 system_process 为 Tag,清空logcat,点击apk图标,大概可以看到下面的日志:

从日志可以看出有3个步骤,

1、启动Activity,

Intent 内容:action=android.intent.action.MAIN  category=android.intent.category.LAUNCHER,

很熟悉吧,每个Android程序主 Activity 都需要配置这2个属性,系统也是通过这2个属性来查找入口activity,如果你没配置category=android.intent.category.LAUNCHER 系统就不会在桌面创建图标

2、启动进程,

启动进程以运行activity,每一个app都有独立的进程(当然不同app也可以共用一个进程,这是后话)

3、渲染UI,

就是解析 layout 生成界面并展示,然后你就看到了画面

 

了解了步骤,那么我们就需要模仿系统干这些事情

一、创建进程

这个很简单,我们只需要给 activity或者service配置 android:process=":app0" 参数,系统就会创建新的进程来启动这个 activity,不然就是在app主进程启动。android:taskAffinity=".QApp0" 参数是配置独立任务,就是在系统任务切换界面会多出一个窗口

假设我们调用了 startActivity(this, com.apkrunner.ProxyActivity0)

二、运行Activity,

这个我们需要从apk的 AndroidManifest.xml 解析出 LAUNCHER Activity(详见代码),假设我们解析到:demo.LauncherActivity (后面会用)

解析到了直接调用 startAcvity(new Intent(this, demo.LauncherActivity.class)) 吗?

NO,你一定遇到过,如果你忘记在 AndroidManifest.xml 添加Activity,启动会崩溃

想要解决这个问题,还是只有一个办法,追本溯源,了解系统如何启动 Activity

startActivity 最终实现调用的是(用 Eclipse 跟着源码一步步走下去就能找到,在 android.app.Instrumentation  1419 行)

            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, null, options);

ActivityManagerNative 通过IPC 调到 system_process ActivityManagerService 服务,(此处省略1w行)系统处理完后回调app进程,最终到 ActivityThread 内部 Handler :

android.app.ActivityThread line 1190

                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;

友情提示:观看此文时请打开源码,跟随脚步)我们现在到了 handleLaunchActivity 

现在又到了 performLaunchActivity(ActivityClientRecord r, Intent customIntent) 

关键步骤来了,

        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
android.app.Instrumentation newActivity line 1057
    public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
    }

这个代码很简单吧,就是 loadClass newInstance,反射法加载 Activity 的class,在创建一个对象,这些都是在 app 进程做的事情,既然是在 app 进程,那么我们就完全可以控制里面的调用参数,将 className 替换成 demo.LauncherActivity

知道了原理,那么如何实现呢?

 

首先,我们启动 Activity 需要传一个 Intent 对象,AMS用来查找Activity 组件,performLaunchActivity 方法的 r 参数里面有一个 intent 对象,这个intent 就是startActivity()方法传递的(这一点非常关键),

1.使用动态代理拦截 ActivityManagerNative 的 startActivity 方法:

判断 Intent 参数,如果要启动的 Activity 是未安装的apk的,那么把他换成宿主已声明的,

 

	private Intent makeProxy(Intent oIntent, String proxyClass) {
		Intent intent = new Intent();
		intent.setClassName(ApkRunner.getShellPkg(), proxyClass);
		intent.putExtra(FLAG_PROXY, true);
		intent.putExtra(KEY_INTENT, oIntent);
		
		/**
		 * 加标志过去会导致一些莫名的问题,我们就默认给他启动一个好了 2014-4-3
		 */
//		intent.addFlags(oIntent.getFlags());
		
		return intent;
	}

把原始的 Intent 作为一个参数存储到 Intent ,

 

2.拦截 ActivityThread H 的 handleMessage(Message msg)方法:

用反射替换 Handler 的 callback 对象。

H 原本的 callback 对象是 null ,所以你的 callback  -> boolean handleMessage(Message msg) 要返回 false,让系统调用原始版本。

在 LAUNCH_ACTIVITY 消息替换 (ActivityClientRecord r) r.activityInfo 和 r.intent 

activityInfo 对象的作用,看这行 line 2808

r.packageInfo = getPackageInfo(aInfo.applicationInfo

这行代码创建了一个 LoadedApk 对象,这个对象非常关键,一个 apk 加载之后所有信息都保存在此对象(比如:DexClassLoader、Resources、Application),一个包对应一个对象,以包名区别,而 ActivityThread 里设计可以缓存N个LoadedApk,以包名为key存储在一个Map里。看 getPackageInfo 方法的部分代码:

 

                packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
                            securityViolation, includeCode &&
                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
                if (includeCode) {
                    mPackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                } else {
                    mResourcePackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                }

所以,我们需要替换 r.activityInfo ,activityInfo 使用 PackageManager getPackageArchiveInfo 创建
 

到了这里你可能发现了,一个apk运行时有3大关键要素

Context Resource ClassLoader

分别是,上下文环境,资源管理器,类管理器

上下文环境通用的,

资源管理器我们需要用未安装 apk 去创建

类管理器可以用 DexClassLoader,下面我们来一一分解

1、ClassLoader 

 

系统做法是 android.app.LoadedApk line 318

        ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();

		String tmpPath = mApkPath;
		

		
		FileUtils.createDir(appLibPath);
		
		mClassLoader = new DexClassLoader(tmpPath, 
				appLibPath,
				appSoPath, 
				baseParent);

用 DexClassloader,所以我们要在 LoadedApk 创建后用反射替换掉 mClassLoader 对象

 

2、Resources 

android.app.LoadedApk line 318

    public Resources getResources(ActivityThread mainThread) {
        if (mResources == null) {
            mResources = mainThread.getTopLevelResources(mResDir,
                    Display.DEFAULT_DISPLAY, null, this);
        }
        return mResources;
    }
        AssetManager assets = new AssetManager();
        if (assets.addAssetPath(resDir) == 0) {
            return null;
        }
...
        r = new Resources(assets, dm, config, compatInfo, token);

所以你知道如何创建一个 apk 的 Resources 了,对的,关键点就是 assets.addAssetPath(resDir)

创建好 Resources 后,替换 LoadedApk 的 mResources 对象

 

到这里准备工作似乎已经妥当,

那么 app启动创建的第一个组件就是 Application

android.app.LoadedApk line 486

    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }

        Application app = null;

        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
            java.lang.ClassLoader cl = getClassLoader();
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } catch (Exception e) {
        }
        mActivityThread.mAllApplications.add(app);
        mApplication = app;

Application对象搞定后,我们模拟调用一下 onCreate() 方法,OK 大功告成,一个 apk 运行所需要的环境就搭建好了,

 

是不是感觉没讲完?

的确是没讲完,

不过大体流程已介绍,

剩下的结合源码领悟吧

 



 

 

 

 

相关推荐
简介VirtualApp是个App虚拟引擎简称VA。VirtualApp已兼容Android 0(8.0 Preview)。VirtualApp在你的App内创建个虚拟空间,你可以在虚拟空间内任意的安装、启动和卸载APK,这切都外部隔离,如同个沙盒。运行在VA中的APK无需在外部安装,即VA支持安装运行APK。VA目前被广泛应用于双开/多开、应用市场、模拟定位、键改机、隐私保护、游戏修改、自动测试、无感知热更新等技术领域,但它决不仅限于此,Android本身就是个极其开放的平台,安装运行APKFeature打开了无限可能--------这都取决于您的想象力。申明当您需要将VirtualApp用于商业用途时,请务必联系QQ:10890 购买商业授权。您如果未经授权将VirtualApp的App模块作为您自己的App用于牟利或上传软件市场,我们取证后将直接报警侵犯著作权罪。购买商业授权是对我们最大的支持和认可,我们将投入更多精力和时间来不断完善优VirtualApp,作为购买商业授权的回报,您可以获得未开放的商业版本和1vs1的支持技术、运营、预警!同时我们也支持基于VirtualApp的APP订制开发,请联系: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});
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页