Android-从数据库到Content Provider

Android-从数据库到Content Provider

在Android中,使用SQLite可以为应用程序创建完全封装的关系数据库。使用这些数据库可以存储和管理复杂的、结构化的应用层序数据。而且,所有的数据库都是私有的,只能被创建它们的应用程序访问。

ContentProvider提供了一种基于使用content://模式的简单URI寻址模型来发布和使用数据的接口。它们允许将应用层从底层数据层中分离,通过抽象底层数据源使应用程序不必依赖于某个数据源。

更为强大的是,我们可以在应用程序之间共享ContentProvider,通过查询ContentProvider来获取结果,更新或删除ContentProvider中的记录,以及在其中添加新记录。
任何具有合适权限的应用程序都可以添加、删除或更新其他任意应用程序中的数据,包括本地Android ContentProvider中的数据。

我们今天来总结一下Android中的数据库和ContentProvider的基本使用。

使用SQLite数据库

SQLiteOpenHelper

使用数据库的时候,我们需要一个工具,来帮助我们管理数据库,当我们需要创建、打开、升级一个数据库的时候,就需要这个工具来帮忙,在Android中,这个工具就是SQLiteOpenHelper类。
SQLiteOpenHelper是一个抽象类,我们通过实现这个类,可以获得创建、打开、升级数据库的最佳实践模式。
SQLiteOpenHelper类可以帮忙我们做很多我们不必要浪费精力在上面的事情,比如,我们在打开数据库的时候,是否要判断这个数据库是否存在,以及数据库是否过期需要升级等等操作,有了SQLiteOpenHelper,我们就可以在使用数据库的时候,直接向SQLiteOpenHelper要就可以,把我们的注意力都放在有价值的地方。
如何实现SQLiteOpenHelper,我们来看一个Demo:

public class MySQLite extends SQLiteOpenHelper {

