Android基于代理的插件化思路分析

前言

正常的App开发流程基本上是这样的:开发功能-->测试--->上线,上线后发现有大bug,紧急修复---->发新版本---->用户更新----->bug修复。从发现bug到修复bug花了很长时间。我们希望bug的修复是立马生效的,用户无感知就能自动修复bug。当然,Android端的动态修复bug已经有不少框架了,不过我们今天讲的是另一个话题:Android的插件化。Android插件化有很多好处:热插拔、静默升级、bug动态修复、代码解耦等。正是因为如此,才有越来越多的公司选择插件化。

分析

Android插件化有很多的开源框架,基本上都是两种思路:代理和替换系统的一些关键变量,通过替换这些变量,达到欺骗系统的目的(又称Hook)。代理的思路比较简单,就是通过代理类Proxy,把主工程和插件工程的组件连接起来。代理类相当于傀儡,当主工程想要启动插件工程时,实际上会先调用代理类的相应方法,然后再通过代理类调用插件工程的组件,间接达到调用插件工程组件的目的。为何不直接调用插件工程的组件呢?因为通过DexClassLoader加载到内存的Activity等组件只是一个普通的类,没有上下文环境,意味着拿不到Context,意味着没有生命周期。

让Activity有"生命"

得益于Java语言的类加载器可以动态加载类的特性,在Android中加载一个普通的类是很容易的

DexClassLoader
 Class<?> mClassLaunchActivity = (Class<?>)
classLoader.loadClass(mLaunchActivity);
        mPluginActivity = (IPluginActivity)
mClassLaunchActivity.newInstance();

通过ClassLoader可以加载一个类,并生成类的实例。但是这个mPluginActivity只是一个普通的类,并没有Activity的生命周期,所以我们需要借助PluginProxyActivity来完成代理工作


public class PluginProxyActivity extends Activity {
    IPluginActivity mPluginActivity;
    String mPluginApkFilePath;
    String mLaunchActivity;
    private String mPluginName;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle bundle = getIntent().getExtras();
        if(bundle == null){
            return;
        }
        mPluginName = bundle.getString(PluginUtils.PARAM_PLUGIN_NAME);
        mLaunchActivity = bundle.getString(PluginUtils.PARAM_LAUNCH_ACTIVITY);
        File pluginFile = PluginUtils.getInstallPath(PluginProxyActivity.this, mPluginName);
        if(!pluginFile.exists()){
            return;
        }
        mPluginApkFilePath = pluginFile.getAbsolutePath();
        try {
            initPlugin();
            mPluginActivity.IOnCreate(savedInstanceState);
        } catch (Exception e) {
            mPluginActivity = null;
            e.printStackTrace();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if(mPluginActivity != null){
            mPluginActivity.IOnResume();
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        if(mPluginActivity != null) {
            mPluginActivity.IOnStart();
        }
    }
    ........ //省略部分代码
    private void initPlugin() throws Exception {
        PackageInfo packageInfo;
        try {
            PackageManager pm = getPackageManager();
            packageInfo = pm.getPackageArchiveInfo(mPluginApkFilePath, PackageManager.GET_ACTIVITIES);
        } catch (Exception e) {
            throw e;
        }

        ClassLoader classLoader = PluginUtils.getOrCreateClassLoaderByPath(this, mPluginName, mPluginApkFilePath);
        // get default launchActivity if target Activity is null
        if (mLaunchActivity == null || mLaunchActivity.length() == 0) {
            if (packageInfo == null || (packageInfo.activities == null) || (packageInfo.activities.length == 0)) {
                throw new ClassNotFoundException("Launch Activity not found");
            }
            mLaunchActivity = packageInfo.activities[0].name;
        }
        Class<?> mClassLaunchActivity = (Class<?>) classLoader.loadClass(mLaunchActivity);

        mPluginActivity = (IPluginActivity) mClassLaunchActivity.newInstance();
        mPluginActivity.IInit(mPluginApkFilePath, this, classLoader, packageInfo);
    }

    protected Class<? extends PluginProxyActivity> getProxyActivity(String pluginActivityName) {
        return getClass();
    }

    @Override
    public void startActivityForResult(Intent intent, int requestCode) {
        boolean pluginActivity = intent.getBooleanExtra(PluginUtils.PARAM_IS_IN_PLUGIN, false);
        if (pluginActivity) {
            String launchActivity = null;
            ComponentName componentName = intent.getComponent();
            if(null != componentName) {
                launchActivity = componentName.getClassName();
            }
            intent.putExtra(PluginUtils.PARAM_IS_IN_PLUGIN, false);
            if (launchActivity != null && launchActivity.length() > 0) {
                Intent pluginIntent = new Intent(this, getProxyActivity(launchActivity));
                pluginIntent.putExtra(PluginUtils.PARAM_PLUGIN_NAME, mPluginName);
                pluginIntent.putExtra(PluginUtils.PARAM_PLUGIN_PATH, mPluginApkFilePath);
                pluginIntent.putExtra(PluginUtils.PARAM_LAUNCH_ACTIVITY, launchActivity);
                startActivityForResult(pluginIntent, requestCode);
            }
        } else {
            super.startActivityForResult(intent, requestCode);
        }
    }
}

每次启动新的Activity的时候,都会调用startActivityForResult,在此方法中,进行了Intent的替换,启动的新Activity还是会跳到ProxyActivity中。

