Android数据库与ContentProvider

2021/9/28 19:12:32

本文主要是介绍Android数据库与ContentProvider,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

数据库基本概念

数据库是“按照数据结构来组织、存储和管理数据的仓库”。是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。

数据库的种类

最常用的数据库模型主要是两种,即关系型数据库和非关系型数据库。

关系型数据库

关系型数据库模型是把复杂的数据结构归结为简单的二元关系(即二维表格形式)。

Oracle

MySQL

SQL Server

Access数据库

非关系型数据库(NoSQL)

NOSQL为了高性能、高并发而生,忽略影响高性能,高并发的功能

Redis 键值(Key-Value)存储数据库

MorgoDB  面向文档(Document-Oriented)

SQLite 轻量级关系型数据库

SQLite是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。

优点:

不需要一个单独的服务器进程或操作的系统(无服务器的)。

SQLite 不需要配置,这意味着不需要安装或管理。

一个完整的 SQLite 数据库是存储在一个单一的跨平台的磁盘文件。

SQLite 是非常小的,是轻量级的,完全配置时小于 400KiB,省略可选功能配置时小于250KiB。

SQLite 是自给自足的,这意味着不需要任何外部的依赖。

SQLite 事务是完全兼容 ACID 的,允许从多个进程或线程安全访问。

SQLite 支持 SQL92(SQL2)标准的大多数查询语言的功能。

SQLite 使用 ANSI-C 编写的,并提供了简单和易于使用的 API。

SQLite 可在 UNIX(Linux, Mac OS-X, Android, iOS)和 Windows(Win32, WinCE, WinRT)中运行。

缺点:

在 SQLite 中,SQL92 不支持的特性如下所示:

特性

描述

RIGHT OUTER JOIN

只实现了 LEFT OUTER JOIN。

FULL OUTER JOIN

只实现了 LEFT OUTER JOIN。

ALTER TABLE

支持 RENAME TABLE 和 ALTER TABLE 的 ADD COLUMN variants 命令,

不支持 DROP COLUMN、ALTER COLUMN、ADD CONSTRAINT。

Trigger 支持

支持 FOR EACH ROW 触发器,但不支持 FOR EACH STATEMENT 触发器。

VIEWs

在 SQLite 中,视图是只读的。您不可以在视图上执行 DELETE、INSERT 或 UPDATE 语句。

GRANT 和 REVOKE

可以应用的唯一的访问权限是底层操作系统的正常文件访问权限。

DROP

删除整个表,或者表的视图,或者数据库中的其他对象。

example:

创建新表:

db.execSQL("CREATE TABLE " + BvProvider.BvNoteRecordColumns.TABLE_NAME + " ("
        + BvProvider.BvNoteRecordColumns._ID + " INTEGER PRIMARY KEY,"
        + BvProvider.BvNoteRecordColumns.INDEX + " INTEGER,"
        + BvProvider.BvNoteRecordColumns.TITLE + " TEXT,"
        + BvProvider.BvNoteRecordColumns.EDIT_TIME + " BIGINT,"
        + BvProvider.BvNoteRecordColumns.ALARM_TIME + " BIGINT,"
        + BvProvider.BvNoteRecordColumns.BGCOLOR + " TEXT,"
        + BvProvider.BvNoteRecordColumns.THUMBNAIL + " TEXT,"
        + BvProvider.BvNoteRecordColumns.PROFILE + " TEXT,"
        + BvProvider.BvNoteRecordColumns.ENCRYPT_FLAG + " INTEGER default 0,"
        + BvProvider.BvNoteRecordColumns.DELETE_FLAG + " INTEGER default 0,"
        + BvProvider.BvNoteRecordColumns.DELETE_TIME + " BIGINT default 0"
        + ");");

删除表:

db.execSQL("DROP TABLE IF EXISTS " + BvProvider.BvNoteRecordColumns.TABLE_NAME);

修改表:

ALTER TABLE Persons ADD Birthday bitint

表记录操作(行):

命令

描述

INSERT

创建一条记录。

UPDATE

修改记录。

常用命令:

表操作

命令

描述

CREATE

创建一个新的表,一个表的视图,或者数据库中的其他对象。

ALTER

修改数据库中的某个已有的数据库对象,比如一个表。

DELETE

删除记录。

example:

写入记录:

