前言
前面写了一篇关于自己开发的一个基于 APT 注解的用于 RecyclerView 复杂楼层的开源框架, 框架的原理比较简单, 通过注解, 在编译期会生成一个 ComponentRule.java 的文件, 然后建立一个映射关系. 使用方式简单介绍一下:
1. 绑定布局文件
- @ComponentType(
- value = ComponentId.SIMPLE,
- layout = R.layout.single_text
- )
- public class SimpleVH extends Component<SimpleModel> {
- private TextView tvList;
- public SimpleVH(Context context, View itemView) {
- super(context, itemView);
- tvList = itemView.findViewById(R.id.tv_simple);
- }
- @Override
- public void onBind(int pos, SimpleModel item) {
- tvList.setText(item.name);
- }
- }
2. 绑定 Model
- @BindType(ComponentId.SIMPLE)
- public class SimpleModel {
- public String name;
- public SimpleModel(String name) {
- this.name = name;
- }
- }
3. 这样在编译的时候就会生成一个 ComponentRule.java 文件, 建立映射关系. 文件的内容大概如下:
- public class ComponentRule implements IComponentRule {
- public static final SparseArray<ViewInfo> WIDGET_TYPE;
- public static final Map<Class<?>, SparseArray<ViewInfo>> ATTACH_TYPE;
- public static final Map<Class<?>, Integer> MODEL_TYPE;
- static {
- WIDGET_TYPE = new SparseArray<>();
- MODEL_TYPE = new HashMap();
- ATTACH_TYPE = new HashMap<>();
- putWidget(2,new ViewInfo(2,
- com.study.xuan.emvp.vh.ImageViewHolder.class,2131296309,1, com.study.xuan.emvp.presenter.Contract.ImagePresenter.class));
- putWidget(8,new ViewInfo(8,
- com.study.xuan.emvp.vh.ImgVH.class,-1,2,Android.widget.ImageView.class, null));
- ......
- putModel(com.study.xuan.emvp.model.Text.class,0);
- }
- }
问题描述
考虑到目前 RecyclerView 的使用率, 一个大型的项目定义的 ViewHolder 数量可能达到上千个, 考虑到如下几个问题:
1. 项目的编译速度影响
2. 是否会有其他问题
所以这里希望模拟创建 1w 个 ViewHolder 类, 使用 @ComponentType 注解, 所以用 Python 写了一个脚本.
- import os
- def createViewHolder(content,fileName):
- path = '/Users/xuan/Projects/EMvp/app/src/main/java/com/study/xuan/emvp/python'
- if not os.path.exists(path):
- os.makedirs(path)
- name = fileName + '.java'
- file = open(name,'w');
- file.write(content)
- file.close()
- print ('ok')
- contentCode = "package com.study.xuan.emvp.python;\n" \
- "import android.content.Context;\n" \
- "import android.view.View;\n" \
- "import android.widget.TextView;\n" \
- "import com.xuan.annotation.ComponentType;\n" \
- "import com.xuan.eapi.component.Component;\n" \
- "import com.study.xuan.emvp.model.Text;\n" \
- "@ComponentType(\n" \
- "value = %s,\n" \
- "view = TextView.class,\n" \
- "attach = Text.class" \
- ")\n" \
- "public class PyThonVH%s extends Component {\n" \
- "public PyThonVH%s(Context context, View itemView) {\n" \
- "super(context, itemView);\n" \
- "}\n" \
- "@Override\n" \
- "public void onBind(int pos, Object item) {\n" \
- "}" \
- "}"
- fileName = 'PyThonVH%s'
- for i in range(1,2000):
- createViewHolder(contentCode%((100+i),i,i),fileName%i)
代码也很基础, 在当前工程的一个目录下, 利用 for 循环, 创建 Java 文件, 文件名就是 PyThonVH1,PyThonVH2,PyThonVH3,PyThonVH4..., 注解就使用最基础的注解, 为了方式 ComponentId 冲突, 这里利用了框架本身提供的多人协作的解决方式, attach 到一个 Model 上, 然后 CompoentId 为 1,2,3,4...
代码写完了, 脚本一运行, 成功创建的 1w 个类
接下来开始验证问题, 当 build 的时候, 编译期报了一个意想不到的异常
代码过长, 没有看错, 还是第一次遇到这样的异常信息, 通过 Google 查询, 得知
JVM 规范里对 Class 文件的规定里有写到每个方法的字节码最多只能有 65535 字节
其实原理和我们经常遇到的 64K 异常一样, 只不过这会不是方法数量超过导致的, 而是方法体大小导致的. 所以解决方式其实也是对应的, 拆分
- if (commonTypeWidget.size() <LINE_LIMIT) {
- // 未超限, 不用分割
- writer.write(writeWidget(0, commonTypeWidget.size()));
- } else {
- // 分割方法, 防止 too large code 异常
- double splitNum = Math.ceil(commonTypeWidget.size() / LINE_LIMIT);
- for (int i = 0; i < splitNum; i++) {
- int start = (int) (i * LINE_LIMIT);
- int end;
- if (i == splitNum - 1) {
- end = commonTypeWidget.size();
- } else {
- end = (int) ((i + 1) * LINE_LIMIT);
- }
- // 保存拆分的方法
- splitMethods.add(String.format(FileCreator.COMMON_METHOD_T, i, writeWidget(start,
- end)));
- writer.write(String.format(FileCreator.COMMON_METHOD_INVOKE, i));
- }
- }
解决方式的核心代码其实就在上面, 对映射表的大小和定义的方法体大小 (500) 比较, 如果超过限制, 则进行分割, 分别查分到 splitAttachMethodStep%s()方法内. 最后编译的成的文件就变成如下:
- static {
- WIDGET_TYPE = new SparseArray<>();
- MODEL_TYPE = new HashMap();
- ATTACH_TYPE = new HashMap<>();
- ...
- splitAttachMethodStep0();
- splitAttachMethodStep1();
- splitAttachMethodStep2();
- splitAttachMethodStep3();
- splitAttachMethodStep4();
- splitAttachMethodStep5();
- splitAttachMethodStep6();
- splitAttachMethodStep7();
- splitAttachMethodStep8();
- splitAttachMethodStep9();
- splitAttachMethodStep10();
- splitAttachMethodStep11();
- splitAttachMethodStep12();
- splitAttachMethodStep13();
- splitAttachMethodStep14();
- splitAttachMethodStep15();
- splitAttachMethodStep16();
- splitAttachMethodStep17();
- splitAttachMethodStep18();
- splitAttachMethodStep19();
- splitAttachMethodStep20();
- putModel(com.study.xuan.emvp.activity.product.Product.class,0);
- putModel(com.study.xuan.emvp.activity.common.SimpleModel.class,9);
- putModel(com.study.xuan.emvp.model.Text.class,0);
- }
当 1w 个类的时候, 编译的速度影响也不是特别的大, 最终的编译时间大是 20s 左右
总结
本篇博客主要讲解了自己写的开源框架时遇到的一个比较有意思的问题, 当然这个问题对于一个稳定的框架是必须要考虑的. 这里再放上框架源码地址吧, 框架支持组件化工程, 适合 RecyclerView 简单或者复杂的楼层样式开发模式, 支持多人多页面楼层打通, 具有很多的拓展 API, 欢迎大家提 issue 讨论~
项目地址: https://github.com/DrownCoder/EMvp
来源: https://juejin.im/post/5bf9284de51d452fd80f08e4