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 运行所需要的环境就搭建好了,

 

是不是感觉没讲完?

的确是没讲完,

不过大体流程已介绍,

剩下的结合源码领悟吧

 



 

 

 

 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值