自己封装一个插件化框架

来源:互联网 发布: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,点击下载源码

原创粉丝点击