 Intent pluginIntent = new Intent(this, getProxyActivity(launchActivity));
                pluginIntent.putExtra(PluginUtils.PARAM_PLUGIN_NAME, mPluginName);
                pluginIntent.putExtra(PluginUtils.PARAM_PLUGIN_PATH, mPluginApkFilePath);
                pluginIntent.putExtra(PluginUtils.PARAM_LAUNCH_ACTIVITY, launchActivity);
                startActivityForResult(pluginIntent, requestCode);

所有的插件工程都需要继承于BasePluginActivity,其主要代码如下


public class BasePluginActivity extends Activity implements IPluginActivity {

    private boolean mIsRunInPlugin;
    private ClassLoader mDexClassLoader;
    private Activity mOutActivity;
    private String mApkFilePath;
    private PackageInfo mPackageInfo;
    private PluginContext mContext;
    private View mContentView;
    private Activity mActivity;
    private boolean mFinished;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (mIsRunInPlugin) {
            mActivity = mOutActivity;
        } else {
            super.onCreate(savedInstanceState);
            mActivity = this;
        }
    }

    @Override
    public void setContentView(int layoutResID) {
        if (mIsRunInPlugin) {
            mContentView = LayoutInflater.from(mContext).inflate(layoutResID, null);
            mActivity.setContentView(mContentView);
        } else {
            super.setContentView(layoutResID);
        }
    }

    @Override
    public void setContentView(View view) {
        if (mIsRunInPlugin) {
            mContentView = view;
            mActivity.setContentView(mContentView);
        } else {
            super.setContentView(view);
        }
    }

    @Override
    public View findViewById(int id) {
        if (mIsRunInPlugin && mContentView != null) {
            View v = mContentView.findViewById(id);
            if (null == v) {
                v = super.findViewById(id);
            }
            return v;
        } else {
            return super.findViewById(id);
        }
    }

    @Override
    public void IOnCreate(Bundle savedInstanceState) {
        onCreate(savedInstanceState);
    }

    @Override
    public void IOnResume() {
        onResume();
    }

    @Override
    public void IOnStart() {
        onStart();
    }

    @Override
    public void IOnPause() {
        onPause();
    }

    @Override
    public void IOnStop() {
        onStop();
    }

    @Override
    public void IOnDestroy() {
        onDestroy();
    }

    @Override
    public void IOnRestart() {
        onRestart();
    }

    @Override
    public void IInit(String path, Activity context, ClassLoader classLoader, PackageInfo packageInfo) {
        mIsRunInPlugin = true;
        mDexClassLoader = classLoader;
        mOutActivity = context;
        mApkFilePath = path;
        mPackageInfo = packageInfo;

        mContext = new PluginContext(context, 0, mApkFilePath, mDexClassLoader);
        attachBaseContext(mContext);
    }

    @Override
    protected void onResume() {
        if (mIsRunInPlugin) {
            return;
        }
        super.onResume();
    }

    @Override
    protected void onPause() {
        if (mIsRunInPlugin) {
            return;
        }
        super.onPause();

    }

    @Override
    protected void onStart() {
        if (mIsRunInPlugin) {
            return;
        }
        super.onStart();
    }

    @Override
    protected void onRestart() {
        if (mIsRunInPlugin) {
            return;
        }
        super.onRestart();
    }

    @Override
    protected void onStop() {
        if (mIsRunInPlugin) {
            return;
        }
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        if (mIsRunInPlugin) {
            mDexClassLoader = null;
            return;
        }
        super.onDestroy();
    }

    @Override
    public void finish() {
        if (mIsRunInPlugin) {
            int resultCode = Activity.RESULT_CANCELED;
            Intent data = null;
            synchronized (this) {
                Field field;
                try {
                    field = Activity.class.getDeclaredField("mResultCode");
                    field.setAccessible(true);
                    resultCode = (Integer) field.get(this);
                    field = Activity.class.getDeclaredField("mResultData");
                    field.setAccessible(true);
                    data = (Intent) field.get(this);
                } catch (Exception e) {
                }
            }
            mOutActivity.setResult(resultCode, data);
            mOutActivity.finish();
            mFinished = true;
        } else {
            super.finish();
        }
    }

    @Override
    public boolean isFinishing() {
        if (mIsRunInPlugin) {
            return mFinished;
        } else {
            return super.isFinishing();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (mIsRunInPlugin) {
            return;
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    @Override
    public LayoutInflater getLayoutInflater() {
        if (mContext != null) {
            return LayoutInflater.from(mContext);
        } else {
            return LayoutInflater.from(mActivity);
        }
    }

    @Override
    public WindowManager getWindowManager() {
        if (mIsRunInPlugin) {
            return mOutActivity.getWindowManager();
        } else {
            return super.getWindowManager();
        }
    }

    @Override
    public void startActivityForResult(Intent intent, int requestCode) {
        if (mIsRunInPlugin) {
            intent.putExtra(PluginUtils.PARAM_IS_IN_PLUGIN, true);
            mActivity.startActivityForResult(intent, requestCode);
        } else {
            super.startActivityForResult(intent, requestCode);
        }
    }
}

在BasePluginActivity中,覆写了很多父类Activity的方法,用来判断当前Activity是独立运行还是作为插件运行,如果是在插件中运行,则是调用插件中设置进来的代理类ProxyActivity(mActivity)的相应方法。同时使用IPluginActivity来模拟Activity的生命周期

public interface IPluginActivity {
    public void IOnCreate(Bundle savedInstanceState);

    public void IOnResume();

    public void IOnStart();

    public void IOnPause();

    public void IOnStop();

    public void IOnDestroy();

    public void IOnRestart();

    public void IInit(String path, Activity context, ClassLoader classLoader, PackageInfo packageInfo);
}

至此Activity已经有生命周期了!但是还有个问题,插件的资源如何获取?

插件资源的获取

这里就不卖关子了。直接通过反射调用AssetManager的addAssetPath方法把资源加载到Resource对象中,即可获取插件中的资源。为了后续方便,我们直接继承ContextWrapper类自己实现getAssets和getResources方法即可。代码如下

class PluginContext extends ContextWrapper {

    private AssetManager mAsset;
    private Resources mResources;
    private Theme mTheme;
    private int mThemeResId;
    private ClassLoader mClassLoader;
    private Context mOutContext;

    private AssetManager getSelfAssets(String apkPath) {
        AssetManager instance = null;
        try {
            instance = AssetManager.class.newInstance();
            Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
            addAssetPathMethod.invoke(instance, apkPath);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return instance;
    }

    private Resources getSelfRes(Context ctx, AssetManager selfAsset) {
        DisplayMetrics metrics = ctx.getResources().getDisplayMetrics();
        Configuration con = ctx.getResources().getConfiguration();
        return new Resources(selfAsset, metrics, con);
    }

    private Theme getSelfTheme(Resources selfResources) {
        Theme theme = selfResources.newTheme();
        mThemeResId = getInnerRIdValue("com.android.internal.R.style.Theme");
        theme.applyStyle(mThemeResId, true);
        return theme;
    }

    private int getInnerRIdValue(String rStrnig) {
        int value = -1;
        try {
            int rindex = rStrnig.indexOf(".R.");
            String Rpath = rStrnig.substring(0, rindex + 2);
            int fieldIndex = rStrnig.lastIndexOf(".");
            String fieldName = rStrnig.substring(fieldIndex + 1, rStrnig.length());
            rStrnig = rStrnig.substring(0, fieldIndex);
            String type = rStrnig.substring(rStrnig.lastIndexOf(".") + 1, rStrnig.length());
            String className = Rpath + "$" + type;

            Class<?> cls = Class.forName(className);
            value = cls.getDeclaredField(fieldName).getInt(null);

        } catch (Throwable e) {
            e.printStackTrace();
        }
        return value;
    }

    public PluginContext(Context base, int themeres, String apkPath, ClassLoader classLoader) {
        super(base, themeres);
        mClassLoader = classLoader;
        mAsset = getSelfAssets(apkPath);
        mResources = getSelfRes(base, mAsset);
        mTheme = getSelfTheme(mResources);
        mOutContext = base;
    }

    @Override
    public Resources getResources() {
        return mResources;
    }

    @Override
    public AssetManager getAssets() {
        return mAsset;
    }

    @Override
    public Theme getTheme() {
        return mTheme;
    }
}

至此,插件化的两大难题已经解决。现在插件中的Activity可以启动了!用同样的思路可以完成其他组件的代码编写。

源码下载

PluginDemo

延伸阅读

资源加载和activity生命周期管理

基于Proxy思想的Android插件框架

时间: 2024-08-05 11:16:58

Android基于代理的插件化思路分析的相关文章

Android热修复与插件化实践之路

第1章 class文件与dex文件解析本章通过从java最基本的class文件与android最基本的dex文件进行对比,并不借助IDE去生成及执行class与dex文件,通过讲解class与dex的手动生成,执行, 格式对比,让学生明白二者的相同与不同.1-1 课程项目整体介绍1-2 本章概述1-3 class文件详解上1-4 class文件详解下1-5 dex文件详解上1-6 dex文件详解下 第2章 虚拟机深入讲解本章主要介绍jvm,dvm,art.通过对这三个虚拟机的介绍让学生明白,an

基于AOP的插件化(扩展)方案

在项目迭代开发中经常会遇到对已有功能的改造需求,尽管我们可能已经预留了扩展点,并且尝试通过接口或扩展类完成此类任务.可是,仍然有很多难以预料的场景无法通过上述方式解决.修改原有代码当然能够做到,但是这会增加许多附加成本,回归测试带来大量工作和一些潜在的未知风险.特别是一些极其重要的公共模块,可谓牵一发而动全身,稍有不慎都将引发重大的故障.这里分享一下自己在项目开发中的一点实践,一种基于AOP的插件化(扩展)方案. 假设一个场景: 现有一个可获取人员信息的服务:UserService. publi

Android架构设计之插件化、组件化

如今移动app市场已经是百花齐放,其中有不乏有很多大型公司.巨型公司都是通过app创业发展起来的:app类型更加丰富,有电子商务.有视频.有社交.有工具等等,基本上涵盖了各行各业每个角落,为了更加具有竞争力app不仅功能上有创性,内容也更加多元化,更加饱满,所以出现了巨大的工程.这些工程代码不停添加如果没有一个好的架构所有代码将会强耦合在一起,功能直接也会有很多依赖,那么就会出现很多问题:例如: 1.修改功能困难,牵一发动全身.很多地方如果api写的不好,封装不优雅,那么就会出现改一个地方需要改

Android插件化的兼容性(中):Android P的适配

Android系统的每次版本升级,都会对原有代码进行重构,这就为插件化带来了麻烦. Android P对插件化的影响,主要体现在两方面,一是它重构了H类中Activity相关的逻辑,另一个是它重构了Instrumentation. 3.1 H类的变身 3.1.1 从Message和Handler说起 对于App开发人员而言,Message和Handler是耳熟能详的两个概念.我们简单回顾一下,一条消息是怎么发送和接收的. 首先,在App启动的时候,会创建ActivityThread,这就是主线程

热门前沿知识相关面试问题-android插件化面试问题讲解

插件化由来: 65536/64K[技术层面上]随着代码越来越大,业务逻辑越来繁杂,所以很容易达到一个65536的天花板,其65536指的是整个项目中的方法总数如果达到这个数量时则不无法创建新的方法了,所以基于这个原因插件化就产生了. 功能层面的解耦.维护团队的分离,这也是大势所趋,每个团队会维护一个APK中的不同的业务模块,如果每个业务模块升级都需要对整个APK进行升级,代价实在太大,虽说目前有H5的方式能解决这个问题,但是体验上肯定是没法中Native的APP进行比较的.虽说来自Faceboo

Android插件化开发-hook动态代理

首先,我们阐述为什么android需要插件化: 1:由于业务的增长,app的方法数逐渐达到65535(有人说用于检索方法数的列表大小使用short存储的,其实我看了源码之后并没有发现相关信息,并对此说法产生了怀疑,不过最后找到的结果就是,65535这个限制可能是由于dalvik的bytecode大小限制的,具体的可以查看官方文档). 2:一个模块的变化都要整体编译一次app,维护成本太大了,用插件开发app会好很多 对于以上问题解决方案不少,著名的有h5,hybird,不过这些都没有native

Android Small插件化框架源码分析

Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github.com/wequick/Small 插件化的方案,说到底要解决的核心问题只有三个: 1.1 插件类的加载 这个问题的解决和其它插件化框架的解决方法差不多.Android的类是由DexClassLoader加载的,通过反射可以将插件包动态加载进去.Small的gradle插件生成的是.so包,在初始

android 广播的插件化

------本文转载自 Android插件化原理解析--广播的管理 这一系列的文章实在是写的好! 1, 概述 为了实现Activity的插件化我们付出了相当多的努力:那么Android系统的 其他组件,比如BroadcastReceiver,Service还有ContentProvider,它们又该如何处理呢? 相比Activity,BroadcastReceiver要简单很多--广播的生命周期相当简单:如果希望插件能够支持广播,这意味着什么? 回想一下我们日常开发的时候是如何使用Broadca

插件化知识详细分解及原理 之代理,hook,反射

上一篇我们说了Binder机制,通过aidl的demo和系统源码的对比进行了运行过程的分析,这一篇我们说代理模式及反射,之前说过了,只是为了梳理插件化需要了解的知识点,所以不会特别深的去讲解. 代理模式: 也叫做委托模式,分为静态代理和动态代理.代理模式也是平时比较常用的设计模式之一,代理模式有代码简洁,高扩展性的特性.主要目的就是为访问者提供一个代理,以达到限制某个对象的访问,也就是说想访问一个对象,其实我给你的是一个代理,不让你直接使用我.估计不理解的人会问为什么使用代理模式,他限制了对象的