您的位置:首页 > 其它

自己封装一个插件化框架

2017-07-26 17:49 351 查看

一 概述

研究了一下滴滴开源的插件化框架,感觉功能挺强大的,于是就想自己动手也封装一个,不过相对于滴滴是支持四大组件的,我这里就只对activity做了支持.要想知道怎么加载一个插件的activity,就得对activity的启动过程有所了解,如果不懂的可以看一下Activity的启动过程这篇文章.从这篇文章的分析得知,Activity的检测工作是在WMS中进行的,所以我们只要使用占坑的方法,先在清单文件中注册一个没用的activity取名为A,然后在进入WMS之前将要启动的activity的包名和类名替换成A的信息,这样在WMS中就可以逃避检查了.当在WMS检测完acitivty的信息要创建acitivty时在将信息替换成我们真正要创建的包名和类名.从之前的文章中分析得知,只要通过反射替换Instrumentation的execStartActivity和newActivity方法即可.

二 加载插件

想要启动插件中的activity,首先就要将apk的信息加载进来,通过PackageManagerService服务得知加载一个apk文件是通过PackageParser来完成的,但是PackageParser是一个隐藏的类,在我们的引用程序中是调用不到的,所以只能从源码中拷贝到自己的项目中来.然后调用PackageParser的parsePackage(apk, flags)方法就可以得到这个apk的Package信息了.

PackageParser.Package mPackage = PackageParserManager.parsePackage(mHostContext, apk, PackageParser.PARSE_MUST_BE_APK);


public class PackageParserManager {

public static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) throws PackageParser.PackageParserException {
if (Build.VERSION.SDK_INT >= 24) {
return PackageParserV24.parsePackage(context, apk, flags);
} else if (Build.VERSION.SDK_INT >= 21) {
return PackageParserLollipop.parsePackage(context, apk, flags);
} else {
return PackageParserLegacy.parsePackage(context, apk, flags);
}
}

private static final class PackageParserV24 {

static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws PackageParser.PackageParserException {
PackageParser parser = new PackageParser();
PackageParser.Package pkg = parser.parsePackage(apk, flags);
ReflectUtil.invokeNoException(PackageParser.class, null, "collectCertificates",
new Class[]{PackageParser.Package.class, int.class}, pkg, flags);
return pkg;
}
}

private static final class PackageParserLollipop {

static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) throws PackageParser.PackageParserException {
PackageParser parser = new PackageParser();
PackageParser.Package pkg = parser.parsePackage(apk, flags);
try {
parser.collectCertificates(pkg, flags);
} catch (Throwable e) {
// ignored
}
return pkg;
}

}

private static final class PackageParserLegacy {

static final PackageParser.Package parsePackage(Context context, File apk, int flags) {
PackageParser parser = new PackageParser(apk.getAbsolutePath());
PackageParser.Package pkg = parser.parsePackage(apk, apk.getAbsolutePath(), context.getResources().getDisplayMetrics(), flags);
ReflectUtil.invokeNoException(PackageParser.class, parser, "collectCertificates",
new Class[]{PackageParser.Package.class, int.class}, pkg, flags);
return pkg;
}

}
}


通过上面PackageParserManager 的parsePackage方法就可以将插件apk加载进来封装成Package,PackageParserManager 只是对PackageParser 在不同版本的封装,PackageParser 的源码太长就不贴了,后面我会将项目的源码提供.

三.封装插件Apk

