By 高焕堂
ee ee
【思考Android技术】
活用Android的ContentProvider通用接口,抽换SQLiteDB模块
1. 没钱就改版、改版就有钱
随着Android的普及到各行各业,Android的大型应用项目愈来愈多,各企业逐渐企盼建立自己的专业应用平台(Platform)和自己能掌控的API;并由自己的平台管理各种插件,透过迅速抽换插件来满足客户需求的改变、Android的改版,以及底层芯片的频繁更替。让自己的产品或系统能实践”没钱就改版、改版就有钱”的市场优势地位。Android的碎片化是大家最担心的事项之一,然而也是Android欣欣向荣、百花齐放的生命力光辉的展现。因此,如何有效运用Android这项特质、水涨船高,抓住机会展现自己产品的独特性和应变能力;更重要的是,确实掌握自己产品主动改版的主导权。欲拥有改版的主导权,就必须掌握框架API,包括应用框架和平台框架的API。尤其是通用型API(接口),它能有效维护底层模块的<变动自由度>,让提供底层模块的厂商能够实现<没钱就改版、改版就有钱>的有利商业策略。
2. 以DB引擎模块为利:确保变动的自由度
在沒有通用性接口保護的情形下,让各Client毫无限制地使用DB引擎的接口。这种接口,就DB引擎而言,都属于被动型API,受制各Client端,严重伤害DB引擎的变动自由度,局限了DB引擎的成长空间;如下圖:
此時,最常见的对策就是:DB引擎廠商(开发者)自己定义一个<接口类>,提供一个对外的接口,隐藏了DB引擎本身的接口;如下圖:
然而,这个<接口类>属于特殊性接口,不同DB引擎的厂商,都有专属的<接口类>;所以不是各DB引擎都适合的通用性接口。比較美好的絕對策是:提供通用性接口給Client端。例如:
这Cursor是Android框架所提供的通用性接口,让App(如Activity或Service等)能透过此Cursor接口来浏览DB里的各笔数据或内容。
3. 认识Cursor通用性接口
在Android里定义了一个Cursor接口,让App(如Activity或Service等)能透过此Cursor接口来浏览DB里的各笔数据或内容。
如果搭配SQLite(数据库)引擎的话,就可设计一个类来实现这Cursor接口。其实,Android里已经撰写了这个实现类,名叫:SQLiteCursor。
于是,Cursor成为Client端浏览SQLite数据库内容的标准接口了。
4. 如何使用Cursor接口
4.1 取得Cursor接口
撰写一个DataPersist类,来开启DB,并提供query()函数;在执行query()时就创建一个SQLiteCursor对象,将其Cursor接口回传给Client端。
4.2 使用Cursor接口
接下来,只要调用Cursor接口的函数,就能浏览DB里的内容了。
4.3 范例代码
由于Android平台里已经有了SQLite引擎了,而且已经写好了SQLiteCursor实现类。因此在这范例里,我们只需要DataPersist类和Activity的子类,就行了。
==> [ 范例代码-Ex01 ]
在ac01类里的指令:
DataPersist dp = new DataPersist(this);
此时,诞生一个DataPersist对象;并开启了DB。然后执行指令:
Cursor cur = dp.query(PROJECTION, null, null, null);
这查询出某些数据,创建Cursor对象,并回传之。
5. 如何使用ContentProvider接口(基类形式)
刚才的范例里,我们直接使用DataPersist类的接口来与SQLite沟通。本节将替DataPersist配上ContentProvider基类,让Client能透过ontentProvider新接口来沟通。
ContentProvider义了多个函数,包括:
• query()函数-- 它查询出合乎某条件的数据。
• insert()函数-- 它将存入一笔新资料。
• delete()函数-- 它删除合乎某条件的资料。
• update()函数-- 更新某些笔数据的内容。
DataPersist类实现query()接口,实际呼叫SQLite数据库的功能。也就是说,Client程序透过ContentProvider接口间接呼叫到DataPersist的query()函数,然后此query()函数才去查询SQLite的DB内容。查询出来,就诞生一个SQLiteCursor对象,并回传Cursor接口。让Client程序可藉由Cursor接口来浏览所查询出来的各笔数据。
6. 进一步优化架构设计--- 从DB引擎提供商的视角看
6.1 擅用EIT造形来提供主动型API
在上述的范例中,是由DataPersist类去开启和调用DB引擎的。
仅仅提供DB引擎的接口,还是不够的。因为这个SQLiteDatabase接口还是被DataPersist所调用的,尤其是DB引擎的开起任务。因而,DB引擎厂商提供接口,只是属于被动型API而已。于是,可擅用EIT造形来提供主动型API。
在Android里,这EIT造形的实现类别如下图所示。
SQLiteOpenHelper类就是<E>角色;而onCreate()就是<I>角色,提供了强势的主动型API。这对DB引擎厂商是有利的,因为SQLiteOpenHelper类有效保护了DB引擎的变动自由度;让DB引擎厂商能够实现”没钱就改版、改版就有钱”的商业策略。[歡迎光臨 高煥堂 網頁: http://www.cnblogs.com/myEIT/ ]
EIT造形只是在于实现主动型API和创造底层(如DB引擎)变动的自由度而已;它并没有改变DB数据的流动路径,所以没有降低数据的存取效率。
SQLiteDatabase接口类开启DB,并提供query()函数;在执行query()时就创建一个SQLiteCursor对象,将其Cursor接口回传给Client端。
Client程序藉由Cursor接口来浏览所查询出来的各笔数据。
这也就是目前Android在ContentProvider和DB引擎的整合架构设计了。
6.2 范例代码
==> [ 范例代码-Ex02 ]
7. 展现DB引擎的变换自由度(没钱就改版、改版就有钱):
7.1 以Linter引擎的移植为例
在上一个范例里,Client使用ContentProvider接口与SQLite DB引擎銜接。
這DataPersist对象是由Android框架所创建的。之后,Client就调用getContentResolver()函数来要求Android框架去进行配对和绑定此DataPersist对象。然后间接绑定了Linter DB引擎。此ContentProvider接口与特定DB引擎是无关的,可以让Client与DB引擎互为独立。非常有助于双方的独立成长,或各自的版本更新,甚至新DB引擎的移植。例如,我们先将Linter DB引擎安装到Android的Linux环境里,并建立JDBC存取通道;接着就我们能轻易地将Linter引擎整合到ContentProvider框架里。如下圖:
这包含了3个类:1) LinterPersist类, 2) LinterCursor类, 和3) DispActivity类。此LinterPersist对象是由Android框架所创建的。之后,Client就调用getContentResolver()函数来要求Android框架去进行配对和绑定此LinterPersist对象。然后间接绑定了Linter DB引擎。
LinterPersist类实现query()接口,实际呼叫Linter数据库的功能。也就是说,DispActivity透过ContentProvider接口呼叫到LinterPersist的query()函数。此时创建一个LinterCursor对象,将其Cursor接口回传给DispActivity 。
呼叫query()函数进行数据查询的任务,其查询出多笔的数据,查询之后,它会传回数据指针值(Record pointer)给LinterCursor,并将LinterCursor的Cursor接口回传给DispActivity程序。 由于在LinterPersist类里,我们定义了一个LinterCursor类,所以这指令所传回来的cursor是参考(Reference)到LinterCursor对象。
於是,DispActivity就能使用Cursor通用性接口來瀏覽查詢的結果了。
7.2 范例代码
于此,我们需要撰写3个类:1) LinterPersist类, 2) LinterCursor类, 和3) DispActivity类。
==> [ 范例代码-Ex03 ]
此DispActivity类呼叫了Linter的executeQuery()函数,要求Linter进行数据库的查询任务。Linter回传一个ResultSet对象,让应用程序可浏览所查询到的各笔数据。在这query()函数里,就诞生一个LinterCursor对象,让它内含该ResultSet对象,然后将此LinterCursor对象回传给DispActivity应用类。于是,顺利地将Linter数据库配上ContentProvider接口,飞上枝头变凤凰,成为Android嫡系成员。 终于完成我们的目:让远从万里之外的Linter舶来组件,顺利融入(移植到)Android之中,成为其嫡系成员之一。接著,在DispActivity類裡的指令:
Cursor cursor = getContentResolver().query( getIntent().getData(),PROJECTION, null, null, null);
透过getContentResolver()而要求Android寻找适当的ContentProvider实作类(如本范例的LinterPersist),并呼叫其query()函数,以进行数据查询的任务。其查询出多笔的数据,查询之后,它会传回数据指针值(Record pointer)给LinterCursor,并将LinterCursor的Cursor接口回传给DispActivity程序。由于在LinterPersist类里,我们定义了一个LinterCursor类,所以这指令所传回来的cursor是参考(Reference)到LinterCursor对象。我们把原来的 Cursor实作类抽换掉了,换为新的LinterCursor类,这些应用类(如DispActivity等)则丝毫不受影响,表现出高度的抽换性。 接下来,DispActivity只要调用Cursor接口的函数,就能浏览Linter DB里的内容了。例如指令:
cursor.moveToFirst();
就将DB的数据指针值(Record pointer)移到最前头。◆
[Go Back]