Direct-Run-Apk apk免安装直接启动原理与实现(一)
2015-10-21 18:15
363 查看
我想这个应该很多人探讨过,我13年的时候就把这个框架做出来了,可以直接运行80%的apk,不过本人比较懒一直没去写文章总结其中一些心得,今天给大家分享一下,
类似项目 http://git.oschina.net/lody/Direct-load-apk
首先我们来探讨一个 apk 是如何启动的,
OK,首先你得安装这个apk,然后你点击图标,结果apk就启动了,看到了画面,完成。
第一条日志是启动Activity,Intent 内容:
action=android.intent.action.MAIN
category=android.intent.category.LAUNCHER,
所以你明白了为什么你的主 Activity 需要这样配置。
第二条日志是启动进程,
所以点击apk图片后系统做了2件事情:1.启动一个进程; 2.在这个进程里启动主Activity
不过我们今天要讨论的是不安装apk启动,不安装怎么启动,总得有个入口给我吧,
是的我们需要一个宿主:想象一个文件管理器,在列表里点击 apk 直接启动,这个文件管理器就是我们的宿主,
那你非要问可以不要宿主吗?
答案是,不可以,我也没办法,没有入口你怎么去操作!
下面我们要讨论的问题是宿主如何实现 apk 直接启动功能。
首先,启动apk启动什么,一个Activity,那么好我们从未安装的apk AndroidManifest.xml 解析出启动Activity,然后调用 startActivity 不就 ok 吗,对不起,崩溃,Activity 未在 AndroidManifest.xml 配置!
所以我们只能启动一个再宿主中已经配置的Activity,
细究Activity如何启动,抓根本,startActivity 最终实现调用的是(不要问我如何找到的,在 android.app.Instrumentation 第 1419 行)
[java] view
plaincopy
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, null, options);
这个方法直接调到了 ActivityManagerService (AMS)系统进程,响应完后,再回到应用进程,回调到 ActivityThread 内部 Handler H:
android.app.ActivityThread line 1190
[cpp] view
plaincopy
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;
android.app.ActivityThread handleLaunchActivity(ActivityClientRecord r, Intent customIntent) line:2217
android.app.ActivityThread performLaunchActivity(ActivityClientRecord r, Intent customIntent) line:2077
[java] view
plaincopy
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
[java] view
plaincopy
android.app.Instrumentation newActivity line 1057
[java] view
plaincopy
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
思路很简单,通过反射创建 activity 对象,注意哦这是在应用进程,聪明的你可能想到了,启动一个在宿主配置的activity,回调创建activity对象的时候换成真正的Activity对象不就OK么
下面我们来探讨这个如何实现,
首先,我们启动 Activity 需要传一个 Intent 对象,AMS用来查找Activity 组件,performLaunchActivity 方法的 r 参数里面有一个 intent 对象,这个intent 就是startActivity()方法传递的(这一点非常关键),
[java] view
plaincopy
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 ,
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 方法的部分代码:
[java] view
plaincopy
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 创建
接下来就是 LoadedApk 几个关键对象的创建:
1.ClassLoader 代码管理器
android.app.LoadedApk line 318
[java] view
plaincopy
mClassLoader =
ApplicationLoaders.getDefault().getClassLoader(
zip, libraryPath, mBaseClassLoader);
.
PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return pathClassloader;
如果使用 PathClassLoader 去创建是不可行的,好在 android 开放了一个 DexClassloader 给我们,所以我们要在 LoadedApk 创建后用反射替换掉 mClassLoader 对象
2.Resources 资源管理器
android.app.LoadedApk line 318
[java] view
plaincopy
public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir,
Display.DEFAULT_DISPLAY, null, this);
}
return mResources;
}
[java] view
plaincopy
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 对象
3.Application 每个apk启动创建的第一个组件就是 Application
android.app.LoadedApk line 486
[java] view
plaincopy
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 运行所需要的环境就搭建好了,
接下来就是使用偷梁换柱方法把 apk 的主 activity 启动起来。
当然,只言片语很难讲解这套框架的实现,其中还有很多细节需要处理
不过原理就是这些,偷梁换柱!
今天先讲到这,框架源码到时候会放出来
类似项目 http://git.oschina.net/lody/Direct-load-apk
首先我们来探讨一个 apk 是如何启动的,
OK,首先你得安装这个apk,然后你点击图标,结果apk就启动了,看到了画面,完成。
第一个问题:点击apk图标的时候系统做了什么事情?
打开 logcat 新建一个过滤器以 ActivityManager 为 Tag,清空logcat,点击apk图标,你可以看到下面的日志:第一条日志是启动Activity,Intent 内容:
action=android.intent.action.MAIN
category=android.intent.category.LAUNCHER,
所以你明白了为什么你的主 Activity 需要这样配置。
第二条日志是启动进程,
所以点击apk图片后系统做了2件事情:1.启动一个进程; 2.在这个进程里启动主Activity
不过我们今天要讨论的是不安装apk启动,不安装怎么启动,总得有个入口给我吧,
是的我们需要一个宿主:想象一个文件管理器,在列表里点击 apk 直接启动,这个文件管理器就是我们的宿主,
那你非要问可以不要宿主吗?
答案是,不可以,我也没办法,没有入口你怎么去操作!
下面我们要讨论的问题是宿主如何实现 apk 直接启动功能。
第二个问题:宿主如何实现 apk 直接启动功能?
第一个问题里我们已经阐述了apk如何启动的,那么我们需要做的就是模拟系统所做的工作。首先,启动apk启动什么,一个Activity,那么好我们从未安装的apk AndroidManifest.xml 解析出启动Activity,然后调用 startActivity 不就 ok 吗,对不起,崩溃,Activity 未在 AndroidManifest.xml 配置!
所以我们只能启动一个再宿主中已经配置的Activity,
细究Activity如何启动,抓根本,startActivity 最终实现调用的是(不要问我如何找到的,在 android.app.Instrumentation 第 1419 行)
[java] view
plaincopy
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, null, options);
这个方法直接调到了 ActivityManagerService (AMS)系统进程,响应完后,再回到应用进程,回调到 ActivityThread 内部 Handler H:
android.app.ActivityThread line 1190
[cpp] view
plaincopy
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;
android.app.ActivityThread handleLaunchActivity(ActivityClientRecord r, Intent customIntent) line:2217
android.app.ActivityThread performLaunchActivity(ActivityClientRecord r, Intent customIntent) line:2077
[java] view
plaincopy
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
[java] view
plaincopy
android.app.Instrumentation newActivity line 1057
[java] view
plaincopy
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
思路很简单,通过反射创建 activity 对象,注意哦这是在应用进程,聪明的你可能想到了,启动一个在宿主配置的activity,回调创建activity对象的时候换成真正的Activity对象不就OK么
下面我们来探讨这个如何实现,
首先,我们启动 Activity 需要传一个 Intent 对象,AMS用来查找Activity 组件,performLaunchActivity 方法的 r 参数里面有一个 intent 对象,这个intent 就是startActivity()方法传递的(这一点非常关键),
1.使用动态代理拦截 ActivityManagerNative 的 startActivity 方法:
(至于Java动态代理技术自己百度)判断 Intent 参数,如果要启动的 Activity 是未安装的apk的,那么把他换成宿主已声明的,[java] view
plaincopy
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 方法的部分代码:
[java] view
plaincopy
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 创建
接下来就是 LoadedApk 几个关键对象的创建:
1.ClassLoader 代码管理器
android.app.LoadedApk line 318
[java] view
plaincopy
mClassLoader =
ApplicationLoaders.getDefault().getClassLoader(
zip, libraryPath, mBaseClassLoader);
.
PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return pathClassloader;
如果使用 PathClassLoader 去创建是不可行的,好在 android 开放了一个 DexClassloader 给我们,所以我们要在 LoadedApk 创建后用反射替换掉 mClassLoader 对象
2.Resources 资源管理器
android.app.LoadedApk line 318
[java] view
plaincopy
public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir,
Display.DEFAULT_DISPLAY, null, this);
}
return mResources;
}
[java] view
plaincopy
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 对象
3.Application 每个apk启动创建的第一个组件就是 Application
android.app.LoadedApk line 486
[java] view
plaincopy
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 运行所需要的环境就搭建好了,
接下来就是使用偷梁换柱方法把 apk 的主 activity 启动起来。
当然,只言片语很难讲解这套框架的实现,其中还有很多细节需要处理
不过原理就是这些,偷梁换柱!
今天先讲到这,框架源码到时候会放出来
相关文章推荐
- android EditText限制输入
- UWP appButtonBar样式
- 正则表达式常用语法
- nodejs图片转换字节保存
- 用AutoLayout对ScrollView进行布局
- Android Fragment间对象传递
- memcached了解使用和常用命令详解
- ffmpeg的学习资源
- Windows Memory DC原理及使用方法
- Cadence PSpice 教程 基础篇(转载)
- Android studio 打jar包
- Python时间,日期,时间戳之间转换
- selinux 与 Can't connect to MySQL server
- 用JavaScript动态加载CSS和JS文件
- 【查分约束】我爱你啊
- Opencv提取不规则ROI
- TIMIT语音库(续)
- 4.数据结构之通用链表实现
- 阿里云云服务器硬盘分区及挂载
- 关 于 解 析 php 的 问 题