    public MySQLite(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }
    public MySQLite(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
        super(context, name, factory, version, errorHandler);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {

    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

通过上面的代码,我们可以看到,为了实现SQLiteOpenHelper类,我们需要重写两个重要的方法,那就是onCreate和onUpgrade,下面我们分别说一下这两个方法。

onCreate
当我们第一次创建数据库的时候,就需要调用这个方法,我们可以看到这个方法有一个参数SQLiteDatabase,没错,这就是系统为我们创建的数据库,我们需要拿到系统创建的数据库,然后在onCreate方法里面,初始化我们的数据表。
onUpgrade
当我们的数据库发生变化的时候,比如我们的某个数据表增加或删除了某些列,这个时候,我们就需要升级我们的数据库,当然,不必担心,升级的这个过程不用我们自己操心,SQLiteOpenHelper会帮我们做好,我们只需要做一小点事情就好,那就是,我们需要在onUpgrade方法里面,把涉及到变化的表删除,然后重建。一般情况下,我们都是把所有的数据表都删除,然后直接调用onCreate方法,再把表重建一遍即可。第一个参数SQLiteDatabase正是我们需要升级的数据库,第二三个参数,就是我们升级前和升级后的数据库版本号。

通过前面对onCreate和onUpgrade方法的介绍,我们对SQLiteOpenHelper有了一个初步的认识,我们看到,SQLiteOpenHelper还有几个构造函数,我们就拿第一个来说一下。

public MySQLite(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) 
  1. 第一个参数,是需要传进来的应用程序上下文,我们需要使用它来打开或者创建一个新的数据库
  2. 第二个参数,就是我们的数据库的名字,也是我们数据库文件的名字。如果这个参数为null的话,就说明要创建的是一个内存中的数据库。
  3. 第三个参数,按照文档的说法,是用来创建cursor对象的,如果为null的话,就执行默认行为。平时写代码没有使用过这个参数。。。
  4. 第四个参数,就是数据库的版本号,如果我们提供的版本号比现在数据库的新,那么就会执行升级数据库的操作,如果旧,就会对现在的数据库进行降级操作。

数据库的增删改查

通过上面我们对SQLiteOpenHelper的介绍,我们已经知道了这个管理工具的基本使用方法,下面,我们就来继续介绍一下数据库的增删改查,不过在这之前,我们需要先介绍几个必备的知识。

Content Value

当我们需要向数据库中插入新的行的时候,我们就需要用Content Value来包装我们数据,每一个Content Value对象都将一个表行表示为列名到值的映射。我们可以通过ContentValue对象的put方法,来将数据以键值对的方式添加到ContentValue中。

Cursor

当我们查询数据后,我们拿到的结果,并不是那么完美的,意思是什么呢,我们如果查询一个人的信息,People,我们并不能直接拿到People对象的结果,Android数据库查询是作为Cursor对象返回的。Cursor是底层数据中的结果集的指针,Cursor类包含了多个导航函数,我们可以通过这些导航函数,在结果集中遍历,拿到我们需要的值。

怎样执行

知道怎么填装数据和获得数据了,我们到底怎样来执行操作呢,比如在sql中,我们查询一个东西,需要sql语句,然后通过管理工具执行就好了,但是在Android中我们怎样去执行一个“sql”语句呢。
这就到了SQLiteOpenHelper发挥威力的时候了,这个时候,我们需要通过SQLiteOpenHelper,来获取一个可以使用的SQLiteDatabase,SQLiteDatabase中包含了插入,查询,删除等操作的方法,我们只需要把相应的参数给他,就可以执行各种操作了。
比如,我们来分析一个插入的操作

long insert (String table, String nullColumnHack, 
                ContentValues values)

当我们使用SQLiteDatabase执行插入操作时,我们就需要使用insert方法,我们可以看到,这个方法包含了三个参数:

  1. 第一个参数,就是我们需要插入的表名
  2. 第二个参数,这个是什么意思呢,如果我们插入的values是个完全空的值,这个时候,Android数据库不知道应该把这个空值给谁,可能我们有好几个列允许空,也可能没有列允许空,这个时候就要出错啦。但是,我们可以提供一个参数,当values完全为空的时候,Android数据库就把这个空值给我们提供的列。
  3. 第三个参数,就是我们封装好的需要插入的数据了。

好了,有了上面两个基础知识,我们就可以来看一下数据库的增删改查操作了。

插入
//封装数据
ContentValues newValues = new ContentValues();
newValues.put(key, value);
[...把所有需要插入的值都填到newValues里面]
//获取数据库
SQLiteDatabase db = MySQLite.getWritableDatabase();
//插入
db.insert(tableName, null, newValues);
查询
//用数组的形式,指定我们需要查询的列集
String[] columns = new String[]{key1, key2, key3, ....};
//指定我们的查询条件
String where = key_id + " = 1";
String whereArgs[] = null;
String groupBy = null;
String having = null;
String order = null;
//获取数据库
SQLiteDatabase db = MySQLite.getWritableDatabase();
//查询
Cursor result = db.query(tableName, columns, where,
whereArgs, groupBy, having, order);
//提取值
List<Project> result = new ArrayList<>();
while(resultCursor.moveToNext()){
     Project project = new Project(
             resultCursor.getInt(0),
             resultCursor.getString(1),
             resultCursor.getString(2),
             [...把我们查询的结果都提取出来...]
     );
     result.add(project);
 }

查询的时候,需要注意的是,我们需要把一个完整的sql语句,分隔开成几个参数,要理解好这一点。

删除
//指定删除的条件
String where = key_id + " = 1";
String whereArgs[] = null;
//获取数据库
SQLiteDatabase db = MySQLite.getWritableDatabase();
//执行删除
db.delete(tableName, where, whereArgs);
更新
//封装需要更新的数据
ContentValues updateValues = new ContentValues();
updateValues.put(key, value);
[...把所有需要插入的值都填到newValues里面]
//指定更新的条件
String where = key_id + " = 1";
String whereArgs[] = null;
//获取数据库
SQLiteDatabase db = MySQLite.getWritableDatabase();
//插入
db.update(tableName, updateValues, where, whereArgs);

由于操作十分简单,我这里只是用伪代码的形式展示了一下,大家有什么疑问,可以参看一下稳定,Android数据库的操作,还是十分简单的。

使用ContentProvider

上面,我们已经介绍了Android中数据库的使用,操作十分简单,但是有点繁琐,因为我们需要和sql语句打交道,如果某个大神,感兴趣,可以自己实现一套Android数据库的orm框架,这样的话应该比较理想。

接下来我们要介绍Android里面的一个重磅内容了,那就是ContentProvider。通过ContentProvider机制,Android向我们展示了一个数据共享的世界。很牛逼啊。

ContentProvider提供了一个接口用来发布数据,通过ContentResolver来访问这些数据。它们允许将使用数据的应用程序组件和底层的数据源分开,并提供了一种通用的机制来允许一个应用程序共享它们的数据或者使用其他应用程序提供的数据。

那么通过上面的描述,很晕,到底什么是ContentProvider,什么是ContentResolver呢,ContentProvider其实就是一个包装,实现了对底层数据源的包装,然后在这个包装之上,提供接口,凡是拿到接口的应用程序,在达到安全机制的允许的条件下,就可以通过ContentResolver来访问底层的数据源,达到Android世界应用之间共享数据的目的。



下面我们来看几个关于ContentProvider的知识点。

创建ContentProvider

创建ContentProvider,我们通常需要为每一个数据表,都创建一个相应的ContentProvider,通过继承ContentProvider类,重写必须的方法,我们就能创建一个自己的ContentProvider。

下面,我们来看一个Demo。

public class TeamContentProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}

我们创建了一个TeamProvider,这个Provider就是对我们team表的一个包装(假如你现在系统中有一个表叫team),我们所需要做的,就是在TeamContentProvider里面,实现team数据表的增删改查操作,这样,外界的ContentResolver拿到TeamContentProvider的接口后,就能对team表的数据进行访问了。

我们可以看到,里面有四个重要的方法,是对应着增删改查的,我们只需要在这里面实现好正删改查的代码,就能在任何地方,需要对team执行操作的时候,直接操作就OK了。

我们这里面是空的代码,我并没有写好,因为这里的增删改查的代码,和前面我们在介绍SQLiteOpenHelper的增删改查的时候的代码,几乎是一样的。我们文章末尾会给大家提供一个开源的云笔记app,大家可以参考那里面的代码去学习。

注册ContentProvider

前面,我们已经创建好了一个TeamContentProvider,这是我们创建的一个对应着我们team表的ContentProvider,我们要想使用它,就必须把它的诞生公诸于世,不然,是没有人知道它的存在的,为了让它被人发现并使用,我们需要对他进行注册。

我们前面提到了,ContentProvider其实就是一个包装器,实现了对底层数据源的包装,然后在这个包装之上,提供接口,凡是拿到接口的应用程序,在达到安全机制的允许的条件下,就可以通过ContentResolver来访问底层的数据源。

没错,你没看错,就是那个接口,我们这里注册的,就是这个接口。

无论是想达到共享的目的,还是就是我们自己想访问自己的数据源,我们都要先拿到这个接口,然后把这个接口给ContentResolver,然后才能通过ContentResolver访问底层的数据源。

那么到底应该怎么去注册呢,想象一下Android中最常用的配置文件是谁,恩,就是AndroidManifest.xml,我们正是需要在这个文件中,去注册接口。具体的注册语法如下:

<provider   android:authorities="com.linukey.Todo.team"            android:name="com.example.linukey.data.source.local.TeamContentProvider"/>

需要一个provider标记,name属性是存放实现了ContentProvider的类信息,authorities属性差不多就是一个所有权的标记。

这样,我们就实现了接口的注册,至于怎样使用,我们接下来再看。

发布ContentProvider地址

嗯,上面,我们已经注册好了接口,但是接下来问题又来了,我们在需要使用接口的时候,怎样拿到接口呢,现在的接口可只是一个在AndroidManifest.xml配置文件中的一个标记啊。嗯,不要慌,我们可以发布ContentProvider地址,这样使用的时候,我们把发布的地址拿过来,就能直接用了。

不怕你笑,说的高大上的地址发布,其实就是下面这一行代码

public static final Uri ContentUri_team = Uri.parse("content://com.linukey.Todo.team");

是的,每个ContentProvider都应该使用一个公有的静态的Uri属性来公开它的授权,不是静态的能行吗,当然可以,但是静态的能让它更容易被获取,你应该不希望单单只是为了获取一个Uri,就去实例化一个类吧,或者更恐怖的是你想在每一个使用它的地方都创建一个属性。。。

这样,我们就把ContentProvider接口发布好了。

通过ContentResolver,访问底层数据源

我们前面经历了三个过程,创建、注册、发布,下面,终于到了使用了。

我们可以通过ContentResolver,拿到我们注册好的接口,来访问我们的底层数据源。

废话不多说,看Demo。

    public static int saveTeam(Team team, Context context){
        //我们仍然需要使用ContentValues封装数据
        ContentValues contentValues = new ContentValues();
        contentValues.put(key, value);
        contentValues.put(key, value);
       //通过上下文获取ContentResolver
        ContentResolver cr = context.getContentResolver();
        //第一个参数,便是我们发布的接口
        //第二个参数,就是我们需要插入的行的数据
        Uri myRowUri = cr.insert(ContentUri_team, contentValues);
        if(myRowUri != null)
            return Integer.parseInt(myRowUri.getPathSegments().get(0));
        return -1;
    }

我们上面演示了一个需要保存team信息的场景,一般,增删改查操作,我们需要做下面几个步骤:

  1. 如果是插入或者更新,用ContentValues封装要插入或更新的数据
  2. 如果是更新或者删除,查询,构建条件语句
  3. 通过上下文环境拿到ContentResolver
  4. 通过ContentResolver的增删改查方法,来执行操作

看了我们上面的插入Demo,相信大家已经对ContentResolver的使用有了一个明确的认识,剩下的三个操作也很相似,我就不再继续演示了。

总结

我们通过这篇文章,对Android中的数据库,以及ContentProvider有了一个基本的认识,我们这里介绍的只是他们的初级用法,像CursorLoader异步查询,还有搜索功能,我们都没有提到,大家感兴趣的可以自己查阅文档,深入的去了解。

最后,我们提供一个开源的云笔记软件作为这部分学习的Demo,大家学习这部分内容的时候,可以去参考这个云笔记系统里面的代码学习。

地址:https://github.com/linukey/Todo

转载请注明地址:http://blog.csdn.net/linukey/article/details/64189951
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值