db.execSQL("insert into "+BvProvider.BvTypeColumns.TABLE_NAME+"("
        +BvProvider.BvTypeColumns.TYPE_NAME+","
        +BvProvider.BvTypeColumns.TYPE_CLASS+","
        +BvProvider.BvTypeColumns.TYPE_ORDER
        +") values('lift','-100','0')");

修改记录:

update records set title="mytitle" where _id=1;

删除记录:

delete from records where _id=1;

查询(表记录)

命令

描述

SELECT

从一个或多个表中检索某些记录。

 

select _id 序号, title 标题,edittime 编辑时间 from records;

序号|标题|编辑时间

2|不同的人,即使站在同一个地方,|1629806241443

其他关键字:

Distinct | Order By | Limit | Group By | Having

example:

SELECT * FROM COMPANY GROUP BY name HAVING count(name) < 2;

数据库-数据查看

adb shell

cd /data/user/0/com.blackview.notepad/databases

BL6000Pro:/data/user/0/com.blackview.notepad/databases # ls

bvnote.db  bvnote.db-journal

qlite3 bvnote.db

SQLite version 3.28.0 2020-05-06 18:46:38

Enter ".help" for usage hints.

sqlite> .tables

android_metadata  records           type_records      types

sqlite> .headers on

_id|record_index|title|edittime|alarmtime|bgcolor|thumbnail|profile|encrypt_flag|delete_flag|deletetime

1|1629805946|以后哼哼唧唧|1629806012052|-1|0|/storage/emulated/0/Android/data/com.blackview.notepad/files/Pictures/1629805946/thumbnail/1629805946.png|以后哼哼唧唧

[image]

可视化工具:

  • 安装sqlite3

地址:smb://192.168.11.109/develop/software/sqlite-autoconf-3360000.tar.gz

1、./configure

2、make

3、sudo make install

二、安装 可视化工具 sqliteman sqlitebrowser

Android ContentProvider

ContentProvider(内容提供者)是 Android 的四大组件之一,管理 Android 以结构化方式存放的数据,以相对安全的方式封装数据(表)并且提供简易的处理机制和统一的访问接口供其他程序调用。

  • 基础概念

URI(Universal Resource Identifier)统一资源定位符

ContentResolver 通过 uri 来定位自己要访问的数据

// 规则

