一年多前就已经使用过xUtils框架,但后来因为项目需求的一些原因放弃了使用。最近看到xUtils框架又有更新了,并已到3.0版本,按耐不住想窥视其源码。OK,废话少说,先来看下uUtils框架组成的四大块:
ViewInjector注解模块、
HttpManager网络请求模块、ImageManager图片模块、
DbManager数据库模块。接下来将根据各个模块逐一来进行源码深入分析,不过在分析之前,先来看一段Github上关于xUtils3的简介。
xUtils3简介
- xUtils 包含了很多实用的android工具.
- xUtils 支持超大文件(超过2G)上传,更全面的http请求协议支持(11种谓词),拥有更加灵活的ORM,更多的事件注解支持且不受混淆影响...
- xUtils 最低兼容Android 4.0 (api level 14). (Android 2.3?)
- xUtils3变化较多所以建立了新的项目不在旧版(github.com/wyouflf/xUtils)上继续维护, 相对于旧版本:
- HTTP实现替换HttpClient为UrlConnection, 自动解析回调泛型, 更安全的断点续传策略.
- 支持标准的Cookie策略, 区分domain, path...
- 事件注解去除不常用的功能, 提高性能.
- 数据库api简化提高性能, 达到和greenDao一致的性能.
- 图片绑定支持gif(受系统兼容性影响, 部分gif文件只能静态显示), webp; 支持圆角, 圆形, 方形等裁剪, 支持自动旋转..
Github上的源码地址:
https://github.com/wyouflf/xUtils3/tree/master ,
接下来直接进入主题
。
一、ViewInjector注解模块
首先,在分析ViewInjector模块前,我们还得先简单了解下Java的注解,因为ViewInjector的注解就是基于Java本身的注解来实现的。注解,是JDK5.0之后引入的一个重要组成部分,它表示的是元数据,也即描述数据的数据(感觉很拗口),实现的过程依靠Java的反射机制。直接拿一段xUtils框架ViewInjector模块自定义的注解类来看,
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
}
在上面的代码其实我们理解比较常用的几个点即可:
a、@interface 声明注解的名称
b、@Target 说明注解应用的范围:
1)、TYPE 类、接口、或者枚举声明
2)、FIELD 成员域的声明
3)、METHOD 方法的声明
4)、PARAMETER 参数声明
5)、CONSTRUCTOR 构造函数声明
6)、LOCAL_VARIABLE 局部变量的声明
7)、ANNOTATION_TYPE 注解的声明(没错,可以自己注解自己)
8)、PACKAGE 包的声明
c、@Retention 定义了注解被保留的策略,简单来说就是注解在什么时候有效。一共三种:
1)、SOURCE 在源文件中有效
2)、CLASS 在class文件,即字节码文件有效
3)、RUNTIME 在运行时有效 (在xUtils都是使用这种策略,因为要在运行期通过反射来解析注解)
在xUtils的ViewInjector模块中一共定义了三种注解类:ContentView(布局文件注解) 、Event(事件处理注解)和ViewInject(成员域注解) ,自定义了注解类后,利用反射机制对annotation进行处理。接下来,我们查看
org.xutils.view.ViewInjectorImpl.java 类,它是对注解的接口的实现类。
1、ContentView布局文件注解
/**
* 根据类来查找ContentView的注解类,当前类如果没有,则递归查找其父类
*/
private static ContentView findContentView(Class<?> thisCls) {
if (thisCls == null || IGNORED.contains(thisCls)) {
return null;
}
ContentView contentView = thisCls.getAnnotation(ContentView.class);
if (contentView == null) {
return findContentView(thisCls.getSuperclass());//如果当前类没有,递归查找其父类
}
return contentView;
}
查找到ContentView注解类后,就是如何去渲染这布局了,在这里Activity和Fragment还是有所区别的。Activity调的是它的setContentView方法,而Fragment则使用inflate来还原,其实这也都是根据Activity 和 Fragment原来添加布局文件的方法来做,注解只是提供了一种自动化操作而已。
Activity类:
ContentView contentView = findContentView(handlerType);
if (contentView != null) {
int viewId = contentView.value();
if (viewId > 0) {
Method setContentViewMethod = handlerType.getMethod("setContentView", int.class);//反射Activity的setContentView方法
setContentViewMethod.invoke(activity, viewId);
}
}
Fragment类:
ContentView contentView = findContentView(handlerType);
if (contentView != null) {
int viewId = contentView.value();
if (viewId > 0) {
Method setContentViewMethod = handlerType.getMethod("setContentView", int.class);//反射Activity的setContentView方法
setContentViewMethod.invoke(activity, viewId);
}
}
2、ViewInject(成员域注解)
xUtils的成员域注解是只对AndroidUI组件有效的(所以基本数据类型这些字段是无效的),通过UI组件上定义的注解获取到在布局文件里面定义的组件id,然后通过传统的findViewById的方法获取到视图View,再使用反射进行赋值。需要注意:如果需要使用到注解的UI组件变量不能带静态字段、final字段。
// inject view
Field[] fields = handlerType.getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
Class<?> fieldType = field.getType();
if (
/* 不注入静态字段 */ Modifier.isStatic(field.getModifiers()) ||
/* 不注入final字段 */ Modifier.isFinal(field.getModifiers()) ||
/* 不注入基本类型字段 */ fieldType.isPrimitive() ||
/* 不注入数组类型字段 */ fieldType.isArray()) {
continue;
}
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
try {
View view = finder.findViewById(viewInject.value(), viewInject.parentId());
if (view != null) {
field.setAccessible(true);//重要!设置为可访问的
field.set(handler, view);
} else {
throw new RuntimeException("Invalid @ViewInject for "
+ handlerType.getSimpleName() + "." + field.getName());
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
}
} // end inject view
3、Event(事件处理注解)
事件注解定义的范围是在方法上的,所以查找所有标注Event事件的方法getDeclaredMethods(),同时也要注意,被标注注解的方法不能为静态的或私有的。具体查找的代码和成员域是接近的,所以就不贴代码了,另外,我们主要是要看下
EventListenerManager.
addEventMethod
(finder
,
info
,
event
,
handler
,
method)
;这个方法内的实现。
//省略代码......
/*
根据View的ID和当前的接口类型获取已经缓存的接口实例对象,
比如根据View.id和View.OnClickListener.class两个键获取这个View的OnClickListener对象
*/
Object listener = listenerCache.get(info, listenerType);
DynamicHandler dynamicHandler = null;
/*
如果接口实例对象不为空
获取接口对象对应的动态代理对象
如果动态代理对象的handler和当前handler相同
则为动态代理对象添加代理方法
*/
if (listener != null) {
dynamicHandler = (DynamicHandler) Proxy.getInvocationHandler(listener);
addNewMethod = handler.equals(dynamicHandler.getHandler());
if (addNewMethod) {
dynamicHandler.addMethod(methodName, method);
}
}
// 如果还没有注册此代理
if (!addNewMethod) {
dynamicHandler = new DynamicHandler(handler);
dynamicHandler.addMethod(methodName, method);
// 生成的代理对象实例,比如View.OnClickListener的实例对象
listener = Proxy.newProxyInstance(
listenerType.getClassLoader(),
new Class<?>[]{listenerType},
dynamicHandler);
listenerCache.put(info, listenerType, listener);
}
Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
setEventListenerMethod.invoke(view, listener);
//省略代码......
事件的注解比普通的视图注解要稍微复杂,主要的流程:
1)、先从listenerCache缓存查找对应的事件是否存在
2)、判断事件是否已存在代理内,没有则加入代理;否则,会创建动态代理
3)、最后invoke该事件,通过反射设置值
在这个流程中,其实有个地方是让我们比较感兴趣的,那就是代理。究竟这个代理是干什么的,又有什么用?带着这些疑问,继续进去代码里面看。其实使用动态代理的原因是,可以对多个不同类型的类进行统一的接口管理,这话好像有点不好理解,在动态代理的invoke方法内做了两件事情,第一:把事件类加入了
WeakReference弱引用,防止内存的泄露;第二:对于
onClick 和
onItemClick 这两种点击事件做了放暴力点击。
//省略代码....
public DynamicHandler(Object handler) {
this.handlerRef = new WeakReference<Object>(handler);//使用弱引用存放事件
}
//省略代码...
//省略代码...
if (AVOID_QUICK_EVENT_SET.contains(eventMethod)) {
long timeSpan = System.currentTimeMillis() - lastClickTime;
if (timeSpan < QUICK_EVENT_TIME_SPAN) {//判断两次事件invoke的事件间隔
LogUtil.d("onClick cancelled: " + timeSpan);
return null;
}
lastClickTime = System.currentTimeMillis();
}
//省略代码...
二、HttpManager网络请求模块
网络模块应该是这四大模块中最为复杂的,分析这块,我习惯是先把它拆分成几个小模块:参数封装小模块,请求过程模块(包括
网络配置、线程处理和缓存处理等
)以及结果处理小模块。其中最佳入手的位置应该是请求过程,去除枝叶,先把请求的主骨架抽出来,后面就好理解了。
另外,xUtils的代码书写是比较规范的,四个模块对外都是面向接口编程,还有一些代理模式也是应用得很溜(后面会讲到),还是
先来走一遍整个请求过程吧。
代码位置:
org.xutils.
http.
HttpManagerImpl.java(对外的接口为HttpManager)
@Override
public <T> Callback.Cancelable request(HttpMethod method, RequestParams entity, Callback.CommonCallback<T> callback) {
//设置请求方式
entity.setMethod(method);
Callback.Cancelable cancelable = null;
if (callback instanceof Callback.Cancelable) {
cancelable = (Callback.Cancelable) callback;
}
//创建一次请求任务
HttpTask<T> task = new HttpTask<T>(entity, cancelable, callback);
return x.task().start(task);
}
看最后一句代码x.task.start(task),这里是把一个任务加入到线程池里面去跑,防止在UI线程导致堵塞。管理任务task这部分可以作为一个独立的模块,其内部流程可以概括为如下:
类x ——> x内部类Ext ——> 接口TaskController ——> 实现类TaskControllerImpl ——> 异步任务代理类TaskProxy
上面的流程TaskProxy类是真正对线程池方面的管理。只要对整个流程清楚好像也没什么好说的了,因为
每个类里面的代码是比较简单的。
OK,了解线程池如何管理任务之后,我们进入到每个任务的具体实现。回去看类
org.xutils.
http.
HttpTask,其继承了AbsTask类,重点看里面的
doBackground
()方法:
//代码太长,省略.....
概况这个方法主要完成以下几件事情:
1)、检查回调接口传递的参数类
2)、判断当前的任务是否下载任务,如果存在相同的下载任务,则把之前的停止掉,使用新的任务去请求(针对图片类请求)
3)、设置请求失败重试的最大次数
4)、检查是否需要缓存请求结果的任务,所以这里如果我们希望每次请求设置缓存,那么我们可以在回调接口里面使用Callback里面的
CacheCallback接口即可。
5)、然后就是发起请求
requestWorker
.request()
;
从request()这个方法可以找到它的内部类
RequestWorker
,这个内部类会有一句调用的代码request.loadResult(),跟着会
转到UriRequest类,它还不是直接请求的代码,它是通过自定义类加载器去sendRequest的,那么直接调用调用应该就是在这些自定义的类加载类内。文字功底不好,好像还是不够描述这一调用流程,所以接下来用一张图来展示整个过程:

