DroidPlugin源码分析插件运行环境初始化

从DroidPlugin的官方文档中我们知道。
2 在AndroidManifest.xml中使用插件的com.morgoo.droidplugin.PluginApplication:
或者在自定义的Application的onCreate()函数中,调用PluginHelper.getInstance().applicationOnCreate(getBaseContext());
在Application的attachBaseContext()函数中,调用
PluginHelper.getInstance().applicationAttachBaseContext(base);
接下来就先分析PluginHelper.applicationOnCreate();
第一步PluginHelper.applicationOnCreate();

    public void applicationOnCreate(final Context baseContext) {
        mContext = baseContext;
        initPlugin(baseContext);
}

这个函数比较简单,只是保存传入的Context对象,然后调用PluginHelper的initPlugin这个函数。
第二步PluginHelper. initPlugin()函数如下:

    private void initPlugin(Context baseContext) {
                fixMiUiLbeSecurity();

                PluginPatchManager.getInstance().init(baseContext);
                PluginProcessManager.installHook(baseContext);

                if (PluginProcessManager.isPluginProcess(baseContext)) {
                    PluginProcessManager.setHookEnable(true);
                } else {
                    PluginProcessManager.setHookEnable(false);
                }
                PluginManager.getInstance().addServiceConnection(PluginHelper.this);
                PluginManager.getInstance().init(baseContext);
} 

