插件化框架DynamicLoadApk与DroidPlugin中代理解析

一、前言:

随着APP到开发到后期众多功能模块的加入,方法数越来越多面临着超出65535的可能。虽然谷歌后来提供了multidex分包功能,但它还是有一些局限性比如分包过大响应慢、占用过大内存、低版本兼容等问题。

频繁的增加新模块更新每次都要重新下载APP、出现紧急BUG需要修复,在这种时候使用插件化开发就很有必要了。

在去年阿里的云栖大会上,阿里宣布他们使用的插件化框架Atlas将于今年年初开源。最近我在看关于插件开发的技术,还想着Atlas什么时候开源,没过几天就发现Atlas框架在这个月13号开源了。Atlas使用入门教程

上面都是题外话,本文主要是对以下两款框架中代理应用解析:

  • 百度员工开源的dynamic-load-apk
  • 360团队开源的DroidPlugin

二、功能比较:

(1)DynamicLoadApk
  • 简介:

插件不依赖宿主,但插件必须遵守一定的规则。
提供了宿主和插件交互的方式。
兼容性好很大程度是通过代理的方式进行插件化。

  • 原理:

1、通过反射调用AssetManager的addAssetPath方法,将一个插件apk中的资源加载到AssetManager中,然后再通过AssetManager来创建一个新的Resources对象,就可以通过这个Resources对象来访问插件apk中的资源了。
2、在宿主Manifest中预注册代理组件Activity,当启动插件组件时首先启动一个代理组件,然后通过这个代理组件来构建、启动插件组件。
插件里的activity其实是通过反射初始化的普通对象是没有生命周期的,通过代理activity去调用插件activity实现的生命周期方法。

(2)DroidPlugin
  • 简介:

宿主和插件完全隔离。
插件不依赖宿主可以独立运行。
接入简单,插件不需要修改。

  • 原理:

1、基于动态代理的Hook,劫持了系统的大部分与系统服务进程通讯的方法,欺骗系统以为只有一个apk在运行,瞒过插件让其认为自己已经安装。
2、基于Android的多个apk可以运行在同一个进程的原理。
3、需要预注册Activity等组件实现免注册。

通过以上简单的介绍,可以很清楚的发现它们的核心实现都是基于代理来完成核心功能的。

三、代理:

代理主要分为动态代理与静态代理,动态代理与静态代理相比较,最大的区别是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。
DynamicLoadApk通过静态代理技术伪装成Activity,DroidPlugin通过动态代理技术Hook劫持了系统管理。

以下我以自己的理解以故事的形式来描述静态代理与动态代理。

静态代理以卖香烟为例:
  • 小明去便利店买烟,店长让店小二去取烟然后偷偷的取出假烟。在店小二把烟交给店长的时候店长用他娴熟的技法掉了包并交给了小明,然而小明并不知道自己买了假烟反而欢喜的拿回家了。
  • 这个故事里小明是客户、便利店是代理接口提供卖烟的功能、店长是代理通过操作店小二并做一些手脚实现卖烟功能、店小二是委托类他负责去仓库取烟。

这个故事里还有个问题,小明再去这个店买假水果的时候,店长必须要重新学习掉包水果的技能很是麻烦。在这个时候动态代理站出来了。

动态代理以卖香烟为例:
  • 小明活到了22世纪这时候都是无人售卖店,他又去买烟了。店长嫌每次学技术太麻烦为了快速赚钱研发了掉包处理器,小明投币到自动售卖机后掉包处理器全自动掉包后交给了小明,最后小明又欢乐的回家了。
  • 这个故事里小明还是客户、无人售卖店是代理接口、掉包处理器是代理(只要监听到有人调用售卖机买东西自动化掉包)、自动售卖机是委托类它负责取货。

文字说明也描述完了我想应该可以帮助小伙伴更容易理解代理的含义,在第四节我将以代码的方式更直观的展示代理的实际应用。

四、源码模拟

(1)DynamicLoadApk

通过查看了DynamicLoadApk的源码,我以自己的理解在这里写了简化版的代理Activity的代码,了解下面代码可以更容易理解DynamicLoadApk。

/**
 * 客户类 
 */
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 静态代理 模拟跳转到指定的插件Activity
        Intent dlIntent = new Intent();
        dlIntent.putExtra(DLProxyActivity.CLASS_NAME, PluginActivity.class.getName());
        DLPluginManager.startPluginActivity(this, dlIntent);
    }
}
/**
 * 静态代理类工厂 
 */
public class DLPluginManager {
    public static void startPluginActivity(Context context, Intent dlIntent) {
        //封装跳转真正的Activity
        dlIntent.setClass(context, DLProxyActivity.class);
        context.startActivity(dlIntent);
    }
}
/**
 * 代理接口 实现模拟Activity的生命周期
 */
public interface DLPlugin {
    public void onStart();
    public void onResume();
    public void onStop();
    public void onDestroy();
    public void onCreate(Bundle savedInstanceState);
    public void attach(Activity proxyActivity);
}
/**
 * 静态代理类 真正启动的Activity
 */
public class DLProxyActivity extends Activity{

