ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
本节将分析MediaProvider query函数。此次分析的焦点不是MediaProvider本身的业务逻辑,而是要搞清query函数返回的Cursor到底是什么,其代码如下,: **MediaProvider.java::query** ~~~ public Cursor query(Uri uri, String[]projectionIn, String selection, String[] selectionArgs, String sort) { int table= URI_MATCHER.match(uri); ...... //根据uri取出对应的DatabaseHelper对象,MediaProvider针对内部存储中的媒体文件和 //外部存储(即SD卡)中的媒体文件分别创建了两个数据库 DatabaseHelper database = getDatabaseForUri(uri); //我们在7.3.2节分析过getReadableDatabase函数的兄弟getWritableDatabase函数 SQLiteDatabase db = database.getReadableDatabase(); //创建一个SQLiteQueryBuilder对象,用于方便开发人员编写SQL语句 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); ......//设置qb的参数,例如调用setTables函数为本次查询设定目标Table //①调用SQLiteQueryBuilder的query函数 Cursor c =qb.query(db, projectionIn, selection, combine(prependArgs, selectionArgs), groupBy, null, sort, limit); if (c !=null) { //2调用该Cursor对象的setNotificationUri函数,这部分内容和ContentObserver //有关,相关内容留到第8章再进行分析 c.setNotificationUri(getContext().getContentResolver(), uri); } return c; } ~~~ 上边代码列出的两个关键点分别是: - 调用SQLiteQueryBuilder的query函数得到一个Cursor类型的对象。 - 调用Cursor类型对象的setNotificationUri函数。从名字上看,是为该对象设置通知URI。和ContentObserver有关的内容留到 第8章再进行分析。 来看SQLiteQueryBuilder的query函数。 1. SQLiteQueryBuilder的query函数分析 **SQLiteQueryBuilder.java::query** ~~~ public Cursor query(SQLiteDatabase db, String[]projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder, String limit) { ...... //调用buildQuery函数得到对应SQL语句的字符串 String sql= buildQuery( projectionIn, selection, groupBy, having, sortOrder, limit); /* 调用SQLiteDatbase的rawQueryWithFactory函数,mFactory是SQLiteQueryBuilder 的成员变量,初始值为null,本例也没有设置它,故mFactory为null */ returndb.rawQueryWithFactory( mFactory, sql, selectionArgs, SQLiteDatabase.findEditTable(mTables)); } ~~~ **SQLiteDatabase.java::rawQueryWithFactory** ~~~ public Cursor rawQueryWithFactory( CursorFactory cursorFactory, String sql, String[] selectionArgs, StringeditTable) { verifyDbIsOpen(); BlockGuard.getThreadPolicy().onReadFromDisk(); //数据库开发中经常碰到连接池的概念,其目的也是缓存重型资源。有兴趣的读者不妨自行 //研究一下面这个getDbConnection函数 SQLiteDatabase db = getDbConnection(sql); //创建一个SQLiteDirectCursorDriver对象 SQLiteCursorDriver driver = new SQLiteDirectCursorDriver( db, sql,editTable); Cursorcursor = null; try { //调用SQLiteCursorDriver的query函数 cursor = driver.query( cursorFactory != null ? cursorFactory : mFactory, selectionArgs); }finally { releaseDbConnection(db); } returncursor; ~~~ 以上代码中又出现一个新类型,即SQLiteCursorDriver,cursor变量是其query函数的返回值。 (1) SQLiteCursorDriver query函数分析 SQLiteCursorDriverquery函数的代码如下: **SQLiteCursorDriver.java::query** ~~~ public Cursor query(CursorFactory factory,String[] selectionArgs) { //本例中,factory为空 SQLiteQuery query = null; try { mDatabase.lock(mSql); mDatabase.closePendingStatements(); //①构造一个SQLiteQuery对象 query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs); //原来,最后返回的游标对象其真实类型是SQLiteCursor if(factory == null) {//②构造一个SQLiteCursor对象 mCursor = new SQLiteCursor(this, mEditTable, query); }else { mCursor =factory.newCursor(mDatabase, this, mEditTable, query); } mQuery = query; query = null; return mCursor;//原来返回的cursor对象其真实类型是SQLiteCursor }finally { if (query != null) query.close(); mDatabase.unlock(); } } ~~~ SQLiteCursorDriver的query函数的主要功能就是创建一个SQLiteQuery实例和一个SQLiteCursor实例。至此,我们终于搞清楚了MediaProvider 的query返回的游标对象其真实类型是SQLiteCursor。 * * * * * **注意**:这里的MediaProvider query指的是图7-5中的编号为8关键调用。 * * * * * 下面来看SQLiteQuery和SQLiteCursor为何方神圣。 (2) SQLiteQuery介绍 在图7-4中曾介绍过SQLiteQuery,它保存了和查询(即SQL的SELECT命令)命令相关的信息,其构造函数的代码如下: **SQLiteQuery.java::构造函数** ~~~ SQLiteQuery(SQLiteDatabase db, String query, intoffsetIndex, String[] bindArgs) { //注意,在本例中offsetIndex为0,offsetIndex的意义到后面再解释 super(db,query);//调用基类构造函数 mOffsetIndex = offsetIndex; bindAllArgsAsStrings(bindArgs); } ~~~ SQLiteQuery的基类是SQLiteProgram,其代码如下: **SQLiteProgram.java::构造函数** ~~~ SQLiteProgram(SQLiteDatabase db, String sql) { this(db,sql, null, true);//调用另外一个构造函数,注意传递的参数 } SQLiteProgram(SQLiteDatabase db, String sql,Object[] bindArgs, boolean compileFlag) { //本例中bindArgs为null,compileFlag为true mSql =sql.trim(); //返回该SQL语句的类型,query语句将返回STATEMENT_SELECT类型 int n =DatabaseUtils.getSqlStatementType(mSql); switch(n) { caseDatabaseUtils.STATEMENT_UPDATE: mStatementType = n | STATEMENT_CACHEABLE; break; caseDatabaseUtils.STATEMENT_SELECT: /* mStatementType成员变量用于标示该SQL语句的类型,如果该SQL语句 是STATEMENT_SELECT类型,则mStatementType将设置STATEMENT_CACHEABLE 标志。该标志表示此对象将被缓存起来,以避免再次执行同样的SELECT命令时重新构造 一个对象 */ mStatementType = n | STATEMENT_CACHEABLE | STATEMENT_USE_POOLED_CONN; break; ......//其他情况处理 default: mStatementType = n; } db.acquireReference();//增加引用计数 db.addSQLiteClosable(this); mDatabase= db; nHandle =db.mNativeHandle;//此mNativeHandle对应一个SQLite3实例 if(bindArgs != null) ......//绑定参数 //complieAndBindAllArgs将为此对象绑定一个sqlite3_stmt实例,native层对象的指针 //保存在nStatement成员变量中 if(compileFlag) compileAndbindAllArgs(); } ~~~ 来看compileAndbindAllArgs函数,其代码是: **SQLiteProgram.java::compileAndbindAllArgs** ~~~ void compileAndbindAllArgs() { ...... //如果该对象还未和native层的sqlite3_stmt实例绑定,则调用compileSql函数 if(nStatement == 0) compileSql(); ......//绑定参数 for(int index : mBindArgs.keySet()) { Object value = mBindArgs.get(index); if (value == null) { native_bind_null(index); }......//绑定其他类型的数据 } } ~~~ compileSql函数将绑定Java层SQLiteQuery对象到一个Native的sqlite3_stmt实例。根据前文的分析,这个绑定是通过SQLiteCompileSql对象实施的,其相关代码如下: **SQLiteProgram.java::compileSql** ~~~ private void compileSql() { //如果mStatementType未设置STATEMENT_CACHEABLE标志,则每次都创建一个 // SQLiteCompiledSql对象。根据7.3.2节小标题3中的分析,该对象会真正和native层的 //sqlite_stmt实例绑定 if((mStatementType & STATEMENT_CACHEABLE) == 0) { mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql); nStatement = mCompiledSql.nStatement; return; } //从SQLiteDatabase对象中查询是否已经缓存过符合该SQL语句要求的SQLiteCompiledSql //对象 mCompiledSql = mDatabase.getCompiledStatementForSql(mSql); if(mCompiledSql == null) { //创建一个新的SQLiteCompiledSql对象,并把它保存到mDatabase中 mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql); mCompiledSql.acquire(); mDatabase.addToCompiledQueries(mSql, mCompiledSql); } ...... //保存Native层sqlite3_stmt实例指针到nStatement成员变量 nStatement= mCompiledSql.nStatement; } ~~~ 通过以上分析可以发现,SQLiteQuery将和一个代表SELECT命令的sqlite3_stmt实例绑定。同时,为了减少创建sqlite3_stmt实例的开销,SQLiteDatabase框架还会把对应的SQL语句和对应的SQLiteCompiledSql对象缓存起来。如果下次执行同样的SELECT语句,那么系统将直接取出之前保存的SQLiteCompiledSql对象,这样就不用重新创建sqlite3_stmt实例了。 * * * * * **思考**:与直接使用SQLite API相比,SQLiteDatabase框架明显考虑了更多问题。不知读者自己封装C++ SQLite库时是否想到了这些问题? * * * * * (3) SQLiteCursor分析 再来看SQLiteCursor类,其构造函数的代码如下: **SQLiteCursor.java::构造函数** ~~~ public SQLiteCursor(SQLiteCursorDriver driver,String editTable, SQLiteQuery query) { ...... mDriver =driver; mEditTable = editTable; mColumnNameMap = null; mQuery =query;//保存此SQLiteQuery对象 query.mDatabase.lock(query.mSql); try { //得到此次执行query得到的结果集所包含的列数 intcolumnCount = mQuery.columnCountLocked(); mColumns = new String[columnCount]; for(int i = 0; i < columnCount; i++) { String columnName = mQuery.columnNameLocked(i); //保存列名 mColumns[i] = columnName; if ("_id".equals(columnName)) { mRowIdColumnIndex = i;//保存“_id”列在结果集中的索引位置 } } }finally { query.mDatabase.unlock(); } } ~~~ SQLiteCursor比较简单,此处不再详述。 2. Cursor分析 至此,我们已经知道MediaProvider query返回的游标对象的真实类型了。现在,终于可以请Cursor家族登台亮相了,如图7-6所示。 :-: ![](http://img.blog.csdn.net/20150803130933944?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图7-6 Cursor家族 图7-6中元素较多,包含的知识点也较为复杂,因此必须仔细阅读下文的解释。 通过7.3.1节中SQLiteTest示例可知,query查询(即SELECT命令)的结果和一个Native sqlite_stmt实例绑定在一起,开发者可通过遍历该sqlite_stmt实例得到自己想要的结果(例如,调用sqlite3_step遍历结果集中的行,然后通过sqlite3_column_xxx取出指定列的值)。查询结果集可通过数据库开发技术中一个专用术语——游标(Cursor)来遍历和获取。图7-6的左上部分是和Cursor有关的类,它们包括:接口类Cursor和CrossProcessCursor、抽象类AbstractCursor、AbstractWindowCursor,以及真正的实现类SQliteCursor。根据前面的分析,SQLiteCursor内部保存一个已经绑定了sqlite3_stmt实例的SQLiteQuery对象,故读者可简单地把SQLiteCursor看成是一个已经包含了查询结果集的游标对象,虽然此时还并未真正执行SQL语句。 如上所述,SQLiteCursor是一个已经包含了结果集的游标对象。从进程角度看,query的结果集目前还属于MediaProvider所在的进程,而本次query请求是由客户端发起的,所以一定要有一种方法将MediaProvider中的结果集传递到客户端进程。数据传递使用的技术很简单,就是大家耳熟能详的共享内存技术。SQLite API没有提供相关的功能,但是SQLiteDatabase框架对跨进程数据传递进行了封装,最终得到了图7-6左上部分的CursorWindow类。其代码中的注释明确表明了CursorWindow的作用:它是“A buffer containing multiplecursor rows”。 认识了CursorWindow,相信读者也能猜出query中数据传递的大致流程了。 MediaProvider将结果集中的数据存储到CursorWindow的共享内存中,然后客户端将其从共享内存中取出来即可。 上述流程描述是对的,但实际过程并非如此简单,因为SQLiteDatabase框架希望客户端看到的不是共享内存,而是一个代表结果集的游标对象,就好像客户端查询的是本进程中的数据库一样。由于存在这种要求[^①],Android构造了图7-6右下角的类家族。 其中,最重要的两个类是CursorToBulkCursorAdaptor和BulkCursorToCursorAdatpor。从名字上看,它们采用了设计模式中的Adaptor模式;从继承关系上看,这两个类将参与跨进程的Binder通信(其中客户端使用的BulkCursorToCursorAdaptor通过mBulkCursor与位于MediaProvider所在进程的CursorToBulkCursorAdaptor通信)。这两个类中最重要的是onMove函数,以后我们碰到时再作分析。 另外,图7-6中右上角部分展示了CursorWrapperInner类的派生关系。CursorWrapperInner类是ContentResolver query函数最终返回给客户端的游标对象的类型。CursorWrapperInner的目的应该是拓展CursorToBulkCursorAdaptor类的功能。 Cursor家族有些复杂。笔者觉得,目前对Cursor的架构设计有些过度(over-designed)。这不仅会导致我们分析时困难重重,并且也会对实际代码的运行效率造成一定损失。 下面我们将焦点放到跨进程的数据传输上。 [^①]:此处应该还存在其他方面的设计考虑,希望读者能参与讨论。