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

一、前言:

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

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

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

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

二、功能比较:

(1)DynamicLoadApk
  • 简介:

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

  • 原理:

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

(2)DroidPlugin
  • 简介:

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

  • 原理:

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

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

三、代理:

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

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

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

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

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

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

四、源码模拟

(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的,我们只要在这里动点手脚就可以了。

IActivityManager的实现类
初始化ActivityManagerProxy
创建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源码并劫持它干一些事情,我觉得还是挺有意思的。好了就到这了希望这篇文章能帮助到各位小伙伴。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容