前言

掌阅出品了X2C 框架,听说可以加快性能。喜欢研究源码的我,肯定要来看下是怎么回事。

作为一个开发,应该不屑于只会使用开源框架。

OK,来尝试下。

项目地址:

https://github.com/TomasYu/X2C

原理分析:

X2C 是把Xml 文件,翻译成Java文件,减少系统利用LayoutInflate 去解析xml 的过程。

有两个技术要点:

  1. 什么时候解析xml?
  2. 怎么生成Java 文件?

对于什么时候解析XML

关键在于下面这行:

        annotationProcessor project(':x2c-apt')
implementation project(':x2c-lib')

这里指定了annotationProcessor ,也就是注解编译处理器。不了解的同学可以百度下java APT 技术。

javac 编译的时候,会调用指定的这个处理器,并把注解都传给你。这时候你就可以做一些事情了。

比如:解析xml。 对应的X2C 的代码如下:

@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("com.zhangyue.we.x2c.ano.Xml")
public class XmlProcessor extends AbstractProcessor { private int mGroupId = 0;
private LayoutManager mLayoutMgr; @Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
Log.init(processingEnvironment.getMessager());
mLayoutMgr = LayoutManager.instance();
mLayoutMgr.setFiler(processingEnvironment.getFiler());
} @Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Xml.class);
TreeSet<String> layouts = new TreeSet<>();
for (Element element : elements) {
Xml xml = element.getAnnotation(Xml.class);
String[] names = xml.layouts();
for (String name : names) {
layouts.add(name.substring(name.lastIndexOf(".") + 1));
}
} for (String name : layouts) {
if (mGroupId == 0 && mLayoutMgr.getLayoutId(name) != null) {
mGroupId = (mLayoutMgr.getLayoutId(name) >> 24);
}
Log.i("xinyu:"+ mGroupId);
mLayoutMgr.setGroupId(mGroupId);
mLayoutMgr.translate(name);
} mLayoutMgr.printTranslate();
return false;
} }
有一个小问题,他怎么知道我的XML文件在哪里?

查看源码之后,可以发现有一个方法:

    private HashMap<String, ArrayList<File>> scanLayouts(File root) {
return new FileFilter(root)
.include("layout")
.include("layout-land")
.include("layout-v28")
.include("layout-v27")
.include("layout-v26")
.include("layout-v25")
.include("layout-v24")
.include("layout-v23")
.include("layout-v22")
.include("layout-v21")
.include("layout-v20")
.include("layout-v19")
.include("layout-v18")
.include("layout-v17")
.include("layout-v16")
.include("layout-v15")
.include("layout-v14")
.exclude("build")
.exclude("java")
.exclude("libs")
.exclude("mipmap")
.exclude("values")
.exclude("drawable")
.exclude("anim")
.exclude("color")
.exclude("menu")
.exclude("raw")
.exclude("xml")
.filter();
}

这个方法会去扫描你项目的res/layout 等一系列文件。

找到文件之后,怎么解析呢?

具体的解析代码在:com.zhangyue.we.view.View#translate(java.lang.StringBuilder, java.lang.String, java.lang.String) 这个方法。

    @Override
public boolean translate(StringBuilder stringBuilder, String key, String value) {
switch (key) {
case "android:textSize":
return setTextSize(stringBuilder, value);
   private boolean setTextSize(StringBuilder stringBuilder, String value) {
String unit;
String dim;
if (value.startsWith("@")) {
unit = "TypedValue.COMPLEX_UNIT_PX";
dim = String.format("(int)res.getDimension(R.dimen.%s)", value.substring(value.indexOf("/") + 1));
} else {
if (value.endsWith("dp") || value.endsWith("dip")) {
unit = "TypedValue.COMPLEX_UNIT_DIP";
dim = value.substring(0, value.indexOf("d"));
} else if (value.endsWith("sp")) {
unit = "TypedValue.COMPLEX_UNIT_SP";
dim = value.substring(0, value.indexOf("s"));
} else {
unit = "TypedValue.COMPLEX_UNIT_PX";
dim = value.substring(0, value.indexOf("p"));
}
}
stringBuilder.append(String.format("%s.setTextSize(%s,%s);\n", getObjName(), unit, dim));
mImports.add("android.util.TypedValue");
return true;
}

其实就是拼接字符串。字符串里面就是Java 代码。

解析完,写入文件:

这里用到了javapoet 技术,不知道的可以百度下,是一个java 库,用它可以生成java 源代码。

public class LayoutWriter {
private Filer mFiler;
private String mName;
private String mMethodSpec;
private String mPkgName;
private String mLayoutCategory;
private String mLayoutName;
private TreeSet<String> mImports; public LayoutWriter(String methodSpec, Filer filer, String javaName
, String pkgName
, String layoutSort
, String layoutName
, TreeSet<String> imports) {
this.mMethodSpec = methodSpec;
this.mFiler = filer;
this.mName = javaName;
this.mPkgName = pkgName;
this.mLayoutCategory = layoutSort;
this.mLayoutName = layoutName;
this.mImports = imports;
} public String write() { MethodSpec methodSpec = MethodSpec.methodBuilder("createView")
.addParameter(ClassName.get("android.content", "Context"), "ctx")
.addStatement(mMethodSpec)
.returns(ClassName.get("android.view", "View"))
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.build(); TypeSpec typeSpec = TypeSpec.classBuilder(mName)
.addMethod(methodSpec)
.addSuperinterface(ClassName.get("com.zhangyue.we.x2c", "IViewCreator"))
.addModifiers(Modifier.PUBLIC)
.addJavadoc(String.format("WARN!!! dont edit this file\ntranslate from {@link %s.R.layout.%s}" +
"\nautho chengwei \nemail chengwei@zhangyue.com\n", mPkgName, mLayoutName))
.build(); String pkgName = "com.zhangyue.we.x2c.layouts";
if (mLayoutCategory != null && mLayoutCategory.length() > 0) {
pkgName += ("." + mLayoutCategory);
}
JavaFile javaFile = JavaFile.builder(pkgName, typeSpec)
.addImports(mImports)
.build();
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
} return pkgName + "." + mName;
}
}

MethodSpec 表示一个方法,addParameter 表示增加一个方法参数。javaFile.writeTo(mFiler); 就会把创建的Java 类写入文件,具体使用大家自己百度学习下吧。

那程序为什么用的是生成的java 文件?而不是xml?

我们写代码的时候,写的

        X2C.setContentView(this, R.layout.activity_main_inter);

就会执行下面的代码,x2c 的getView 会去拿生成的Java 文件,然后创建View.

    public static void setContentView(Activity activity, int layoutId) {
if (activity == null) {
throw new IllegalArgumentException("Activity must not be null");
}
View view = getView(activity, layoutId);
if (view != null) {
activity.setContentView(view);
} else {
activity.setContentView(layoutId);
}
} public static View getView(Context context, int layoutId) {
IViewCreator creator = sSparseArray.get(layoutId);
if (creator == null) {
try {
int group = generateGroupId(layoutId);
String layoutName = context.getResources().getResourceName(layoutId);
layoutName = layoutName.substring(layoutName.lastIndexOf("/") + 1);
String clzName = "com.zhangyue.we.x2c.X2C" + group + "_" + layoutName;
creator = (IViewCreator) context.getClassLoader().loadClass(clzName).newInstance();
} catch (Exception e) {
e.printStackTrace();
} //如果creator为空,放一个默认进去,防止每次都调用反射方法耗时
if (creator == null) {
creator = new DefaultCreator();
}
sSparseArray.put(layoutId, creator);
}
return creator.createView(context);
}

OK。到这里整个流程就走通了。

但是X2C 有BUG,有用户反馈:

SeekBar的MaxHeight和MinHeight属性,用X2C翻译成Java代码为:seekBar.setMaxHeight()和seekBar.setMinHeigh(),在seekBar源码中也没有这两个方法的. 编译报错。

这个BUG原作者可能没有仔细看,我给解决了。主要是View 属性翻译的时候,方法写错了方法名字。大家可以看下这个 https://github.com/TomasYu/X2C Git log提交 就知道了。

总结:

个人觉得,X2C ,思路很不错,而且作者对开源项目都熟知,了解市场上常用的框架。

但是,X2C 的局限性太大了,很多View 的属性,作者都没有写进去。比如:SeekBar的一些属性,

如:progress 这个属性你设置了默认是20的话,发现没有效果。没错,X2C 没有处理。它就处理了常见的

几个属性,但是并不能满足很多情况,很多常用控件的属性都没有做处理。个人还是对这个缺陷比较在意的。

android 的View 很多,TextView,EditText,这些都没有完全支持。

但是,作者的思路,创新,以及对新技术的学习之后使用,还是很值得我们肯定和学习的。

源码解析:解析掌阅X2C 框架的更多相关文章

  1. iOS开发之Masonry框架源码深度解析

    Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁.Masonry简化了NSLayoutConstraint的使用方式,让 ...

  2. Android 图片加载框架Glide4.0源码完全解析(二)

    写在之前 上一篇博文写的是Android 图片加载框架Glide4.0源码完全解析(一),主要分析了Glide4.0源码中的with方法和load方法,原本打算是一起发布的,但是由于into方法复杂性 ...

  3. Masonry框架源码深度解析

    Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁.Masonry简化了NSLayoutConstraint的使用方式,让 ...

  4. netty源码解解析(4.0)-18 ChannelHandler: codec--编解码框架

    编解码框架和一些常用的实现位于io.netty.handler.codec包中. 编解码框架包含两部分:Byte流和特定类型数据之间的编解码,也叫序列化和反序列化.不类型数据之间的转换. 下图是编解码 ...

  5. Spring框架之beans源码完全解析

    导读:Spring可以说是Java企业开发里最重要的技术.而Spring两大核心IOC(Inversion of Control控制反转)和AOP(Aspect Oriented Programmin ...

  6. Spring框架之AOP源码完全解析

    Spring框架之AOP源码完全解析 Spring可以说是Java企业开发里最重要的技术.Spring两大核心IOC(Inversion of Control控制反转)和AOP(Aspect Orie ...

  7. Spring框架之jms源码完全解析

    Spring框架之jms源码完全解析 我们在前两篇文章中介绍了Spring两大核心IOC(Inversion of Control控制反转)和AOP(Aspect Oriented Programmi ...

  8. Spring框架之spring-web http源码完全解析

    Spring框架之spring-web http源码完全解析 Spring-web是Spring webMVC的基础,由http.remoting.web三部分组成. http:封装了http协议中的 ...

  9. Spring框架之spring-web web源码完全解析

    Spring框架之spring-web web源码完全解析 spring-web是Spring webMVC的基础,由http.remoting.web三部分组成,核心为web模块.http模块封装了 ...

随机推荐

  1. IOS 获取更多的设备信息

    ●  如果想获得更多的设备信息,比如 ●  设备型号.CPU情况.内存使用情况.硬盘使用情况 ●  是否越狱.装了哪些传感器.当前运行的进程 ●  ... ... ●  有2种方法获取更多的设备信息 ...

  2. python:协程

    1,如何实现在两个函数之间的切换? def func1(): print(l) yield print(3) yield def func2(): g =func1() next(g) print(2 ...

  3. Gym 101334C 无向仙人掌

    给出图,求他的“仙人掌度”,即求包括他自身的生成子图有多少? 只能删去仙人掌上的叶子的一条边,然后根据乘法原理相乘: 1.怎么求一个仙人掌叶子上有多少边? 可以利用点,边双连通的时间戳这个概念,但是绝 ...

  4. sparkStreamming原理

    一.Spark Streamming 是基于spark流式处理引擎,基本原理是将实时输入的数据以时间片(秒级)为单位进行拆分,然后经过spark引擎以类似批处理的方式处理每个时间片数据. 二.Spar ...

  5. 【转】Mac本地生成SSH Key 的方法

    1. 查看秘钥是否存在 打开终端查看是否已经存在SSH密钥:cd ~/.ssh 如果没有密钥则不会有此文件夹,有则备份删除,   也可以直接删除, 2.生成新的秘钥, 命令如下 $ssh-keygen ...

  6. 【luogu P2731 骑马修栅栏】 题解

    题目链接:https://www.luogu.org/problemnew/show/P2731 这个题是欧拉回路的模板题,那么在这里给出一个hierholzer的做法. 对于求欧拉回路的问题,有Fl ...

  7. c语言描述的简单选择排序

    基本思想:首先,选出最小的数,放在第一个位置:然后,选出第二小的数,放在第二个位置:以此类推,直到所有的数从小到大排序 #include<stdio.h> #include<stdl ...

  8. LeetCode13.罗马数字转整数 JavaScript

    罗马数字包含以下七种字符: I, V, X, L,C,D 和 M. 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 写做 II ,即为两个并 ...

  9. Struts2 第一讲 -- Struts2开发前奏

    我们在学习Struts之前,先来复习一下Servlet,众所周知Servlet是JavaWeb的三大组件.我们发送一个请求,这个请求交给Servlet处理,Servlet将处理的结果返还给浏览器.每个 ...

  10. Python基础—11-面向对象(01)

    面向对象 面向对象 与面向过程对比: 面向过程:数学逻辑的映射,学会做个好员工 面向对象:生活逻辑的映射,学会做个好领导 生活实例: 类: 人 手机 电脑 对象: 我的手机.女朋友的手机 你的那部T4 ...