基本介绍
使用插件的原因:
- 减小安装包的体积,通过网络选择性地进行插件下发
- 模块化升级,减小网络流量
- 静默升级,用户无感知情况下进行升级
- 解决低版本机型方法数超限导致无法安装的问题
- 代码解耦
目前插件方式
代理模式。在主程序的AndroidManifest.xml中声明一些代理ActivityProxyActivity
,启动插件中的Activity会转为启动主程序中的一个ProxyActivity
,ProxyActivity
中所有系统回调都会调用插件Activity中对应的实现,最后的效果就是启动的这个Activity实际上是主程序中已经声明的一个Activity,但是相关代码执行的却是插件Activity中的代码。
缺点:
1).不够灵活,依赖代理Activity(Service)
2).需要在AndroidManifest配置的属性无法动态添加,每种配置需对应一种代理的Activity(Service)
3).插件需要经过修改才能独立运行(虽然改动很小)
4).插件代码无法断点调试。问题追踪比较麻烦(只能打log)
容器模式
优点:真正意义上的独立插件,插件部分无需做特殊的逻辑,和独立开发的app一样。
解析完之后,使用android.content.pm下各种xxxInfo类来进行封装:
所以第一步我们先来试一试使用PackageParser.parserPackage解析apk中的info。PackageParser是一个隐藏的类,所以可以利用Java的反射机制来访问其中隐藏的类和方法。
<span style="font-family:Tahoma;"> public void loadedAPK(){
String apkPath = Environment.getExternalStorageDirectory() + "/Download/plugin/plugin.apk";
try {
// 这是一个Package 解释器, 是隐藏的
// 构造函数的参数只有一个, apk文件的路径
Class cPackageParser = Class.forName("android.content.pm.PackageParser");
Class[] typeArgs = new Class[1];
typeArgs[0] = String.class;
Constructor conPackageParser= cPackageParser .getConstructor(typeArgs);
Object[] valueArgs = new Object[1];
valueArgs[0] = apkPath;
Object packageParserObj = conPackageParser.newInstance(valueArgs);
// 这个是与显示有关的, 里面涉及到一些像素显示等等, 我们使用默认的情况
DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
typeArgs = new Class[4];
typeArgs[0] = File.class;
typeArgs[1] = String.class;
typeArgs[2] = DisplayMetrics.class;
typeArgs[3] = Integer.TYPE;
Method mParsePackage = cPackageParser .getDeclaredMethod("parsePackage", typeArgs);
valueArgs = new Object[4];
valueArgs[0] = new File(apkPath);
valueArgs[1] = apkPath;
valueArgs[2] = metrics;
valueArgs[3] = 0;
Object packageObj = mParsePackage .invoke(packageParserObj , valueArgs);
// 获取应用程序信息包
Field appInfoFld = packageObj.getClass().getDeclaredField("applicationInfo");
ApplicationInfo info = (ApplicationInfo) appInfoFld.get(packageObj);
Log.d("ANDROID_LAB", "pkg:" + info.packageName + " uid=" + info.uid);
//获取Activity信息
Field factivities = packageObj.getClass().getDeclaredField("activities");
ArrayList<Object> activitiesObject = (ArrayList<Object>)factivities.get(packageObj);
Class cActivityInfo = Class.forName("android.content.pm.PackageParser$Activity");
Field factivityInfo= cActivityInfo .getDeclaredField("info");
for (Object object : activitiesObject){
ActivityInfo actInfo = (ActivityInfo)cActivityInfo.get(object);
Log.d("ANDROID_LAB", " name=" + actInfo.name);
}
} catch (Exception e) {
e.printStackTrace();
}
}</span>
从Logcat中可以看到输出了Application的包名、uid以及Activity的名称,这样我们就已经成功解析了APK中的信息。
2.要将这些信息注入到运行环境中。
获得AndroidManifest.xml信息后,如何启动Activity呢?无论是通过Launcher来启动Activity,还是通过Activity内部调用startActivity来启动新的Activity,都通过Binder进程间通信进入到ActivityManagerService进程中。ActivityManagerService没有提供相关接口,没办法通过AMS启动插件。但是研究源码发现ActivityThread中的startActivityNow这个方法绕过了AMS,就是说AMS此时根本不知道已经启动了一个Activity。所以我们可以用startActivityNow获取插件的Activity对象,再将Activity实例的DecorView设置成代理Activity的ContentView的方法启动插件Activity。
public final Activity startActivityNow(Activity parent, String id,
Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
Activity.NonConfigurationInstances lastNonConfigurationInstances) {
...
return performLaunchActivity(r, null);
}
我们可以看到startActivityNow调用了performLaunchActivity。应用程序进程通过performLaunchActivity函数将即将要启动的Activity加载到当前进程空间来,同时为启动Activity做准备。在该函数中,首先通过PMS服务查找到即将启动的Activity的包名信息,然后通过类反射方式创建一个该Activity实例,同时为应用程序启动的每一个Activity创建一个LoadedApk实例对象,应用程序进程中创建的所有LoadedApk对象保存在ActivityThread的成员变量mPackages中。接着通过LoadedApk对象的makeApplication函数,使用单例模式创建Application对象,因此在android应用程序进程中有且只有一个Application实例。然后为当前启动的Activity创建一个ContextImpl上下文对象,并初始化该上下文,到此我们可以知道,启动一个Activity需要以下对象:
1) Activity对象,需要启动的Activity;
2) LoadedApk对象,每个启动的Activity都拥有属于自身的LoadedApk对象;
3) ContextImpl对象,每个启动的Activity都拥有属于自身的ContextImpl对象;
4) Application对象,应用程序进程中有且只有一个实例,和Activity是一对多的关系;
这些对象需要我们自己构建。
1)
//
ComponentName componentName = new ComponentName(pkg,cls);
//
mActivityInfo = load.getActivityInfo(componentName);
Object[] startActivityNowParams = new Object[7];
startActivityNowParams[0] = null;
startActivityNowParams[1] = mActivityInfo.targetActivity;
startActivityNowParams[2] = innerIntent;
startActivityNowParams[3] = mActivityInfo;
startActivityNowParams[4] = null;
startActivityNowParams[5] = savedInstanceState;
startActivityNowParams[6] = null;
Method mStartActivityNow = mReflectManager.cActivityThread.mClass.getDeclaredMethod("startActivityNow",new Class[] {Activity.class, String.class, Intent.class,
ActivityInfo.class, IBinder.class, Bundle.class, mReflectManager.cConfigurationInstances.mClass});
mStartActivityNow.setAccessible(true);
mActivity = (Activity)mStartActivityNow.invoke(load.getMainThreadObj(), startActivityNowParams);