public class LoadPlugin {
private final File mNativeLibDir;
private Context mHostContext;
private final PackageParser.Package mPackage;
private final PackageInfo mPackageInfo;
private AssetManager mAssets;
private Resources mResources;
private ClassLoader mClassLoader;
private Context mPluginContext;
private Map<ComponentName, ActivityInfo> mActivityInfos;
private Application mApplication;

public LoadPlugin(Context context, File apk) throws PackageParser.PackageParserException {
this.mHostContext = context;
//加载插件apk
mPackage = PackageParserManager.parsePackage(mHostContext, apk, PackageParser.PARSE_MUST_BE_APK);
mPackageInfo = new PackageInfo();
this.mPackageInfo.applicationInfo = this.mPacka
4000
ge.applicationInfo;
this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();
this.mPackageInfo.signatures = this.mPackage.mSignatures;
this.mPackageInfo.packageName = this.mPackage.packageName;
this.mPackageInfo.versionCode = this.mPackage.mVersionCode;
this.mPackageInfo.versionName = this.mPackage.mVersionName;
this.mPackageInfo.permissions = new PermissionInfo[0];
//封装插件的context
this.mPluginContext = new PluginContext(this,mHostContext);
//创建插件的resource
this.mResources = createResources(context, apk);

this.mNativeLibDir = context.getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE);
//设置插件的assets
this.mAssets = this.mResources.getAssets();
//创建插件的类加载器
this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());

Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();
for (PackageParser.Activity activity : this.mPackage.activities) {
activityInfos.put(activity.getComponentName(), activity.info);
}
this.mActivityInfos = Collections.unmodifiableMap(activityInfos);
this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]);

}

@WorkerThread
private static Resources createResources(Context context, File apk) {
if (Constants.COMBINE_RESOURCES) {
Resources resources = new ResourcesManager().createResources(context, apk.getAbsolutePath());
ResourcesManager.hookResources(context, resources);
return resources;
} else {
Resources hostResources = context.getResources();
AssetManager assetManager = createAssetManager(context, apk);
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
}

private static AssetManager createAssetManager(Context context, File apk) {
try {
AssetManager am = AssetManager.class.newInstance();
ReflectUtil.invoke(AssetManager.class, am, "addAssetPath", apk.getAbsolutePath());
return am;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

private  ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) {
File dexOutputDir = context.getDir(Constants.OPTIMIZE_DIR, Context.MODE_PRIVATE);
String dexOutputPath = dexOutputDir.getAbsolutePath();
DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);

if (Constants.COMBINE_CLASSLOADER) {
try {
DexUtil.insertDex(loader,mHostContext);
} catch (Exception e) {
e.printStackTrace();
}
}

return loader;
}

public String getPackageName() {
return this.mPackage.packageName;
}

public AssetManager getAssets() {
return this.mAssets;
}

public Resources getResources() {
return this.mResources;
}

public ClassLoader getClassLoader() {
return this.mClassLoader;
}

public Context getHostContext() {
return this.mHostContext;
}

public Context getPluginContext() {
return this.mPluginContext;
}

public Resources.Theme getTheme() {
Resources.Theme theme = this.mResources.newTheme();
theme.applyStyle(PluginUtil.selectDefaultTheme(this.mPackage.applicationInfo.theme, Build.VERSION.SDK_INT), false);
return theme;
}

public void setTheme(int resid) {
try {
ReflectUtil.setField(Resources.class, this.mResources, "mThemeResId", resid);
} catch (Exception e) {
e.printStackTrace();
}
}

public String getPackageResourcePath() {
int myUid = Process.myUid();
ApplicationInfo appInfo = this.mPackage.applicationInfo;
return appInfo.uid == myUid ? appInfo.sourceDir : appInfo.publicSourceDir;
}

public String getCodePath() {
return this.mPackage.applicationInfo.sourceDir;
}

public ActivityInfo getActivityInfo(ComponentName componentName) {
return this.mActivityInfos.get(componentName);
}

public Application getApplication() {
return mApplication;
}

public void invokeApplication(Instrumentation instrumentation) {
if (mApplication != null) {
return;
}

mApplication = makeApplication(false, instrumentation);
}

public Intent getLaunchIntent() {
ContentResolver resolver = this.mPluginContext.getContentResolver();
Intent launcher = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER);

for (PackageParser.Activity activity : this.mPackage.activities) {
for (PackageParser.ActivityIntentInfo intentInfo : activity.intents) {
if (intentInfo.match(resolver, launcher, false, "") > 0) {
return Intent.makeMainActivity(activity.getComponentName());
}
}
}

return null;
}