完成一次的http请求过程之后,大家会不会还有一个疑问,就是请求结果是如何回调的?其实如果又仔细查看上面介绍的一段关于任务线程过来那块,我们就可以知道,答案就在
TaskProxy类的doBackground方法里面。另外,从这个方法里面我们可以看到回调方法都是执行在UI线程的,这点与OKHttp框架的有点区别。
这样那基本HTTP模块的主线就介绍完了。其他还有很多枝叶的内容也非常有价值,但篇幅有限,就不再展开分析。
三、ImageManager图片模块
说到图片框架,可能大家就会毫不犹豫的想到其三级图片缓存策略,为了提高图片的加载速度,节省不必要的流量,提高应用的使用体验,往往目前的图片框架都采用了这种做法,xUtils框架的图片模块也不例外。可能部分人会问,什么是三级缓存?用一句话概括就是,我们根据url作为key,先去内存查找对应的图片是否存在,如果不存在,继续到本地去查找图片文件,如果依然没找到就从网络去下载该图片。简单了解三级缓存策略之后,我们就开始来介绍下xUtils是如何实现的。在xUtils里面图片加载与网络模块是紧密联系的。
下面是对外提供的一系列调用接口
//省略代码.....
void bind(ImageView view, String url, ImageOptions options);
void bind(ImageView view, String url, Callback.CommonCallback<Drawable> callback);
void bind(ImageView view, String url, ImageOptions options, Callback.CommonCallback<Drawable> callback);
从内存中检查,首先会进入到
org
.xutils.image.
ImageLoader这个类
static Cancelable doBind(final ImageView view,
final String url,
final ImageOptions options,
final Callback.CommonCallback<Drawable> callback) {
//省略代码....
// 从内存中加载图片
Drawable memDrawable = null;
if (localOptions.isUseMemCache()) {
memDrawable = MEM_CACHE.get(key);
if (memDrawable instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) memDrawable).getBitmap();
if (bitmap == null || bitmap.isRecycled()) {
memDrawable = null;
}
}
}
//省略代码....
}
如果内存中没有找到图片,则开始从本地查找,由于从本地去读取图片文件是相对内存更耗时的,所以这里开启了一个任务,放到任务队列里面去执行,那么和网络数据请求走的就一样了,可以进入
org.xutils.
http.
HttpTask类的
doBackground方法去看,这里开始请求本地缓存:
// 尝试从缓存获取结果, 并为请求头加入缓存控制参数.
try {
clearRawResult();
LogUtil.d("load cache: " + this.request.getRequestUri());
rawResult = this.request.loadResultFromCache();
} catch (Throwable ex) {
LogUtil.w("load disk cache error", ex);
}
request
.loadResultFromCache()的实现类在
org
.xutils.http.request.
HttpRequest。
@Override
public Object loadResultFromCache() throws Throwable {
isLoading = true;
DiskCacheEntity cacheEntity = LruDiskCache.getDiskCache(params.getCacheDirName())
.setMaxSize(params.getCacheSize())
.get(this.getCacheKey());
if (cacheEntity != null) {
if (HttpMethod.permitsCache(params.getMethod())) {
Date lastModified = cacheEntity.getLastModify();
if (lastModified.getTime() > 0) {
params.setHeader("If-Modified-Since", toGMTString(lastModified));
}
String eTag = cacheEntity.getEtag();
if (!TextUtils.isEmpty(eTag)) {
params.setHeader("If-None-Match", eTag);
}
}
return loader.loadFromCache(cacheEntity);//对应自定义的loader去解析,如org.xutils.http.loader.JSONObjectLoader
} else {
return null;
}
}
上面的内存缓存使用了LruCache,本地缓存LruDiskCache,是一种LRU缓存算法。因为手机的内存和存储空间都很有限,它把最近使用到的图片保持强引用,或写入本地,当缓存的数量达到一定的数量之后,就会把不经常使用到的图片删除掉。
最后,内存和本地都不存在就开始进行网络请求了,网络请求和数据请求是一样的,这里就不累赘介绍。另外,最想说下的还是它对gif图片和webp图片的支持。
简单介绍GIF的文件结构:
一个GIF文件的结构分为文件头(File Header)、GIF 数据流(GIF Data Stream)和文件终结器(Trailer)三部分。
文件头:GIF文件署名(Signature)和版本号(Version)
GIF数据流:控制标识符、图像块(Image Block)和其他的一些扩展块组成;
文件终结器:只有一个值为0x3B的字符(“;”)表示文件结束;