A 适配小米系统 fixMiUiLbeSecurity
首先调用fixMiUiLbeSecurity函数如下:

    //解决小米JLB22.0 4.1.1系统自带的小米安全中心(lbe.security.miui)广告拦截组件导致的插件白屏问题
    private void fixMiUiLbeSecurity(){

        //卸载掉LBE安全的ApplicationLoaders.mLoaders钩子
        Class ApplicationLoaders = Class.forName("android.app.ApplicationLoaders");
        Object applicationLoaders = MethodUtils.invokeStaticMethod(ApplicationLoaders, "getDefault");
        Object mLoaders = FieldUtils.readField(applicationLoaders, "mLoaders", true);
        if (mLoaders instanceof HashMap) {
            HashMap oldValue = ((HashMap) mLoaders);
            if ("com.lbe.security.client.ClientContainer$MonitoredLoaderMap".equals(mLoaders.getClass().getName())) {
                HashMap value = new HashMap();
                value.putAll(oldValue);
                FieldUtils.writeField(applicationLoaders, "mLoaders", value, true);
            }
        }

        //卸载掉LBE安全的ActivityThread.mPackages钩子
        Object currentActivityThread = ActivityThreadCompat.currentActivityThread();
        Object mPackages = FieldUtils.readField(currentActivityThread, "mPackages", true);
        if (mPackages instanceof HashMap) {
            HashMap oldValue = ((HashMap) mPackages);
            if ("com.lbe.security.client.ClientContainer$MonitoredPackageMap".equals(mPackages.getClass().getName())) {
                HashMap value = new HashMap();
                value.putAll(oldValue);
                FieldUtils.writeField(currentActivityThread, "mPackages", value, true);
            }
        }

        //当前已经处在主线程消息队列中的所有消息,找出lbe消息并remove之
        if (Looper.getMainLooper() == Looper.myLooper()) {
         final MessageQueue queue = Looper.myQueue();
          Object mMessages = FieldUtils.readField(queue, "mMessages", true);
                if (mMessages instanceof Message) {
                    findLbeMessageAndRemoveIt((Message) mMessages);
                }
    }

其实这个函数的作用注释已经说得非常清楚了,小米某一款手机自带了一款Lbe的安全软件,
而这个软件内部自定义MonitoredLoaderMap类应该是继承自Map类,替换了ApplicationLoaders类中的原本的成员变量mLoaders来管理Android系统中的应用程序的类加载器。LoadedApk内一个成员变量mClassLoader就是通过ApplicationLoaders来创建的。这里卸载掉钩子就是把mLoaders替换成原生的HashMap对象。
另外一个类MonitoredPackageMap替换了ActivityThread中原本的mPackages对象,mPackages是HashMap类对象以应用报名为key,LoadedApk对象实例为Value。这里卸掉钩子也就是把mPackages对象替换成原生的HashMap对象。
在Activity启动过程中,我们的插件Activity实例化,就是通过预先把插件Apk包名对应的LoadedApk对象保存到ActivityThread的成员变量mPackages中,顺便用PluginClassLoader 替换LoadedApk中的mClassLoader.这样PluginClassLoader就可以加载插件中的类了。(这里会在插件Activity启动中详细分析)
也许是因为影响到了插件Activity的启动过程,所以需要处理一下。
最后把主线程里面所有lbe的消息都删除。

B 插件异常处理PluginPatchManager.getInstance().init()

    public void init(Context context){
        mContext = context;
    }

这个初始化就是把context保存到PluginPatchManager的成员变量mContext中,这里保存Context对象主要就是为了延迟启动插件Activity。这个类主要是在启动插件Activity过程中,会调用PluginPatchManager的函数canStartPluginActivity判断PluginManagerService这个插件管理服务是否已经启动,如果没有启动则会调用PluginPatchManager的函数startPluginActivity延迟启动插件Activity等待PluginManagerService启动完成。后面会在Activity相关文章中分析。

C 初始化Hook系统插件需要用到的相关服务
PluginProcessManager.installHook(baseContext);
这个函数主要是调用HookFactory.getInstance().installHook(hostContext, null); Hook系统相关服务。(Hook的过程稍后会详细分析)

D 宿主进程关闭hook开关等待PluginManagerService启动后在打开

      if (PluginProcessManager.isPluginProcess(baseContext)) {
           PluginProcessManager.setHookEnable(true);
       } else {
           PluginProcessManager.setHookEnable(false);
      }

通过PluginProcessManager.isPluginProcess判断,如果是插件进程则打开Hook开关,如果是宿主进程暂时关闭Hook开关。它会等到PluginManagerService启动完成后,再打开Hook开关。

PluginProcessManager.isPluginProcess函数如下:
    public static final boolean isPluginProcess(Context context) {
        String currentProcessName = getCurrentProcessName(context);
        if (TextUtils.equals(currentProcessName, context.getPackageName()))
            return false;

        initProcessList(context);
        return !sProcessList.contains(currentProcessName);
    }

首先获取当前进程的名字再判断是否和宿主进程包名相同,如果相同说明不是插件进程。如果不同还需要继续判断,是否是宿主应用开出的其他进程(除去DroidPlugin为插件预定义了N个进程)initProcessList就会把宿主应用开出的其他进程保存在sProcessList。
在这里为什么如果是宿主进程需要先关闭Hook?因为Hook主要是为了瞒过系统保证插件Apk在未安装的情况下也能正常运行, PluginManagerService还没有启动的时候,已经安装的插件Apk是还没有装载进来的。另外,如果是在插件进程中,那么Hook完成之后可以直接打开Hook开关,因为PluginManagerService是运行在宿主进程中的。

E 注册服务连接Callback启动PluginManagerService
PluginManager.getInstance().addServiceConnection(PluginHelper.this);
PluginManager.getInstance().init(baseContext);
首先注册服务连接,等待服务启动完成后,调用PluginProcessManager.setHookEnable(true)函数打开Hook开关。
然后调用PluginManager的init函数,这个函数主要就是启动PluginManagerService。

F 启动PluginManagerService初始化IPluginManagerImpl对象。

PluginManagerService的onCreate函数。
    public void onCreate() {
        keepAlive();
        mPluginPackageManager = new IPluginManagerImpl(this);
        mPluginPackageManager.onCreate();
}

首先提升service所在进程优先级。
创建IPluginManagerImpl对象, IPluginManagerImpl的构造函数比较简单,只是创建了一个MyActivityManagerService 对象,他主要是用于插件进程管理的。
接着调用IPluginManagerImpl的onCreate函数。
onCreate函数中主要是启动一个线程,运行onCreateInner函数。

G 加载已经安装的插件,获取宿主进程申明的权限

private void onCreateInner() {

        loadAllPlugin(mContext);
        loadHostRequestedPermission();

        mHasLoadedOk.set(true);
        synchronized (mLock) {
           mLock.notifyAll();
        }
}

函数loadAllPlugin()主要是从/data/data/com.HOST.PACKAGE/Plugin目录下面找到已经安装的插件包,创建PluginPackageParser对象并缓存,然后缓存签名。这些基本都在DroidPlugin源码分析安装和卸载中有分析。
函数loadHostRequestedPermission()主要是搜集宿主进程的申明的权限信息。

H 创建并保存IPluginManagerImpl Proxy对象,创建线程,等待IPluginManagerImpl初始化完成后,分发服务连接成功监听,注册服务死亡回调。

    public void onServiceConnected(final ComponentName componentName, final IBinder iBinder) {
        mPluginManager = IPluginManager.Stub.asInterface(iBinder);
        new Thread() {
            @Override
            public void run() {
                    mPluginManager.waitForReady();
                    mPluginManager.registerApplicationCallback(new IApplicationCallback.Stub() {
                        @Override
                        public Bundle onCallback(Bundle extra) throws RemoteException {
                            return extra;
                        }
                    });
                    Iterator<WeakReference<ServiceConnection>> iterator = sServiceConnection.iterator();
                    while (iterator.hasNext()) {
                        WeakReference<ServiceConnection> wsc = iterator.next();
                        ServiceConnection sc = wsc != null ? wsc.get() : null;
                        if (sc != null) {
                            sc.onServiceConnected(componentName, iBinder);
                        } else {
                            iterator.remove();
                        }
                    }

                    mPluginManager.asBinder().linkToDeath(new IBinder.DeathRecipient() {
                        @Override
                        public void binderDied() {
                            onServiceDisconnected(componentName);
                        }
                    }, 0);
                }
            }
        }.start();
}

函数首先保存IPluginManagerImpl Proxy 对象到mPluginManger中,
然后调用mPluginManager.waitForReady()查询服务是否初始化完成,如果没有完成则等待。
完成后,则分发这服务连接成功监听。在E步中收到服务连接成功回调,设置hook开关为True。
最后注册Binder死亡毁掉,当IPluginManagerImpl本地binder对象销毁时,我们会收到Binder死亡通知,然后在通知中重新启动PluginManagerService。

第三步:在Application的attachBaseContext()函数中,调用

PluginHelper.getInstance().applicationAttachBaseContext(base)
    public void applicationAttachBaseContext(Context baseContext) {
        MyCrashHandler.getInstance().register(baseContext);
}

这个函数主要是注册一个UncaughtExceptionHandler在应用异常退出时,保存Crash异常信息。
到此插件运行环境初始化过程结束,总结一下:

  • 1 适配小米手机预装LBE,替换ApplicationLoaders成员变量mLoaders,替换ActivityThread成员变量mPackages,然后从主线程队列里面移除所有lbe相关的消息。
  • 2 初始化插件异常处理和初始化Hook系统相关服务。如果是在宿主进程中先关闭Hook开关,如果不是则打开Hook开关。
  • 3 注册PluginManagerService的服务连接监听对象,然后启动PluginManagerService服务,在服务中创建MyActivityManagerService的对象,管理所有的插件进程。然后装载所有已经安装的插件Apk。服务启动完成以后,服务监听收到回调,打开Hook开关。

由此我们可以知道PluginManagerService主要职责,装载已经安装的插件Apk,插件Apk的安装和卸载,插件Apk信息的查询,以及插件进程管理等。
下一篇博客我们再来分析,Hook初始化过程。

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。

字母雨的实现 - 2016-07-23 14:07:06

有段时间没写博文了,前段时间比较忙,这几天闲下来,想着写点东西,脑袋一下就闪过以前学习Android的时候见到的别人实现的黑客帝国的字母雨效果,当时对于小菜鸟的自己,那叫一个膜拜啊,时隔几年,自己实现一下,算是对以前的自己一个交代吧。 先看效果: 一、实现原理 在实现过程中,主要考虑整个界面由若干个字母组成的子母线条组成,这样的话把固定数量的字母封装成一个字母线条,而每个字母又封装成一个对象,这样的话,就形成了如下组成效果: 字母对象--》字母线条对象--》界面效果 每个字母都应该知道自己的位置坐标,自己

DroidPlugin源码分析Hook过程 - 2016-07-23 14:07:06

插件运行环境初始化过程中我们知道,Hook的初始化是在PluginHelper的initPlugin函数中通过调用PluginProcessManager.installHook来实现的。而在分析DroidPlugin Hook过程之前需要先简单了解一下Java的动态代理。 Java动态代理与之相关的一个类Proxy,一个接口InvocationHandler,一个函数invoke他们之间的关系。就通过DroidPlugin 的BinderHook类的部分代码来解释一下他们的关系; abstract cl
大概半年之前,看过鸿洋大神的一篇博客 Android 自定义控件玩转字体变色 打造炫酷ViewPager指示器 他说大概想了32秒就知道了实现思路,这深深的刺痛了我。最近又看了一遍,决定做点什么 我要自定义的控件是一个盖世英雄, 它不仅仅是一个 Loading控件 ,同时还支持 进度条 (ProgressBar) 功能 。 它会在你需要的时候出现, 它支持 left , top , right , bottom 四个方向加载(变色),最重要的是,它可以是 文字 ,也可以是 图片 ,能够满足开发者一切需求。
首先为权限: uses-permission android:name="android.permission.INTERNET" / uses-permission android:name="com.android.vending.BILLING" / uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" / uses-permission android:name="android.permission.AC
Day01 html与css基础入门 1.html的常见标签和实战 1.1 a标签 a href=#> 1.2 img标签 img src="plmm.jpg" width="100px" height="200px" alt="图片信息丢失!"/// alt属性的值表示当图片找不到时显示的文字信息 1.3 列表标签 ol type="I" start="1" li我是天才1号/li li我是天才2号/li li我是天才3号/li li我是天才4号/li/olul type="circle" li我是逗逼

EventBus 源码分析 - 2016-07-23 14:07:10

0. 前言 EventBus 是一款针对Android优化的发布/订阅事件总线。主要功能是替代 Intent , Handler , BroadCast 在 Fragment , Activity , Service ,线程之间传递消息。优点是开销小,代码更优雅,以及将发送者和接收者解耦。此文将对最新的 EventBus 3.0 的源码进行简要的分析。 1. 用法回顾 EventBus 3.0 的用法较之前的版本有所变化,它使用了最近较为流行的注解形式取代以前的 onEvent 开头作为方法名,但使用步骤

Qt之资源系统 - 2016-07-23 14:07:10

简述 Qt 的资源系统用于存储应用程序的可执行二进制文件,它采用平台无关的机制。当你的程序总需要这样的一系列文件(图标、翻译文件等)并且不想冒丢失某些文件的风险时,这就显得十分有用。 资源系统基于 qmake、rcc(Qt 资源编译器) 和 QFile 之间的紧密合作。 简述 资源集合文件qrc 外部二进制资源 内编译资源 压缩 在程序中使用资源 在库中使用资源 更多参考 资源集合文件(.qrc) 与程序相关的资源在被指定在一个 .qrc 文件中,其基于 XML 的文件格式列出了磁盘上的文件,可以为它们指
一.前提条件 1.纯熟扎实的语言基础   如果你学java,却对反射、泛型、注解一直半解,还是不要去读什么框架了,回去把java基础打扎实反而对你自身更有益。 2.UML能力   在软件工程中,UML在软件的不同生命周期阶段扮演着非常重要的角色,没有好的UML水平,面对大型的项目源码会束手无策。 3.对业务的理解   如果你要阅读的项目业务性比较强,事先对业务有一定的了解是必须的。 4.设计模式、重构的掌握   编程语言什么的没什么好说。着重提一个:设计模式由于Android源代码用到各种各样的设计模式,
在上一篇博文《 LTE下行物理层传输机制(7)-DCI2格式和预编码矩阵的选择 》中已经提到,如果当前UE的传输模式是TM4,且可以执行空分复用(一个PDSCH信道传输2个TB块),那么需要采用DCI2格式来承载控制信息域,使用的预编码矩阵需要参考UE反馈的PMI值,因此属于闭环性质的空分复用。相应的,LTE系统中也有一种开环的空分复用: 如果当前UE的传输模式是TM3,且可以执行空分复用,那么此时PDCCH需要采用DCI2A格式发送,这时的空分复用就属于开环性质的空分复用,不需要参考UE反馈的PMI值。
好久没写android的博客,最近在做一个android的项目,里面用到我们经常用的一个控件就是对话框,大家都知道android自带的对话框是很丑的,android5.x之后除外.所以就出现了自定义view,自己定义美观的对话框.好我们就来自定义对话框. 整体思路:定义一个类然后去继承Dialog类,然后重写相应的构造器方法.大家都知道一般的对话框的创建过程都是来一个AlertDialog.Builder对象,然后使用一些set方法来设置标题内容以及设置一些自定义的view和点击的Button以及相应的点