public Intent getLeanbackLaunchIntent() {
ContentResolver resolver = this.mPluginContext.getContentResolver();
Intent launcher = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER);

for (PackageParser.Activity activity : this.mPackage.activities) {
for (PackageParser.ActivityIntentInfo intentInfo : activity.intents) {
if (intentInfo.match(resolver, launcher, false, "") > 0) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setComponent(activity.getComponentName());
intent.addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER);
return intent;
}
}
}

return null;
}

public ApplicationInfo getApplicationInfo() {
return this.mPackage.applicationInfo;
}

public PackageInfo getPackageInfo() {
return this.mPackageInfo;
}

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

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

try {
this.mApplication = instrumentation.newApplication(this.mClassLoader, appClass, this.getPluginContext());
instrumentation.callApplicationOnCreate(this.mApplication);
return this.mApplication;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

class ResourcesManager {

public static synchronized Resources createResources(Context hostContext, String apk) {
Resources hostResources = hostContext.getResources();
Resources newResources = null;
AssetManager assetManager;
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
assetManager = AssetManager.class.newInstance();
ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", hostContext.getApplicationInfo().sourceDir);
} else {
assetManager = hostResources.getAssets();
}
ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", apk);
/*    List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins();
for (LoadedPlugin plugin : pluginList) {
ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", plugin.getLocation());
}*/
if (isMiUi(hostResources)) {
newResources = MiUiResourcesCompat.createResources(hostResources, assetManager);
} else if (isVivo(hostResources)) {
newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager);
} else if (isNubia(hostResources)) {
newResources = NubiaResourcesCompat.createResources(hostResources, assetManager);
} else if (isNotRawResources(hostResources)) {
newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager);
} else {
// is raw android resources
newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
} catch (Exception e) {
e.printStackTrace();
}

return newResources;

}

public static void hookResources(Context base, Resources resources) {
if (Build.VERSION.SDK_INT >= 24) {
return;
}

try {
ReflectUtil.setField(base.getClass(), base, "mResources", resources);
Object loadedApk = ReflectUtil.getPackageInfo(base);
Reflec
10ab2
tUtil.setField(loadedApk.getClass(), loadedApk, "mResources", resources);

Object activityThread = ReflectUtil.getActivityThread(base);
Object resManager = ReflectUtil.getField(activityThread.getClass(), activityThread, "mResourcesManager");
Map<Object, WeakReference<Resources>> map = (Map<Object, WeakReference<Resources>>) ReflectUtil.getField(resManager.getClass(), resManager, "mActiveResources");
Object key = map.keySet().iterator().next();
map.put(key, new WeakReference<>(resources));
} catch (Exception e) {
e.printStackTrace();
}
}

private static boolean isMiUi(Resources resources) {
return resources.getClass().getName().equals("android.content.res.MiuiResources");
}

private static boolean isVivo(Resources resources) {
return resources.getClass().getName().equals("android.content.res.VivoResources");
}

private static boolean isNubia(Resources resources) {
return resources.getClass().getName().equals("android.content.res.NubiaResources");
}

private static boolean isNotRawResources(Resources resources) {
return !resources.getClass().getName().equals("android.content.res.Resources");
}

private static final class MiUiResourcesCompat {
private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception {
Class resourcesClazz = Class.forName("android.content.res.MiuiResources");
Resources newResources = (Resources)ReflectUtil.invokeConstructor(resourcesClazz,
new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},
new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()});
return newResources;
}
}

private static final class VivoResourcesCompat {
private static Resources createResources(Context hostContext, Resources hostResources, AssetManager assetManager) throws Exception {
Class resourcesClazz = Class.forName("android.content.res.VivoResources");
Resources newResources = (Resources)ReflectUtil.invokeConstructor(resourcesClazz,
new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},
new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()});
ReflectUtil.invokeNoException(resourcesClazz, newResources, "init",
new Class[]{String.class}, hostContext.getPackageName());
Object themeValues = ReflectUtil.getFieldNoException(resourcesClazz, hostResources, "mThemeValues");
ReflectUtil.setFieldNoException(resourcesClazz, newResources, "mThemeValues", themeValues);
return newResources;
}
}

