在做项目代码静态分析的过程中,我们发现,第三方登录的SDK的代码占比在9%左右。从功能上看,这些SDK的主要作用有三个,一是账户登录,二是分享,三是能在微信里发送图片表情。从产品数据了解到,前两个功能用户使用比例不高。
总结起来,就是说,第三方登录的SDK代码占比大,且不常用。因此考虑将这部分代码剥离出输入法的dex文件,实现按需加载。
从广义上来说,按需加载也是插件化的一种。只是,考虑到这部分代码里没有资源文件(只有少量的assets),更新频率极低,且交互简单(单向调用为主),因此,可以忽略一般插件化中的复杂问题(资源文件合并等),做成一个简化的插件化方案。
注:因为使用微信SDK的代码较多,且有在微信中发送图片的功能,暂时不做优化处理。
要解决的问题
要做成按需加载,首先要解决三个问题:
1 如何制作SDK插件?
SDK以jar的形式提供,需要把它转成dex文件。可以使用以下命令:
# 把class文件目录转成jar包
$ jar cvf sdk.jar <sdk_classes_dir>
# 将jar包转成dex文件
$ <SDK_PATH>/<SDK_VERSION>/dx --dex --output sdk.dex sdk.jar
2 SDK的代码如何加载?
SDK被打成dex包,使用DexClassLoader类来加载,原理可以参考Andrioid MultiDex和热修复。
3 主应用和SDK的代码如何交互?
主应用对SDK不可见,因此不能直接访问其代码,能想到的最直接的方式就是反射。输入法和SDK之间是单项调用关系,调用形式也比较简单,用反射方法也能够做到。
本质上,这里要解决的是主应用(host)和插件(plugin)之间的交互问题,这就包括双向调用和功能扩展。因此,我们想用一种更好的方法来解决它。下面就详细说明两种解决方案。
host和plugin交互的实现方案
方案一:反射
- 优点:直观
- 缺点:代码可读性差,且容易出错;代码改动大
方案二: 代理类+接口类+实现类
- 优点:
- 代码可读性好,改动小(跟当前集成方法比)
- 方便实现双向调用
- 缺点:
- 需要添加的类多
- 插件代码中需要加入新的类,打包流程需要改
说明:使用反射方式时,对于SDK提供的API带有接口类参数的case,可以使用Java的动态代理技术,生一个跟接口类对应的代理类对象并传入API中。
结合项目的实际情况对比两种方案,我们认为方案二优于方案一。而且,考虑到以后可能把更多的外围功能改造成插件,那就需要一种扩展性好的得方案。
下面详细讨论方案二的实现细节。
假设Foo.java是SDK中的类,提供了一个func()的API,下面的UML图说明如何用“代理类+接口类+实现类”的方式实现集成SDK。
约定:XXX_Proxy是代理类,XXX_Interface是接口类,XXX_Concrete是实现类。
在host中调用plugin的代码实现如下:
image上边展示的比较简单的情况,实际情况比较复杂,可能API含有枚举类参数,接口类参数,接口类可能还有其它类的参数。解决问题的方法跟上边的差不多,都是加接口类。比如下边这个例子,Foo::func()使用到一个Callback接口类,这样就需要添加一个Callback_Interface,在Foo_Concrete中处理两个接口的转换。
image上图没法说清一个问题,就是Foo_Concrete如何访问到Foo_Interface接口?毕竟它俩不在同一个包中。这个还要看具体情况,看下一节的说明。
代码组织和生成SDK插件
分两种情况进行说明:
1 没有SDK源码(适合集成第三方SDK的情况):
-
调试阶段
可以先把实现类和接口类都放在host中,SDK也以jar的形式引入进来,这样就能直接运行看到调用结果了。
-
打包阶段:
- host
打包前把实现类从host中删掉,并保留其编译后的.class文件。
注意:使用到接口类的时候可能产生匿名对象,因此编译器也会为生成一个对应的匿名类,注意把实现类和匿名类的class文件都保留出来。
-
plugin
把上一步保存的.class文件,放到SDK jar的解压目录下,注意文件路径要跟反射时查找路径一致。再使用上边介绍的生成dex文件的方法来生成。
2 有SDK源码(例如内部项目的子module)
待更新:在做插件化的过程中发现一个更好的组织代码的方法,就是把接口类做成一个module,让host和plugin都依赖它,但是plugin打包时会把它排除掉(原理是debugCompile)。
需要为SDK创建一个module,就是下边的plugin。
-
调试阶段
- host
代码不变,只包括Proxy类和Interface类。
- plugin
需要Concrete类和Interface类,注意Interface类的路径跟在host中一致。
-
打包阶段:
- host
跟普通流程一样。
- plugin
打包时需要删掉Interface类的class文件,否则host加载插件时会报错。
优化后效果
通过剥离SDK,输入法的两个dex文件共减小了1.2M,在art手机上,odex减小3.6M。主要好处是减小输入法应用的存储空间,在6.0.0上,从70M减少到66.5M。在dalvik手机上,优化后的dex文件不计算在应用存储空间内,所以没有明显效果。
优化带来的问题
功能还没开始测试,待补充。
待解决的问题
1 删除插件中Interface类的class文件。有两种方式
- 一是半自动化模式(生成插件后)
- 通过脚本,把dex中多余的文件或目录删掉,再重新生成dex文件。辅助工具有smali/baksmali。这种方式已经基本完成,还需要后期集成到打包系统中。
-
二是通过gradle插件完成(生成插件前)
开发中
2 plugin调用host代码
跟host调用plugin一样,有反射和接口两种实现方式。待补充。
网友评论