xUtils3.0框架源码粗浅讲解

本文深入分析xUtils框架的四大核心模块:ViewInjector注解模块、HttpManager网络请求模块、ImageManager图片模块、DbManager数据库模块。详细介绍各模块的实现原理及应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一年多前就已经使用过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)上继续维护, 相对于旧版本:
    1. HTTP实现替换HttpClient为UrlConnection, 自动解析回调泛型, 更安全的断点续传策略.
    2. 支持标准的Cookie策略, 区分domain, path...
    3. 事件注解去除不常用的功能, 提高性能.
    4. 数据库api简化提高性能, 达到和greenDao一致的性能.
    5. 图片绑定支持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框架粗浅的 分析 ,个人感觉还是受益匪浅!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值