private static final class NubiaResourcesCompat {
private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception {
Class resourcesClazz = Class.forName("android.content.res.NubiaResources");
Resources newResources = (Resources)ReflectUtil.invokeConstructor(resourcesClazz,
new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},
new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()});
return newResources;
}
}

private static final class AdaptationResourcesCompat {
private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception {
Resources newResources;
try {
Class resourcesClazz = hostResources.getClass();
newResources = (Resources) ReflectUtil.invokeConstructor(resourcesClazz,
new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},
new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()});
} catch (Exception e) {
newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}

return newResources;
}
}

}


因为我只是要替换一下acitivty,所以只封装一下替换activity所需要的resource,assets,context,mClassLoader 等信息,resource和assest分别表示了插件apk的资源信息,context是activity的上下文管理者,用来获取资源,mClassLoader 是用来将加载相应的activity类文件.

四.封装插件activity的Context

class PluginContext extends ContextWrapper {

private final LoadPlugin mPlugin;

public PluginContext(LoadPlugin plugin,Context context) {
super(context);
this.mPlugin = plugin;
}
@Override
public Context getApplicationContext() {
return this.mPlugin.getApplication();
}

@Override
public ApplicationInfo getApplicationInfo() {
return this.mPlugin.getApplicationInfo();
}

private Context getHostContext() {
return getBaseContext();
}

@Override
public ClassLoader getClassLoader() {
return this.mPlugin.getClassLoader();
}

@Override
public String getPackageName() {
return this.mPlugin.getPackageName();
}

@Override
public String getPackageResourcePath() {
return this.mPlugin.getPackageResourcePath();
}

@Override
public String getPackageCodePath() {
return this.mPlugin.getCodePath();
}

@Override
public Object getSystemService(String name) {
// intercept CLIPBOARD_SERVICE,NOTIFICATION_SERVICE
if (name.equals(Context.CLIPBOARD_SERVICE)) {
return getHostContext().getSystemService(name);
} else if (name.equals(Context.NOTIFICATION_SERVICE)) {
return getHostContext().getSystemService(name);
}

return super.getSystemService(name);
}

@Override
public Resources getResources() {
return this.mPlugin.getResources();
}

@Override
public AssetManager getAssets() {
return this.mPlugin.getAssets();
}


到时将activity的context替换成我们封装的PluginContext 对象,这样在activity启动的时候获取对应的资源信息都是从我们LoadPlugin 中获取到的资源来获取.

五.定义Instrumentation子类VAInstrumentation

public class VAInstrumentation extends Instrumentation  {
public static final String TAG = "VAInstrumentation";
public static final int LAUNCH_ACTIVITY         = 100;

private Instrumentation mBase;
private Context mContext;
private LoadPlugin mLoadPlugin;

public VAInstrumentation(Instrumentation base,Context mContext,LoadPlugin loadPlugin) {

this.mBase = base;
this.mContext = mContext;
this.mLoadPlugin = loadPlugin;
}

public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
拿到要启动的acitivty的包名和类名
String targetPackageName = intent.getComponent().getPackageName();
String targetClassName = intent.getComponent().getClassName();
如果要启动的包名和当前应用的包名不同,表示是要启动插件的activity
if (!targetPackageName.equals(mContext.getPackageName()) ) {
将插件的包名和类名,先用其他字段存起来
intent.putExtra(Constants.KEY_IS_PLUGIN, true);
intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
将intent的包名和类名替换成我们占坑的acitivty信息
dispatchStubActivity(intent);
}

ActivityResult result = null;
try {
Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class,
int.class, Bundle.class};
通过反射调用Instrumentation的execStartActivity方法.
result = (ActivityResult) ReflectUtil.invoke(Instrumentation.class, mBase,
"execStartActivity", parameterTypes,
who, contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
e.printStackTrace();
}

return result;

}

private void dispatchStubActivity(Intent intent) {
ComponentName component = intent.getComponent();
String targetClassName = intent.getComponent().getClassName();

ActivityInfo info = mLoadPlugin.getActivityInfo(component);
if (info == null) {
throw new RuntimeException("can not find " + component);
}

Resources.Theme themeObj = mLoadPlugin.getResources().newTheme();
themeObj.applyStyle(info.theme, true);
//这个就是占坑的类名
String stubActivity = "com.lyf.pluginapk.SecondeActivity";
Log.i(TAG, String.format("dispatchStubActivity,[%s -> %s]", targetClassName, stubActivity));
替换intent中的包名和类名
intent.setClassName(mContext, stubActivity);
}

@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
if (intent.getBooleanExtra(Constants.KEY_IS_PLUGIN, false)) {
//如果是启动插件的acitivty,就拿到真正要启动的包名和类名
String targetClassName = PluginUtil.getTargetActivity(intent);
if (targetClassName != null) {
调用newActivity方法创建acitivty,不过这里传入的是插件acitivty的类加载器,和插件的包名,类名.所以创建的也就是插件的acitivty
Activity activity = mBase.newActivity(mLoadPlugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);

try {
// for 4.1+
ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", mLoadPlugin.getResources());
} catch (Exception ignored) {
// ignored.
}

return activity;
}
}
//如果不是启动插件,就直接启动对应的acitivty
return mBase.newActivity(cl, className, intent);

}