    protected DLPlugin mRemoteActivity;
    public static final String CLASS_NAME = "className";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        String mClass = getIntent().getStringExtra(DLProxyActivity.CLASS_NAME);
        try {
            // 通过反射创建委托类
            Class localClass = getClassLoader().loadClass(mClass);
            Constructor localConstructor = localClass.getConstructor(new Class[] {});
            Object instance = localConstructor.newInstance(new Object[] {});
            mRemoteActivity = (DLPlugin) instance;
            // 绑定委托类
            mRemoteActivity.attach(this);
            mRemoteActivity.onCreate(new Bundle());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 关联生命周期
     */
    @Override
    protected void onStart() {
        //.....预处理
        mRemoteActivity.onStart();
        //.....事后处理
        super.onStart();
    }

    @Override
    protected void onResume() {
        mRemoteActivity.onResume();
        super.onResume();
    }

    @Override
    protected void onStop() {
        mRemoteActivity.onStop();
        super.onStop();
    }
}
/**
 * 委托类基类,对代理类功能的封装
 */
public abstract class DLBasePluginActivity extends Activity implements DLPlugin {

    // 代理activity
    protected Activity mProxyActivity;
    protected Activity that;
    
    @Override
    public void attach(Activity proxyActivity) {
        mProxyActivity = (Activity) proxyActivity;
        that = mProxyActivity;
    }
    
    @Override
    public void setContentView(int layoutResID) {
        mProxyActivity.setContentView(layoutResID);
    }
    
    @Override
    public View findViewById(int id) {
        return mProxyActivity.findViewById(id);    
    }
    
    public void startPluginActivity(Intent dlIntent) {
        DLPluginManager.startPluginActivity(that, dlIntent);
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {}
    
    @Override
    public void onStart() {}
    
    @Override
    public void onResume() {}
    
    @Override
    public void onStop() {}
    
    @Override
    public void onDestroy() {}
}
/**
 * 委托类,具体处理业务。
 * 其实就是一个假的Activity并没有真正的生命周期,每个方法的调用都是通过代理Activity来实现的。
 */
public class PluginActivity extends DLBasePluginActivity{
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main2);
        findViewById(R.id.click).setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                Toast.makeText(that, "didid", 0).show();
            }
        });
    }
}

以上就是DynamicLoadApk启动插件Activity的简化版流程,想要更深入的理解可以查看DynamicLoadApk的源码。

(2)DroidPlugin

DroidPlugin更偏向与framework层,要对Android源代码有一定的理解。
我这里推荐一个在线查看Android源码的地址:http://www.grepcode.com/

通过查看源码可以看到启动Activity的时候,是通过ActivityManagerNative类里面的ActivityManagerProxy类来启动Activity的,我们只要在这里动点手脚就可以了。

插件化框架DynamicLoadApk与DroidPlugin中代理解析_第1张图片
IActivityManager的实现类
插件化框架DynamicLoadApk与DroidPlugin中代理解析_第2张图片
初始化ActivityManagerProxy
插件化框架DynamicLoadApk与DroidPlugin中代理解析_第3张图片
创建ActivityManagerProxy

我们只要劫持了gDefault就可以做我们想要做的事情了。

以启动Activity为例:
/**
 * 客户类 
 */
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 动态代理 以4.x以上版本为例
        try {
            Class activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
            // 找到Hook点 通过反射获取gDefault字段
            Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
            // gDefault是私有的 需要暴利反射
            gDefaultField.setAccessible(true);
            // 因为是静态方法所以传入null就可以了
            Object gDefault = gDefaultField.get(null);
            // gDefault是一个 android.util.Singleton对象
            Class singleton = Class.forName("android.util.Singleton");
            // 里面有一个成员变量IActivityManager mInstance 反射获取它
            Field mInstanceField = singleton.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            // 获取到IActivityManager对象
            Object rawIActivityManager = mInstanceField.get(gDefault);
            // 创建动态代理Hook
            ActivityManagerHook hooks = new ActivityManagerHook();
            Class iActivityManagerInterface = Class.forName("android.app.IActivityManager");
            // 传入委托类、委托类名
            Object proxy = hooks.newProxyInstance(rawIActivityManager, iActivityManagerInterface);
            // 替换成我们的代理
            mInstanceField.set(gDefault, proxy);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

/**
 * 动态代理IActivityManager
 */
public class ActivityManagerHook implements InvocationHandler {

    private Object mHookedObject;

    /**
     * @param hookedObject 委托类
     * @param clazz 代理接口
     * @return 代理
     */
    public Object newProxyInstance(Object hookedObject, Class clazz){
        this.mHookedObject = hookedObject;
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class[] { clazz }, this); 
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // 如果调用了startActivity方法 我们劫持传过来的Intent替换成我想要跳转的Activity
        if("startActivity".equals(method.getName()) && args[2] instanceof Intent){
            Intent i = new Intent();
            i.setClassName("com.example.plugindemo", "com.example.plugindemo.dynamicplugin.HomeActivity");
            args[2] = i;
        }
        Object result = method.invoke(mHookedObject, args);
        return result;
    }
}

五、结尾:

通过查看Android源码并劫持它干一些事情,我觉得还是挺有意思的。好了就到这了希望这篇文章能帮助到各位小伙伴。

你可能感兴趣的:(插件化框架DynamicLoadApk与DroidPlugin中代理解析)