[造轮子]一个关于IOC初始化的失败脑洞

问题

所有IOC系统,都不可避免的要进行实现的注册,包括很多初始化相关的事情。在Android上,随便一个多module的App,多多少少都有相同的问题。
Android冷启动App,IOC系统启动时,基本都要反射来突破Module间依赖的限制(如果这个能解决,也就不需要IOC了)。此时,性能一定会有一些问题,而且理解上不太容易描述清楚。

解决方法

提高性能的方法有不少种:

  1. 每个Module中的所有内容都聚合到一起,构成一个大的初始化器。则最多只需要反射n(n = Module数)次,即可完成初始化。
  2. 整个Project聚合到一起。不论如何,总归有一个Module(最终输出APK)会依赖所有子Module。这样,可以以apt的方法生成出一个大的聚合类,只需反射一次即可完成初始化。
  3. 让子Module主动注册自己,这样理论上能解决反射问题。

方法中的细节

第一种方法

其实就是大家用的方案。并没有什么特别的,稳定无公害,只是Module个数收到了一些限制。

第二种方法

方案具体来说是这样的,App分为三层(以解决依赖问题):

  • 最下层是Plugin interface的声明层,这一层是PluginManager所在的层级。运行时反射最上层生成的类来初始化。
  • 中间层是Plugin实现所在层,可以是任意多的module,依赖最下层。在编译期,用apt的方法,输出本Module中共插件信息的config文件。
  • 最上层依赖一切。在编译期,仍然是apt的方法,读取每个中间层生成的config文件,生成一个初始化类,所有Impl都可以在这里依赖,正常new出来。

第三种方法

并没听说,有哪个大厂用这个办法。这里有一个catch,Java的运行周期中,并没有一个能既不反射、又不引用类就能loadClass的方式,连Class都没load,什么都玩不了。
不过,这里是Android,实际上是可以通过AndroidManifest来做一些不用loadClass就注册上的组件。用来初始化,Receiver + Action的方式就很合适,在任何Class都不在的情况下,系统会找到Receiver,调用onReceive。那么onReceive中做主动的注册就很好了。沿着这个思路,研究了两天,发现我想多了。
这个办法有一些问题:

  • Receiver要做一套permission来保证Action不会唤起外部App(监听大厂App绝对是保活利器)
  • Receiver的执行都被post到了主线程中,所以并不能保证顺序、不能保证及时性
  • Receiver里都是垃圾代码,第二种方法中的apt生成可以搞定这一点
  • Receiver需要注册,这个是口头约定。最好是能够用生成的办法去自动修改AndroidManifest。这个就是症结,暂时无解

为什么Android原生的Manifest要手写

不知道有谁想过没,为什么Android原生的Manifest要手写?Activity在编译期加几个Annotation,apt做注册不好吗?答案是,做不到!
想这样做,一定要求apt发生在resource处理之前。前提条件是不成立的!!!gradle中的processManifest是发生在generateSource之前的。所以,apt并不能有效的修改Manifest。
上代码:

task genManifest(type: Exec) {
    doLast {
        System.out.println("gradle logging after compile")
        project.android.libraryVariants.all { variant ->
            variant.outputs.each { output ->
            //这里用来读apt生成的东西
                File file = new File(project.projectDir.absolutePath + "/build/generated/source/apt/" + variant.getBuildType().getName() + "/test_file")
                System.out.println("gradle logging file " + file.absolutePath)
                if (file.exists()) {
                    System.out.println("gradle logging exist")
                }
            }
        }
    }
}
afterEvaluate {
    // 可以保证genManifest执行
    project.tasks.findByName('assembleDebug').dependsOn genManifest
    genManifest.mustRunAfter ':generateDebugSources'
}

但是,根据stackOverflow,修改后的manifest要加到resourceSet中,而这个早已在processResource时用过了…

结论

要么用一坨反射,受不了就做一下apt时的merge,并没有办法做成Receiver形式,看起来很美的脑洞而已。

后续解决

使用了一个很简单的方案:javassist在合包的时候修改二进制。代码可以参考autoregister只是把ASM改成javassist(容易读一些)。比较好的拆分是:

  • 有一个Config类,负责被javassist插入代码。这样安排的目的在于:
    • 降低debug断点难度,因为是个纯数据类,所以基本不需要断点
    • 解除掉类间依赖(在做首个dex方法数优化的时候,非常有用)
  • 一个Manager类,负责管理所有的IOC实现。其中对于Config的引用是靠反射的,这样Manager在编译期是不依赖Config的,不需要打入同一个dex

猜你喜欢

转载自blog.csdn.net/pouloghost/article/details/79322767
今日推荐