[scheme:][//host:port][path][?query]

// 示例

content://com.blackview.notepad/records/id:

public static final String AUTHORITY = "com.blackview.notepad";

public static final String TABLE_NAME = "records";
public static final Uri CONTENT_URI = Uri.parse("content://"+ AUTHORITY +"/"+TABLE_NAME);

1、标准前缀(scheme)——content://,用来说明一个Content Provider控制这些数据;

2、URI 的标识 (host:port)—— com.wang.provider.myprovider,用于唯一标识这个 ContentProvider,外部调用者可以根据这个标识来找到它。对于第三方应用程序,为了保

证 URI 标识的唯一性,它必须是一个完整的、小写的类名。这个标识在元素的authorities属性中说明,一般是定义该 ContentProvider 的包.类的名称;

3、路径(path)——tablename,通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义,记得在使用的时候保持一致就可以了;

4、记录ID(query)——id,如果URI中包含表示需要获取的记录的 ID,则返回该id对应的数据,如果没有ID,就表示返回全部;

MIME

MIME 类型 通常用来说明文件的类型。

比如 : 在http协议中

text/html

text/css

text/xml

application/vnd.android.package-archive

文件扩展名:.apk

MIME类型:application/vnd.android.package-archive

方便数据接收者,做相应的处理。

//集合类型

public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.bv.note";//vendor-specific

//item类型
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.bv.note";//vendor-specific

UriMatcher

UriMatcher 类用于匹配 Uri

example:

private static final int RECORDS = 1;

sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// content://com.blackview.notepad/records
sUriMatcher.addURI(BvProvider.AUTHORITY, BvProvider.BvNoteRecordColumns.TABLE_NAME, RECORDS);

sUriMatcher.addURI(BvProvider.AUTHORITY,

// content://com.blackview.notepad/records/index/10

BvProvider.BvTypeAndRecordColumns.TABLE_NAME+"/index/#",TYPE_RECORD_INDEX );

@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
    SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    int count;
    switch (sUriMatcher.match(uri)) {
        case RECORDS: {
            count =    db.update(BvProvider.BvNoteRecordColumns.TABLE_NAME, values, where, whereArgs);
        }
            break;

  • ContentProvider 主要方法

ContentProvider 是一个抽象类,如果我们需要开发自己的内容提供者我们就需要继承这个类并复写其方法,需要实现的主要方法如下:

public boolean onCreate():在创建 ContentProvider 时使用

public Cursor query():用于查询指定 uri 的数据返回一个 Cursor

public Uri insert():用于向指定uri的 ContentProvider 中添加数据

public int delete():用于删除指定 uri 的数据

public int update():用户更新指定 uri 的数据

public String getType():用于返回指定的 Uri 中的数据 MIME 类型

  • 创建ContentProvider

1、在AndroidManifest.xml中声明

<provider android:name="com.blackview.notepad.providers.BvNoteProvider"
    android:authorities="com.blackview.notepad" />

  1. 新建一个类继承ContentProvider 

public class BvNoteProvider extends ContentProvider {

    private final String TAG=getClass().getSimpleName();

...

@Override
public boolean onCreate() {
    LogUtil.Logd(TAG,"BvNoteProvider onCreate....");
    mOpenHelper = new DatabaseHelper(getContext());
    SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    db.close();
    return true;
}

...

}

public class DatabaseHelper extends SQLiteOpenHelper {

...

@Override
public void onCreate(SQLiteDatabase db) {
    LogUtil.Logd(TAG,"DatabaseHelper onCreate...");

    db.execSQL("CREATE TABLE " + BvProvider.BvNoteRecordColumns.TABLE_NAME +

...

}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    LogUtil.Logd(TAG,"onUpgrade...");
    db.execSQL("DROP TABLE IF EXISTS " + BvProvider.BvNoteRecordColumns.TABLE_NAME);
    db.execSQL("DROP TABLE IF EXISTS " + BvProvider.BvTypeColumns.TABLE_NAME);
    db.execSQL("DROP TABLE IF EXISTS " + BvProvider.BvTypeAndRecordColumns.TABLE_NAME);
    onCreate(db);
}

  1. 重写主要方法

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                    String sortOrder) {
    SQLiteQueryBuilder qb = new SQLiteQueryBuilder();


    switch (sUriMatcher.match(uri)) {
        case RECORDS:
            qb.setTables(BvProvider.BvNoteRecordColumns.TABLE_NAME);
            qb.setProjectionMap(sRecordProjectionMap);
            break;

...


// content://com.blackview.notepad/records/index/10

  case RECORD_INDEX:
            qb.setTables(BvProvider.BvNoteRecordColumns.TABLE_NAME);
            qb.setProjectionMap(sRecordProjectionMap);
            qb.appendWhere(BvProvider.BvNoteRecordColumns.INDEX + "=" + uri.getPathSegments().get(2));
            break;

说明:

getPathSegments()方法,返回的是一个元素为String的List

。集合里是依次截取Uri内的字符串的,List集合的下标从0开始。第一个元素为第一个“/”右边的字符。

sTypeProjectionMap = new HashMap<String, String>();
sTypeProjectionMap.put(BvProvider.BvTypeColumns._ID, BvProvider.BvTypeColumns._ID);
sTypeProjectionMap.put(BvProvider.BvTypeColumns.TYPE_NAME,BvProvider.BvTypeColumns.TYPE_NAME);
sTypeProjectionMap.put(BvProvider.BvTypeColumns.TYPE_CLASS,BvProvider.BvTypeColumns.TYPE_CLASS);
sTypeProjectionMap.put(BvProvider.BvTypeColumns.TYPE_ORDER,BvProvider.BvTypeColumns.TYPE_ORDER);

sTypeProjectionMap 用于把要查询的字段放在这里,并填写对应的别名。最终组成的sql语句,形如:

select _id 序号, title 标题,edittime 编辑时间 from records;

其中 _id 就是原表中的列名,序号 就是查询结果中,把_id重新命名为 序号

  1. 调用

public static BvNoteRecord getNoteRecordFromDbByIndex(Context context,int index){
    ContentResolver contentResolver = context.getContentResolver();
    Uri uri=BvProvider.getRecordUriByIndex(index);
    BvNoteRecord record=new BvNoteRecord();

 Cursor cursor = contentResolver.query(uri,null,null,null,null,null);
    if(cursor!=null&&cursor.getCount()>0){
        while (cursor.moveToNext()){
            record.setRecord_index(cursor.getInt(cursor.getColumnIndex(BvNoteRecordColumns.INDEX)));
            record.setAlarmtime(cursor.getLong(cursor.getColumnIndex(BvNoteRecordColumns.ALARM_TIME)));
            ...
            break;
        }
    }
    cursor.close();
    return record;
}

这里是通过index查询出对应的记录的所有信息,这里我们没有指定任何的条件,包括排序。index是通过Uri uri=BvProvider.getRecordUriByIndex(index); 带在了uri中了。形如:// content://com.blackview.notepad/records/index/10

public static Uri getRecordUriByIndex(int index){
    return Uri.withAppendedPath(BvProvider.BvNoteRecordColumns.CONTENT_URI,"index/"+index);
}

等效于:

Uri uri=Uri.parse("content://"+ AUTHORITY +"/"+TABLE_NAME);

//content://com.blackview.notepad/records

contentResolver.query(uri,null,”index=”+index,null,null,null);

  • ContentProvider启动流程

当前应用进程启动时,输出log如下:

09-02 11:33:04.694 D/zyfdebug.BvNoteProvider(23301): onCreate....

09-02 11:33:04.694 D/zyfdebug.DatabaseHelper(23301): DatabaseHelper...

09-02 11:33:04.715 D/zyfdebug.DatabaseHelper(23301): onCreate...

当前应用进程启动时,contentprovider的onCreate()函数,被系统自动调用。

具体的代码在:frameworks/base/core/java/android/app/ActivityThread.java

中的ActivityThread类的成员函数handleBindApplication()里有

 installContentProviders(app, providers);

以后我们做一期android 进程启动流程的培训,这里不做展开,有兴趣的可以自行去了解。

因为我们在onCreate 中主动调用了DatabaseHelper,

BvNoteProvider:

public boolean onCreate() {
    LogUtil.Logd(TAG,"BvNoteProvider onCreate....");
    mOpenHelper = new DatabaseHelper(getContext());
    SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    db.close();
    return true;
}

DatabaseHelper:

public DatabaseHelper(Context context) {
    super(context, DATABASE_NAME, null, DATABASE_VERSION);
    LogUtil.Logd(TAG,"DatabaseHelper...");
}

DatabaseHelper的构造函数中,把DATABASE_NAME和 DATABASE_VERSION 传给了父类SQLiteOpenHelper:

public abstract class SQLiteOpenHelper implements AutoCloseable {

...

}

getWritableDatabase 就是调用SQLiteOpenHelper.getDatabaseLocked()函数。

SQLiteOpenHelper类:

private SQLiteDatabase getDatabaseLocked(boolean writable) {

        SQLiteDatabase db = mDatabase;

        try {

            mIsInitializing = true;

            if (db != null) {

                ...

            } else if (mName == null) {

                ...

            } else {

                final File filePath = mContext.getDatabasePath(mName);

                SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();

                try {

                    db = SQLiteDatabase.openDatabase(filePath, params);

                    // Keep pre-O-MR1 behavior by resetting file permissions to 660

                    setFilePermissionsForDb(filePath.getPath());

                } catch (SQLException ex) {

                    ...

                }

            }

            onConfigure(db);

            final int version = db.getVersion();

            if (version != mNewVersion) {

                ...

                } else {

                    db.beginTransaction();

                    try {

                        if (version == 0) {

                            onCreate(db);

                        } else {

                            if (version > mNewVersion) {

                                onDowngrade(db, version, mNewVersion);

                            } else {

                                onUpgrade(db, version, mNewVersion);

                            }

                        }

                    db.setVersion(mNewVersion);

                        db.setTransactionSuccessful();

                    } finally {

                        db.endTransaction();

                    }

                }

            }

            onOpen(db);

           ...

        } finally {

           ...

        }

    }

 * @see #openOrCreateDatabase
 */
public abstract File getDatabasePath(String name);

这个函数,如果应用是第一次启动,会走以上代码。其中 final File filePath = mContext.getDatabasePath(mName);
会创建db文件。

还会主动调用回DatabaseHelper的onCreate函数。

如果当前的版本比传入的版本号要高的话,调用

onDowngrade(db, version, mNewVersion);

如果比传入的版本要低的话,调用:

onUpgrade(db, version, mNewVersion);

这是为了版本兼容做的处理。

  • 关系型数据库设计原则与E-R图

第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)

1.第一范式(确保每列保持原子性)

 

 

2、第二范式(确保表中的每列都和主键相关)

 

 

3.第三范式(确保每列都和主键列直接相关,而不是间接相关)

 

  1. R图:

 



这篇关于Android数据库与ContentProvider的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程