@Override
public void callActivityOnCreate(Activity activity, Bundle icicle) {
final Intent intent = activity.getIntent();
这个方法,主要是在调用acitivty的onCreate之前,将插件的resrouse,context等信息替换成我们之前加载插件获取的相应信息,避免找不到资源.
if (PluginUtil.isIntentFromPlugin(intent)) {
Context base = activity.getBaseContext();
try {

ReflectUtil.setField(base.getClass(), base, "mResources", mLoadPlugin.getResources());
ReflectUtil.setField(ContextWrapper.class, activity, "mBase", mLoadPlugin.getPluginContext());
ReflectUtil.setField(Activity.class, activity, "mApplication", mLoadPlugin.getApplication());
ReflectUtil.setFieldNoException(ContextThemeWrapper.class, activity, "mBase", mLoadPlugin.getPluginContext());

// set screenOrientation
ActivityInfo activityInfo = mLoadPlugin.getActivityInfo(PluginUtil.getComponent(intent));
if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
activity.setRequestedOrientation(activityInfo.screenOrientation);
}
} catch (Exception e) {
e.printStackTrace();
}

}

mBase.callActivityOnCreate(activity, icicle);
}

@Override
public Context getContext() {
return mBase.getContext();
}

@Override
public Context getTargetContext() {
return mBase.getTargetContext();
}

@Override
public ComponentName getComponentName() {
return mBase.getComponentName();
}

}


六.加载插件,替换Instrumentation

获取插件的路径
File apk = new File(Environment.getExternalStorageDirectory(), "Test3.apk");
if (apk.exists()) {
try {
加载插件
loadPlugin = new LoadPlugin(this, apk);
//hook Instrumentation类 Instrumentation在Activitythread中
Instrumentation instrumentation = ReflectUtil.getInstrumentation(this);
VAInstrumentation vaInstrumentation = new VAInstrumentation(instrumentation,this,loadPlugin);
Object activityThread = ReflectUtil.getActivityThread(this);
替换Instrumentation为我们定义的vaInstrumentation
ReflectUtil.setInstrumentation(activityThread, vaInstrumentation);
//为了设置application,否则可以不调用
loadPlugin.invokeApplication(vaInstrumentation);

} catch (Exception e) {
System.out.println("加载失败");
e.printStackTrace();
}
}


到这一步替换完instrumentation类,在启动插件中的acitivty,就可以启动起来了.

七 总结

这只是我自己为了验证滴滴的插件原理而自己实现的一遍过程,想要研究源码的可以去看看滴滴的源码,同时我也将我实现的源码上传到了github,点击下载源码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: