自己封装一个插件化框架
来源:互联网 发布:python 字典推导式 编辑:程序博客网 时间:2024/06/05 12:03
一 概述
研究了一下滴滴开源的插件化框架,感觉功能挺强大的,于是就想自己动手也封装一个,不过相对于滴滴是支持四大组件的,我这里就只对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.mPackage.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); ReflectUtil.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,点击下载源码
- 自己封装一个插件化框架
- 封装一个自己的mvc框架
- 自己做工具--用原生js封装一个AJAX插件
- 如何用原生js封装一个属于自己的插件
- 如何封装一个自己的mvc框架(一)
- 如何封装一个自己的mvc框架(二)
- 如何封装一个自己的mvc框架(三)
- 封装一个属于自己的mvc框架(1)
- 如何封装一个自己的mvc框架(四)
- 如何封装一个自己的mvc框架(五)
- 如何封装一个自己的mvc框架(六)
- 封装一个属于自己的mvc框架(2)
- 如何封装一个自己的mvc框架(七)
- 封装一个属于自己的mvc框架(3)
- 封装框架(一)为什么要封装一个自己的框架
- 封装jquery,自己制作插件
- 封装自己的jquery插件
- 封装自己的jq插件
- CVPR 2017最佳论文解读:密集连接卷积网络DenseNet
- HDU
- hibernate动态表名
- nio简单demo,帮助理解io与nio区别
- Linux环境下通过pdb调试Python程序
- 自己封装一个插件化框架
- OSI模型与TCP/IP参考模型
- NYOJ-括号匹配(二)
- 喷水装置一(贪心算法,南阳oj ,6)
- 数据库事务的四大特性以及事务的隔离级别
- Oracle12c CDB和PDB数据库的启动与关闭说明
- 单点登录
- 类加载顺序
- 找点 NYOJ-891 【贪心】