- 文档
- 教程
- 知识库
【免费下载链接】android-open-project-analysis
Analysis implementation of android open source project, 微信公众号:codekk, 网站:
本文基于本仓库 tool-lib/common/xutils/README.md 的 xUtils 源码解析文档整理成文,并结合 公共技术点之 Java 注解 Annotation、公共技术点之 Java 动态代理 与 Volley 源码解析 等仓库资源做横向印证。
导读:xUtils 是一个以"注解 + 反射"为骨架的 Android 公共库框架,覆盖 View 注入、数据库 ORM、网络请求与图片加载四个高频场景。本文系统拆解其四大模块的类设计与核心调用链:View 模块如何用注解完成 UI / 资源 / 事件绑定,Db 模块如何用注解与反射拼装 SQL 并做查询缓存,Http 模块如何在 HttpClient 之上叠加 gzip 压缩与线程池调度,Bitmap 模块如何用"内存 + 闪存"两级缓存实现图片异步加载。读完本文,你既能照文档还原 xUtils 的实现思路,也能将其中的注解注入、动态代理、LRU 缓存与线程池设计迁移到自己的组件开发中。
1. 功能介绍与模块总览
xUtils 是一个 Android 公共库框架,主要包括四个部分:View、Db、Http、Bitmap 四个模块。
| 模块 | 主要功能 |
|---|---|
| View | 通过注解绑定 UI、资源和事件 |
| Db | 数据库 ORM 框架,简单语句即可进行数据操作 |
| Http | 访问网络,支持同步、异步方式的请求,支持文件下载 |
| Bitmap | 加载图片以及图片处理,支持加载本地、网络图片,并支持图片的内存缓存和本地缓存 |
分析对象为 README 标注的 xUtils 指定版本(commit192c2a886c)。仓库中同时保留了该框架的 UML 设计文件 xUtils.mdl 与各模块的流程图、类图,可作为阅读源码时的对照材料。
2. View 模块:注解绑定 UI、资源与事件
2.1 总体设计
View 模块的流程和关系较少,核心思想非常集中:用注解描述"注入什么",用反射执行"如何注入"。整体流程见下图。
2.2 前置知识:注解与动态代理
注解和反射知识是这个模块的主要内容,建议先阅读仓库内两份公共技术点文档:
- 公共技术点之 Java 注解 Annotation:Annotation 是能够添加到 Java 源代码的元数据,类、方法、变量、参数、包都可以被注解。按 @Retention 可分为 SOURCE(源码时)、CLASS(编译时)、RUNTIME(运行时)三类;运行时注解可手动调用
getAnnotation(Class)、getAnnotations()、isAnnotationPresent(Class)等 API 解析出注解信息并读取属性值。 - 公共技术点之 Java 动态代理:动态代理类在程序运行前不存在、运行时由程序动态生成。实现动态代理包括三步:新建委托类(必须实现接口);实现
InvocationHandler接口;通过Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler)新建代理类对象。调用代理对象的每个函数最终都会进入InvocationHandler.invoke统一处理。
xUtils 的 View 模块正是把这两项技术组合起来:注解标记注入目标,反射读取注解信息,动态代理统一接管事件监听回调。
2.3 核心类功能介绍
2.3.1 ViewUtils.java——注入的入口
View 和各种事件的注入以及资源的注入,都由ViewUtils完成。主要函数:
private static void injectObject(Object handler, ViewFinder finder)第一个参数Object handler代表需要注入的对象;第二个参数是需要注入 View(即 handler 的成员变量)所在的 View 或者 Activity 的包装对象ViewFinder。该方法通过反射和注解完成了四件事:
- 完成 Activity 的
setContentView; - 完成 View 的注入;
- 完成资源的注入;
- 完成各种事件的注入。
从该方法的功能划分可以推断,框架内部围绕这四件事分别定义了对应语义的注解类(Activity 布局、View 绑定、资源引用、事件监听),统一在injectObject中按注解类型逐一解析并完成赋值与注册。
2.3.2 ViewFinder.java——View 查找器
public View findViewById(int id, int pid) public View findViewById(int id)ViewFinder封装了"到哪里去找 View"的规则:如果存在父 View,优先从父 View 中寻找;否则从当前的 View 或者 Activity 中寻找。这种"先父后己"的查找顺序,使注入逻辑可以同时服务于 Activity(直接findViewById)和自定义 View 容器(从父容器查找),是注入目标解析的定位基础。
2.3.3 ResLoader.java——资源加载器
public static Object loadRes(ResType type, Context context, int id)ResLoader用于获取资源文件值,支持多种资源的获取(通过ResType枚举区分资源类型,如字符串、颜色、尺寸、图片等),是"资源注入"步骤背后的实际执行者:拿到资源 id 后按目标类型从Context中读取对应的资源值。
2.3.4 EventListenerManager.java——事件注入与动态代理
事件注入是这个模块中设计最精巧的部分,核心是通过动态代理实现的。类中持有一个监听事件接口的缓存:
private final static DoubleKeyValueMap<ViewInjectInfo, Class<?>, Object> listenerCache = new DoubleKeyValueMap<ViewInjectInfo, Class<?>, Object>();DoubleKeyValueMap以(ViewInjectInfo, Class<?>)双键存储已注册的监听对象。这样做的原因在于:有些监听接口含有多个函数(例如OnClickListener只有一个回调,而OnTouchListener、TextWatcher等接口有多个方法),代理会先判断事件接口是否已存在——如果已存在,只需在既有代理上增加代理方法即可,避免对同一个控件重复设置监听接口。
public static void addEventMethod( ViewFinder finder, ViewInjectInfo info, Annotation eventAnnotation, Object handler, Method method)addEventMethod负责代理监听事件:根据注解中声明的事件类型(如 click、longClick、touch 等)与回调方法名,通过Proxy.newProxyInstance动态生成监听接口的代理对象,把所有事件回调统一转发到 handler 上被注解标记的那个方法,从而把"控件事件 → handler 方法"的映射从模板代码中解放出来。
2.3.5 注解类
View 模块还包含一组注解类,分别标注 Activity 布局、View 成员变量、资源字段与事件回调方法。它们均声明为运行时注解(配合反射解析),注解的属性值(如 View id、事件类型、回调方法名)就是注入时需要的元数据。具体定义细节可对照 UML 设计文件 xUtils.mdl 进一步梳理。
3. Db 模块:注解 + 反射驱动的 ORM 数据库框架
3.1 总体设计
Db 模块的流程和关系同样较少,核心在于:实体类用注解描述表结构,框架用反射读取注解自动建表、拼装 SQL、完成增删改查。注解、反射和数据库操作知识是这个模块的主要内容,流程见下图。
3.2 核心类功能介绍
3.2.1 DbUtils.java——数据库的创建与增删改查
DbUtils是 Db 模块的门面类,主要功能是数据库的创建和数据的增删改查。类中用一个 Map 管理所有数据库实例:
private static HashMap<String, DbUtils> daoMap = new HashMap<String, DbUtils>();该 Map 存放DbUtils实例对象,每个数据库对应一个实例,key 为数据库名称。获取实例的方法采取单例模式:
private synchronized static DbUtils getInstance(DaoConfig daoConfig)根据DaoConfig创建数据库,中间还涉及到数据库升级逻辑。核心的增删改查 API 包括:
delete; findAll; findById; saveOrUpdate;// 当数据库没有时保存,存在时修改。 update;其中saveOrUpdate的语义是:数据库中没有对应记录时保存(insert),已存在时修改(update),是典型的"有则更新、无则插入"的 upsert 操作。
3.2.2 DaoConfig.java——数据库配置
private String dbName = "xUtils.db"; // default db name 数据库名称 private int dbVersion = 1; //数据库版本 private DbUpgradeListener dbUpgradeListener; //升级监听事件DaoConfig是数据库配置类,包含三个关键配置:数据库名称(默认xUtils.db)、数据库版本(默认1)、升级监听器DbUpgradeListener。当版本号变化触发数据库升级时,升级监听器会被回调,供调用方执行表结构迁移。
3.2.3 FindTempCache.java——查询缓存
在DbUtils的查询数据中,有一个针对查询结果的临时缓存FindTempCache:
@SuppressWarnings("unchecked") public <T> List<T> findAll(Selector selector) throws DbException { .... String sql = selector.toString(); long seq = CursorUtils.FindCacheSequence.getSeq(); findTempCache.setSeq(seq); Object obj = findTempCache.get(sql);//优先从缓存读取 if (obj != null) { return (List<T>) obj; } ... }findAll先以 SQL 字符串为 key 从缓存中读取结果,命中则直接返回,未命中才真正执行数据库查询。这里的FindCacheSequence是一个序列号机制:在数据发生变化(增删改)时会递增序列号,使缓存与底层数据保持一致——可以推断,框架通过比对序列号来决定缓存是否仍然有效。
3.2.4 SqlInfoBuilder.java——SQL 语句构造器
SqlInfoBuilder负责 sql 建表、增删改语句的组合,核心方法如下:
public static SqlInfo buildCreateTableSqlInfo(DbUtils db, Class<?> entityType) public static SqlInfo buildDeleteSqlInfo(DbUtils db, Class<?> entityType, Object idValue) public static SqlInfo buildDeleteSqlInfo(DbUtils db, Class<?> entityType, WhereBuilder whereBuilder) public static SqlInfo buildDeleteSqlInfo(DbUtils db, Object entity) public static SqlInfo buildInsertSqlInfo(DbUtils db, Object entity) public static SqlInfo buildUpdateSqlInfo(DbUtils db, Object entity, String... updateColumnNames) public static SqlInfo buildUpdateSqlInfo(DbUtils db, Object entity, WhereBuilder whereBuilder, String... updateColumnNames)可以看到删除语句有三种重载:按主键 id 删除、按WhereBuilder条件删除、按实体对象删除;更新语句则支持指定要更新的列名,或配合WhereBuilder精确限定更新范围。所有方法都返回SqlInfo——SQL 语句与其参数值的统一包装对象。
3.2.5 其余核心类
- SqlInfo.java:SQL 语句和值的包装对象,是
SqlInfoBuilder的输出,也是真正执行语句前"语句 + 参数"的载体。 - Table.java:表对象,封装了实体类对应的表名、主键、列集合等表结构信息。
- Column.java:表中列对象,对应实体类中一个被注解标记的字段与数据库列的映射。
- Id.java:表对应的主键对象,标识实体类中作为主键的字段。
- Selector.java:SQL 查询语句的组合器,通过链式方法描述查询条件、排序、limit 等,最终
toString()生成查询 SQL。 - WhereBuilder.java:SQL 条件语句的组合器,用于拼接
WHERE子句,供查询、更新和删除共用。
4. Http 模块:同步/异步请求与断点下载
4.1 总体设计
Http 模块的整体构建思路如下图所示。
完整的类图见 http_class.png,访问网络数据的处理流程见 request_sque.png。
4.2 HttpUtils.java——网络请求入口
HttpUtils支持异步、同步访问网络数据,以及断点下载文件。类中持有几个关键成员:
//网络数据的缓存。 public final static HttpCache sHttpCache = new HttpCache(); //访问网络的 HttpClient。 private final DefaultHttpClient httpClient; private final HttpContext httpContext = new BasicHttpContext(); //线程池。 private final static PriorityExecutor EXECUTOR = new PriorityExecutor(DEFAULT_POOL_SIZE);sHttpCache:全局共享的网络数据缓存;httpClient:基于 Apache HttpClient 的DefaultHttpClient,访问网络的底层载体;httpContext:BasicHttpContext,保存一次请求的上下文状态;EXECUTOR:PriorityExecutor线程池,池大小由DEFAULT_POOL_SIZE决定,负责调度网络任务。
构造函数集中完成了客户端配置:
public HttpUtils(int connTimeout, String userAgent) { //配置超时时间,UserAgent, http 版本信息协议等一些信息 ..... //将配置的参数统一放到 httpClient 中 httpClient = new DefaultHttpClient(new ThreadSafeClientConnManager(params, schemeRegistry), params); .... //下面这个关键,设置拦截器。 默认加上 gizp 压缩。 通过 gizp 压缩后的数据传输效率高很多。 httpClient.addRequestInterceptor(new HttpRequestInterceptor() { @Override public void process(org.apache.http.HttpRequest httpRequest, HttpContext httpContext) throws org.apache.http.HttpException, IOException { if (!httpRequest.containsHeader(HEADER_ACCEPT_ENCODING)) { httpRequest.addHeader(HEADER_ACCEPT_ENCODING, ENCODING_GZIP); } } }); httpClient.addResponseInterceptor(new HttpResponseInterceptor() { @Override public void process(HttpResponse response, HttpContext httpContext) throws org.apache.http.HttpException, IOException { final HttpEntity entity = response.getEntity(); if (entity == null) { return; } final Header encoding = entity.getContentEncoding(); if (encoding != null) { for (HeaderElement element : encoding.getElements()) { if (element.getName().equalsIgnoreCase("gzip")) { //这里判断从服务器传输的数据是否需要通过 gzip 解压。 response.setEntity(new GZipDecompressingEntity(response.getEntity())); return; } } } } }); }构造函数中配置了超时时间、UserAgent、HTTP 版本信息协议等参数,并统一放入httpClient。其中最关键的一步是设置请求/响应拦截器,默认启用 gzip 压缩:
- 请求拦截器:如果请求头中没有
Accept-Encoding,则自动加上gzip,经过 gzip 压缩后的数据传输效率高很多; - 响应拦截器:检查响应实体的
Content-Encoding,如果服务器返回的是gzip压缩内容,则用GZipDecompressingEntity包装响应实体,实现透明解压。
两个核心操作入口:
//访问网络数据 private <T> HttpHandler<T> sendRequest(HttpRequest request, RequestParams params, RequestCallBack<T> callBack); //下载网络文件 public HttpHandler<File> download(HttpRequest.HttpMethod method, String url, String target, RequestParams params, boolean autoResume, boolean autoRename, RequestCallBack<File> callback);sendRequest负责通用网络数据请求;download负责文件下载,其中autoResume表示是否断点续传(从已下载部分继续),autoRename表示是否根据服务器返回自动重命名目标文件。
4.3 请求相关类
- HttpRequest.java:网络请求的包装类,包括 url、访问请求方法(
HttpMethod)、参数值等。 - RequestCallBack.java:完成数据请求的回调接口,异步请求的结果通过该回调返回给调用方。
4.4 HttpHandler.java——请求处理逻辑
HttpHandler是获取网络数据逻辑的实现,这里可以理解为系统内部的 AsyncTask:在后台线程执行网络请求,通过线程通信(Handler)把结果回调到主线程。访问网络数据的处理流程见 request_sque.png,流程中会涉及缓存判断、gzip 解压、数据转换与回调分发等环节。
4.5 HttpCache.java——网络数据缓存
HttpCache是网络数据的缓存,内部包含一个LruMemoryCache。在获取数据的时候会判断是否过期,命中且未过期的数据直接从内存返回,从而减少重复网络请求。
4.6 StringDownLoadHandler.java 与 FileDownLoadHandler.java
- StringDownLoadHandler.java:
handleEntity()将网络 IO 流转化为 String; - FileDownLoadHandler.java:
handleEntity()将网络 IO 流转化为 File。
两者分别对应"请求字符串数据"与"下载文件"两种场景,把统一的HttpEntity按需转换为不同产物。
4.7 HttpException.java
框架的统一异常类,网络层所有错误(连接失败、超时、IO 异常等)统一包装为HttpException抛出,方便调用方集中处理。
5. Bitmap 模块:图片异步加载与两级缓存
5.1 总体设计
Bitmap 模块负责图片的异步加载,支持本地和网络图片,内置图片压缩处理、内存缓存与本地缓存。整体构建思路与类图见 bitmap_design.png、bitmap_class.png,其网络加载部分复用 Http 模块的思路(详见第 4 章)。图片设置与加载的详细流程见下图。
5.2 BitmapUtils.java——图片加载入口
BitmapUtils是图片加载的门面类,核心成员如下:
private BitmapGlobalConfig globalConfig; // 线程池,缓存,和网络的配置 private BitmapDisplayConfig defaultDisplayConfig; //图片显示的配置对外核心方法:
/** * @param container 表示需要显示图片的 View * @param uri 图片的 uri * @param displayConfig 图片显示的配置 * @param callBack 图片加载的回调接口 */ public <T extends View> void display(T container, String uri, BitmapDisplayConfig displayConfig, BitmapLoadCallBack<T> callBack)display负责把uri指向的图片异步加载并显示到指定 View 上,displayConfig控制显示尺寸、动画与占位图,callBack在加载完成后回调。设置图片的流程见 bitmap_sque.png。
5.3 BitmapLoadTask.java——加载异步任务
BitmapLoadTask是加载图片的异步任务,在doInBackground中读取图片资源(内存缓存 → 闪存缓存 → 网络/文件),避免在主线程执行解码与 IO。
5.4 BitmapCache.java——两级缓存
private LruDiskCache mDiskLruCache; //闪存缓存 private LruMemoryCache<MemoryCacheKey, Bitmap> mMemoryCache; //运存缓存BitmapCache持有一份闪存缓存(磁盘,LruDiskCache)和一份运存缓存(内存,LruMemoryCache),构成"内存优先、磁盘兜底"的两级缓存结构。主要函数:
//下载网络图片, 然后根据配置压缩图片, 将图片缓存。 public Bitmap downloadBitmap(String uri, BitmapDisplayConfig config, final BitmapUtils.BitmapLoadTask<?> task) //从运存缓存中读取 bitmap 在获取的时候会判断是否过期 public Bitmap getBitmapFromMemCache(String uri, BitmapDisplayConfig config) //从闪存缓存中读取 bitmap public Bitmap getBitmapFromDiskCache(String uri, BitmapDisplayConfig config)downloadBitmap:负责下载网络图片,然后根据配置压缩图片,并将图片写入缓存;getBitmapFromMemCache:从运存缓存中读取 bitmap,读取时会判断是否过期;getBitmapFromDiskCache:从闪存缓存中读取 bitmap。
注意缓存 key 由uri与BitmapDisplayConfig共同决定——同一张图片以不同尺寸显示时,会按不同的显示配置分别缓存,这与"显示配置影响压缩结果"的设计一致。
5.5 BitmapGlobalConfig.java——全局配置
BitmapGlobalConfig集中了线程池、缓存路径与缓存大小的配置:
//闪存缓存的路径 private String diskCachePath; //运存缓存的最大值 private int memoryCacheSize = 1024 * 1024 * 4; // 4MB //闪存缓存的最大值 private int diskCacheSize = 1024 * 1024 * 50; // 50M //从网络加载数据的线程池 private final static PriorityExecutor BITMAP_LOAD_EXECUTOR = new PriorityExecutor(DEFAULT_POOL_SIZE); //从闪存读取数据的线程池 private final static PriorityExecutor DISK_CACHE_EXECUTOR = new PriorityExecutor(2); //bitmap 缓存的的时间 private long defaultCacheExpiry = 1000L * 60 * 60 * 24 * 30; // 30 days //bitmap 缓存 private BitmapCache bitmapCache;关键默认值如下表:
| 配置项 | 默认值 | 说明 |
|---|---|---|
memoryCacheSize | 4 MB | 运存(内存)缓存最大值 |
diskCacheSize | 50 MB | 闪存(磁盘)缓存最大值 |
BITMAP_LOAD_EXECUTOR | DEFAULT_POOL_SIZE | 网络加载图片的线程池 |
DISK_CACHE_EXECUTOR | 2 线程 | 闪存读取的线程池 |
defaultCacheExpiry | 30 天 | bitmap 缓存过期时间 |
diskCachePath | 由调用方指定 | 闪存缓存路径 |
从配置可以看出两个设计意图:一是网络 IO 与磁盘 IO 使用独立线程池(分别控制并发度,磁盘读取只需少量线程),避免互相阻塞;二是缓存容量与过期时间都可调,便于针对不同机型与应用场景做内存权衡。
5.6 BitmapDisplayConfig.java——显示配置
//图片显示的大小 private BitmapSize bitmapMaxSize; //图片的动画 private Animation animation; // 图片加载过程中的显示图片 private Drawable loadingDrawable; // 图片加载失败的显示图片 private Drawable loadFailedDrawable; // 图片显示的配置色彩 private Bitmap.Config bitmapConfig = Bitmap.Config.RGB_565;BitmapDisplayConfig控制图片在 View 上的最终呈现效果:bitmapMaxSize限定显示尺寸(配合压缩策略降低内存占用),animation为加载完成后的展示动画,loadingDrawable与loadFailedDrawable分别是加载中与加载失败时的占位图,bitmapConfig默认使用RGB_565(每像素 2 字节)而非ARGB_8888(每像素 4 字节),在不需要透明通道的场景下可以显著减少内存占用。
5.7 DefaultDownloader.java——图片来源分发
DefaultDownloader负责获取 bitmap,支持三种获取路径:本地文件、资产(assets)文件、网络图片。调用方只需传入统一的uri,由它按 uri 前缀/协议自动路由到对应来源,对上层BitmapLoadTask屏蔽了来源差异。
5.8 DefaultBitmapLoadCallBack.java——默认加载回调
图片加载完成的回调接口,默认实现将获取的 bitmap 值直接传递给 View(即调用setImageBitmap完成展示),开发者可通过自定义回调在图片显示前做进一步处理。
6. 与 Volley 框架的对比
xUtils 与同为网络/图片框架的 Volley(仓库内亦有完整源码解析,见 Volley 源码解析)相比,可以从缓存、线程模型、数据流三个维度对比。
相同点
- 采用了网络数据缓存机制——两者都通过缓存减少重复网络请求;
- 通过 Handler 进行线程通信——异步结果都经由 Handler 回到主线程回调。
不同点
- 底层网络实现不同:Volley 的 Http 请求在 Android 2.3 版本之前通过 HttpClient,之后的版本通过 HttpURLConnection;xUtils 全部通过 HttpClient 请求网络(bitmap 模块图片下载是通过 HttpURLConnection)。HttpURLConnection 默认支持 GZIP 压缩,API 操作简单。
- 数据流转方式不同:Volley 将 Http 请求数据先缓存进
byte[],再分配给不同的请求转化为需要的格式;xUtils 是直接转化为想要的格式。由此带来各自的取舍——Volley 扩展性好,但不能存在大数据请求,否则会 OOM;xUtils 不缓存入byte[],支持大数据请求,速度比 Volley 稍快,但扩展性较低。 - 并发模型不同:Volley 访问网络数据时直接开启固定个数的线程访问网络,在
run方法中执行死循环、阻塞等待请求队列;xUtils 是开启线程池来管理线程(PriorityExecutor)。 - 缓存失效策略不同:Volley 的所有网络数据支持从 HTTP 响应头中控制是否缓存和读取缓存失效时间,每个请求可以控制是否缓存和缓存失效时间;xUtils 网络数据请求是统一的自定义缓存失效时间。
7. 小结
从源码结构看,xUtils 的四大模块共享同一套方法论:用注解做声明、用反射做解析、用缓存做加速、用线程池做并发控制。View 模块用"注解 + 动态代理"消灭 UI 绑定模板代码;Db 模块用"注解 + 反射"把实体类映射为表结构与 SQL;Http 模块在 HttpClient 之上统一注入 gzip 压缩并以线程池调度请求;Bitmap 模块则以"内存 + 闪存"两级缓存为核心实现图片的高效异步加载。理解这四个模块的组合方式,也就掌握了早期 Android 公共库在"减少样板代码、提升 IO 效率"上的典型工程实践。配套的 UML 设计文件 xUtils.mdl 与流程图、类图资源均保留在本仓库 tool-lib/common/xutils 目录下,可继续深入研读。
- 文档
- 教程
- 知识库
【免费下载链接】android-open-project-analysis
Analysis implementation of android open source project, 微信公众号:codekk, 网站:
相关推荐
xUtils3 框架全栈实践:ORM 数据库、HTTP(S) 网络、图片加载与 View 注解四大模块详解
xUtils3 框架全栈实践:ORM 数据库、HTTP S 网络、图片加载与 View 注解四大模块详解 xUtils3 是一个面向 Android 的轻量级工
移动开发数据库DeaDBeeF GTK与Cocoa界面对比:选择最适合你的UI
DeaDBeeF GTK与Cocoa界面对比:选择最适合你的UI 🎵 DeaDBeeF音乐播放器提供了两种主要的用户界面选择:跨平台的GTK界面和macOS原
音视频揭秘efaqa-corpus-zh数据结构:3大核心标签与多轮对话格式全解析
揭秘efaqa corpus zh数据结构:3大核心标签与多轮对话格式全解析 efaqa corpus zh是一个专业的心理咨询问答与聊天机器人语料库,包含丰富
数据集医疗健康NLP
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考