(
说明:图片来源于网络)
这里我们后面代码要判断下载的是否GIF图片的判断,只通过GIF文件的署名即可。它由三个字符组成“GIF"。同样webp的图片也可以采用类似这方法来进行判断,不过它是从第8个字符开始。下面就是两个判断GIF和webp图片的方法,
查看
org.xutils.image.ImageDecoder类
/**
* 判断是否gif图片
* @param file
* @return
*/
public static boolean isGif(File file) {
FileInputStream in = null;
try {
in = new FileInputStream(file);
byte[] header = IOUtil.readBytes(in, 0, 3);
return Arrays.equals(GIF_HEADER, header);
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
} finally {
IOUtil.closeQuietly(in);
}
return false;
}
/**
* 判断是否webp图片
* @param file
* @return
*/
public static boolean isWebP(File file) {
FileInputStream in = null;
try {
in = new FileInputStream(file);
byte[] header = IOUtil.readBytes(in, 8, 4);
return Arrays.equals(WEBP_HEADER, header);
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
} finally {
IOUtil.closeQuietly(in);
}
return false;
}
文件到GIF图片的转码,主要用到Android系统提供的Movie这个类,然后再由movie——>
GifDrawable(这个是外包装了movie类的绘制),同时GifDrawable是继承Drawable类的,获取到GifDrawable就可以直接与ImageView组件进行交互了,看下面这两个方法:
static Drawable decodeFileWithLock(final File file,
final ImageOptions options,
final Callback.Cancelable cancelable) throws IOException {
//省略代码....
Drawable result = null;
if (!options.isIgnoreGif() && isGif(file)) {
Movie movie = null;
synchronized (gifDecodeLock) { // decode with lock
movie = decodeGif(file, options, cancelable);
}
if (movie != null) {
result = new GifDrawable(movie, (int) file.length());
}
//省略代码....
}
/**
* 转换文件为Movie, 可用于创建GifDrawable.
*/
public static Movie decodeGif(File file, ImageOptions options, Callback.Cancelable cancelable) throws IOException {
//省略代码....
InputStream in = null;
//省略代码....
Movie movie = Movie.decodeStream(in);
//省略代码....
}
上面这两块代码就是GIF转换实现的过程,如果不需深究Movie这类,那么过程也是很简单的。接下来再说下webp,webp的解码使用了一个native的库lib
webpbackport.so ,xUtils工程上是没有提供这个so库的源码的,如果大家感兴趣可以到这个地址去下载:
https://github.com/alexey-pelykh/webp-android-backport,然后进行编译。webp图片的编解码工作都是在这个库里面完成,这里介绍到此为止。
对外提供的编码调用接口:
// decode file
Bitmap bitmap = null;
if (isWebP(file)) {
bitmap = WebPFactory.decodeFile(file.getAbsolutePath(), bitmapOps);
}
四、DbManager数据库模块
数据库这块其实在上面网络请求保存缓存记录里面已经使用到了,不过下面单独来分析的是数据库框架具体实现。我们知道Android应用使用的数据库是sqlite轻量嵌入式数据库,对于我们上层使用的开发者来说,与mysql这些语言几乎接近,所以如果有mysql数据库这些的开发经验,理解sqlite数据库没有任何难度。
大多数情况下,如果涉及到数据库方面的应用,我们都会选择使用这方面的数据库框架,为什么?主要应该还是它采用了ORM这种设计。什么是ORM ?
ORM即Object-Relational Mapping,对象关系映射。简单理解就是把我们Java的对象与数据库里面的记录进行映射,可以把实体对象持久化到数据库中,也能把查询到的记录映射成Java对象。这样一来,我们几乎都不需要去关注那些sql语法什么的了。它的原理就是通过Java的反射机制把对象和数据库记录关联映射起来。
1、数据库是如何创建的?
在
org
.xutils.db.
DbManagerImpl.java类的构造函数就是创建数据库的,通常我们都只需要用到一个数据库即可,避免重复创建多个以及造成没必要的内存空间浪费,这里的构造函数是private的,采用单例模式。另外,我们在创建数据库的时候需要做一些默认的配置,所以下面这些代码如果在正式的应用了,可以配置到Application里面去。
DbManager.DaoConfig daoConfig = new DbManager.DaoConfig()
.setDbName("test.db")//数据库名称
// 不设置dbDir时, 默认存储在app的私有目录.
.setDbDir(new File("/sdcard")) // "sdcard"的写法并非最佳实践, 这里为了简单, 先这样写了.
.setDbVersion(2)
.setDbOpenListener(new DbManager.DbOpenListener() {
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onDbOpened(DbManager db) {
// 开启WAL, 对写入加速提升巨大
db.getDatabase().enableWriteAheadLogging();
}
})
.setDbUpgradeListener(new DbManager.DbUpgradeListener() {
@Override
public void onUpgrade(DbManager db, int oldVersion, int newVersion) {
// TODO: ...
// db.addColumn(...);
// db.dropTable(...);
// ...
// or
// db.dropDb();
}
});
对外调用
DbManager
db = x.
getDb
(
daoConfig
)
;即可拿到了
DbManagerImpl里面创建的数据库引用
private DbManagerImpl(DaoConfig config) {
if (config == null) {
throw new IllegalArgumentException("daoConfig may not be null");
}
this.daoConfig = config;
this.allowTransaction = config.isAllowTransaction();
this.database = openOrCreateDatabase(config);
DbOpenListener dbOpenListener = config.getDbOpenListener();
if (dbOpenListener != null) {
dbOpenListener.onDbOpened(this);
}
}
如此,数据库就创建完成了,就和普通类创建一样,还没ORM这方面的影子。
2、Java实体对象是如何映射成数据库表记录?
从保存一个实体对象开始,了解其整个过程,那增删改查这些也简单了....
@Override
public void save(Object entity) throws DbException {
try {
//开启事务
beginTransaction();
//批量保存
if (entity instanceof List) {
List<?> entities = (List<?>) entity;
if (entities.isEmpty()) return;
//1.从Java实体到关联数据库表对象的转换
TableEntity<?> table = this.getTable(entities.get(0).getClass());
//2.检查表是否存在
createTableIfNotExist(table);
for (Object item : entities) {
//3.创建相应的sql语句并执行
execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, item));
}
} else {//单条记录保存
TableEntity<?> table = this.getTable(entity.getClass());
createTableIfNotExist(table);
execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, entity));
}
//标识当前事务执行成功
setTransactionSuccessful();
} finally {
//结束事务
endTransaction();
}
}
第一步:先是根据类名去查找相应的表关联对象,
public <T> TableEntity<T> getTable(Class<T> entityType) throws DbException {
//省略代码....
table = new TableEntity<T>(this, entityType);
//省略代码....
}
如若没有找到,就是新创建,下面就可以看到,通过注解把定义的table的值作为数据库表名,反射Java对象的所有成员域(static或transient关键字的不作映射),获取带
Column
注解的映射为表对应的字段。
/*package*/ TableEntity(DbManager db, Class<T> entityType) throws Throwable {
//省略.....
//获取注解定义的表名
Table table = entityType.getAnnotation(Table.class);
this.name = table.name();
this.onCreated = table.onCreated();
//查找Java实体定义的表字段
this.columnMap = TableUtils.findColumnMap(entityType);
//省略.....
}
查找表字段
/* package */
static synchronized LinkedHashMap<String, ColumnEntity> findColumnMap(Class<?> entityType) {
LinkedHashMap<String, ColumnEntity> columnMap = new LinkedHashMap<String, ColumnEntity>();
addColumns2Map(entityType, columnMap);
return columnMap;
}
private static void addColumns2Map(Class<?> entityType, HashMap<String, ColumnEntity> columnMap) {
if (Object.class.equals(entityType)) return;
try {
Field[] fields = entityType.getDeclaredFields();
for (Field field : fields) {
int modify = field.getModifiers();
if (Modifier.isStatic(modify) || Modifier.isTransient(modify)) {
continue;
}
Column columnAnn = field.getAnnotation(Column.class);
if (columnAnn != null) {
if (ColumnConverterFactory.isSupportColumnConverter(field.getType())) {
ColumnEntity column = new ColumnEntity(entityType, field, columnAnn);
if (!columnMap.containsKey(column.getName())) {
columnMap.put(column.getName(), column);
}
}
}
}
addColumns2Map(entityType.getSuperclass(), columnMap);
} catch (Throwable e) {
LogUtil.e(e.getMessage(), e);
}
}
第二步:Java实体对象到表关联对象的创建完成之后,回到开始的代码,开始检查数据库表是否存在,不存在则会拼接sql语句,物理创建Java实体对象到数据库的表结构
protected void createTableIfNotExist(TableEntity<?> table) throws DbException {
if (!table.tableIsExist()) {
synchronized (table.getClass()) {
if (!table.tableIsExist()) {
SqlInfo sqlInfo = SqlInfoBuilder.buildCreateTableSqlInfo(table);//拼接sql语句
execNonQuery(sqlInfo);//执行sql语句,物理创建Java实体对象到数据库的表结构
String execAfterTableCreated = table.getOnCreated();
if (!TextUtils.isEmpty(execAfterTableCreated)) {
execNonQuery(execAfterTableCreated);//Java实体对象oncreate存放的要是完整的sql语句,在表结构创建完成之后执行
}
table.setCheckedDatabase(true);
TableCreateListener listener = this.getDaoConfig().getTableCreateListener();
if (listener != null) {
listener.onTableCreated(this, table);
}
}
}
}
}
真正执行数据库的sql语句
@Override
public void execNonQuery(SqlInfo sqlInfo) throws DbException {
//省略代码...
statement = sqlInfo.buildStatement(database);
statement.execute();
//省略代码...
}
第三步:表已经存在就可以插数据了,那么框架要完成的事情就肯定是对sql语句的封装,全自动插入,如此外部调用接口则可不必关心那些繁杂的sql语法了。
org.xutils.db.
sqlite.
SqlInfoBuilder.java这个类封装了增删改查等的所有sql拼接的方法
//*********************************************** insert sql ***********************************************
public static SqlInfo buildInsertSqlInfo(TableEntity<?> table, Object entity) throws DbException {
List<KeyValue> keyValueList = entity2KeyValueList(table, entity);
if (keyValueList.size() == 0) return null;
SqlInfo result = new SqlInfo();
String sql = INSERT_SQL_CACHE.get(table);
if (sql == null) {
StringBuilder builder = new StringBuilder();
builder.append("INSERT INTO ");
builder.append("\"").append(table.getName()).append("\"");
builder.append(" (");
for (KeyValue kv : keyValueList) {
builder.append("\"").append(kv.key).append("\"").append(',');
}
builder.deleteCharAt(builder.length() - 1);
builder.append(") VALUES (");
int length = keyValueList.size();
for (int i = 0; i < length; i++) {
builder.append("?,");
}
builder.deleteCharAt(builder.length() - 1);
builder.append(")");
sql = builder.toString();
result.setSql(sql);
result.addBindArgs(keyValueList);
INSERT_SQL_CACHE.put(table, sql);
} else {
result.setSql(sql);
result.addBindArgs(keyValueList);
}
return result;
}
上面三步走完之后,新的Java对象就在数据库创建了表结构,同时把对象映射为数据库记录了。其他删除、修改则雷同,就不再详细说明。
经过对xUtils框架粗浅的
分析
,个人感觉还是受益匪浅!!!