6.5 共享数据

本节介绍Android的四大组件之一ContentProvider的基本概念和常见用法:首先说明如何使用内容提供器封装内部数据的外部访问接口,然后阐述如何使用内容解析器通过外部接口操作内部数据,最后叙述如何利用内容解析器读写联系人信息,以及如何利用内容观察器监听收到的短信内容。

6.5.1 通过ContentProvider封装数据

Android提供了四大组件,分别是活动Activity、广播Broadcast、服务Service和内容提供器ContentProvider。其中内容提供器涵盖与内部数据存取有关的一系列组件,完整的内容组件由内容提供器ContentProvider、内容解析器ContentResolver、内容观察器ContentObserver三部分组成。

ContentProvider给App存取内部数据提供了统一的外部接口,让不同的应用之间得以互相共享数据。像上一章提到的SQLite可操作应用自身的内部数据库,上传和下载功能可操作后端服务器的文件,而ContentProvider可操作当前设备其他应用的内部数据,它是一种中间层次的数据存储形式。

在实际编码中,ContentProvider只是服务端App存储数据的抽象类,开发者需要在其基础上实现一个完整的内容提供器,并重写下列数据库管理方法。

● onCreate:创建数据库并获得数据库连接。

● insert:插入数据。

● delete:删除数据。

● update:更新数据。

● query:查询数据,并返回结果集的游标。

● getType:获取内容提供器支持的数据类型。

这些方法看起来是不是很像SQLite?没错,ContentProvider作为中间接口,本身并不直接保存数据,而是通过SQLiteOpenHelper与SQLiteDatabase间接操作底层的数据库。所以要想使用ContentProvider,首先得实现SQLite的数据库帮助器,然后由ContentProvider封装对外的接口。以封装用户信息为例,具体步骤主要分成以下3步。

1. 编写用户信息表的数据库帮助器

这个数据库帮助器就是常规的SQLite操作代码,实现过程参见本章的"6.2.3 数据库帮助器SQLiteOpenHelper",完整代码如下:

java 复制代码
package com.example.roomdatabase.entity;
//用户信息
public class UserInfo {
    public long rowid; // 行号
    public int xuhao; // 序号
    public String name; // 姓名
    public int age; // 年龄
    public long height; // 身高
    public float weight; // 体重
    public boolean married; // 婚否
    public String update_time; // 更新时间
    public String phone; // 手机号
    public String password; // 密码

    public UserInfo() {
        rowid = 0L;
        xuhao = 0;
        name = "";
        age = 0;
        height = 0L;
        weight = 0.0f;
        married = false;
        update_time = "";
        phone = "";
        password = "";
    }
}
java 复制代码
package com.example.roomdatabase.database;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

import com.example.roomdatabase.entity.UserInfo;

import java.util.ArrayList;
import java.util.List;

public class UserDBHelper extends SQLiteOpenHelper {
    private static final String TAG = "UserDBHelper";
    private static final String DB_NAME = "user.db"; // 数据库的名称
    private static final int DB_VERSION = 1; // 数据库的版本号
    private static UserDBHelper mHelper = null; // 数据库帮助器的实例
    private SQLiteDatabase mDB = null; // 数据库的实例
    public static final String TABLE_NAME = "user_info"; // 表的名称

    private UserDBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    private UserDBHelper(Context context, int version) {
        super(context, DB_NAME, null, version);
    }

    // 利用单例模式获取数据库帮助器的唯一实例
    public static UserDBHelper getInstance(Context context, int version) {
        if (version > 0 && mHelper == null) {
            mHelper = new UserDBHelper(context, version);
        } else if (mHelper == null) {
            mHelper = new UserDBHelper(context);
        }
        return mHelper;
    }

    // 打开数据库的读连接
    public SQLiteDatabase openReadLink() {
        if (mDB == null || !mDB.isOpen()) {
            mDB = mHelper.getReadableDatabase();
        }
        return mDB;
    }

    // 打开数据库的写连接
    public SQLiteDatabase openWriteLink() {
        if (mDB == null || !mDB.isOpen()) {
            mDB = mHelper.getWritableDatabase();
        }
        return mDB;
    }

    // 关闭数据库连接
    public void closeLink() {
        if (mDB != null && mDB.isOpen()) {
            mDB.close();
            mDB = null;
        }
    }

    // 创建数据库,执行建表语句
    @Override
    public void onCreate(SQLiteDatabase db) {
        Log.d(TAG, "onCreate");
        String drop_sql = "DROP TABLE IF EXISTS " + TABLE_NAME + ";";
        Log.d(TAG, "drop_sql:" + drop_sql);
        db.execSQL(drop_sql);
        String create_sql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
                + "_id INTEGER PRIMARY KEY  AUTOINCREMENT NOT NULL,"
                + "name VARCHAR NOT NULL," + "age INTEGER NOT NULL,"
                + "height INTEGER NOT NULL," + "weight FLOAT NOT NULL,"
                + "married INTEGER NOT NULL," + "update_time VARCHAR NOT NULL"
                //演示数据库升级时要先把下面这行注释
                + ",phone VARCHAR" + ",password VARCHAR"
                + ");";
        Log.d(TAG, "create_sql:" + create_sql);
        db.execSQL(create_sql); // 执行完整的SQL语句
    }

    // 升级数据库,执行表结构变更语句
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.d(TAG, "onUpgrade oldVersion=" + oldVersion + ", newVersion=" + newVersion);
        if (newVersion > 1) {
            //Android的ALTER命令不支持一次添加多列,只能分多次添加
            String alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "phone VARCHAR;";
            Log.d(TAG, "alter_sql:" + alter_sql);
            db.execSQL(alter_sql);
            alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "password VARCHAR;";
            Log.d(TAG, "alter_sql:" + alter_sql);
            db.execSQL(alter_sql); // 执行完整的SQL语句
        }
    }

    // 根据指定条件删除表记录
    public int delete(String condition) {
        // 执行删除记录动作,该语句返回删除记录的数目
        return mDB.delete(TABLE_NAME, condition, null);
    }

    // 删除该表的所有记录
    public int deleteAll() {
        // 执行删除记录动作,该语句返回删除记录的数目
        return mDB.delete(TABLE_NAME, "1=1", null);
    }

    // 往该表添加一条记录
    public long insert(UserInfo info) {
        List<UserInfo> infoList = new ArrayList<UserInfo>();
        infoList.add(info);
        return insert(infoList);
    }

    // 往该表添加多条记录
    public long insert(List<UserInfo> infoList) {
        long result = -1;
        for (int i = 0; i < infoList.size(); i++) {
            UserInfo info = infoList.get(i);
            List<UserInfo> tempList = new ArrayList<UserInfo>();
            // 如果存在同名记录,则更新记录
            // 注意条件语句的等号后面要用单引号括起来
            if (info.name != null && info.name.length() > 0) {
                String condition = String.format("name='%s'", info.name);
                tempList = query(condition);
                if (tempList.size() > 0) {
                    update(info, condition);
                    result = tempList.get(0).rowid;
                    continue;
                }
            }
            // 如果存在同样的手机号码,则更新记录
            if (info.phone != null && info.phone.length() > 0) {
                String condition = String.format("phone='%s'", info.phone);
                tempList = query(condition);
                if (tempList.size() > 0) {
                    update(info, condition);
                    result = tempList.get(0).rowid;
                    continue;
                }
            }
            // 不存在唯一性重复的记录,则插入新记录
            ContentValues cv = new ContentValues();
            cv.put("name", info.name);
            cv.put("age", info.age);
            cv.put("height", info.height);
            cv.put("weight", info.weight);
            cv.put("married", info.married);
            cv.put("update_time", info.update_time);
            cv.put("phone", info.phone);
            cv.put("password", info.password);
            // 执行插入记录动作,该语句返回插入记录的行号
            result = mDB.insert(TABLE_NAME, "", cv);
            if (result == -1) { // 添加成功则返回行号,添加失败则返回-1
                return result;
            }
        }
        return result;
    }

    // 根据条件更新指定的表记录
    public int update(UserInfo info, String condition) {
        ContentValues cv = new ContentValues();
        cv.put("name", info.name);
        cv.put("age", info.age);
        cv.put("height", info.height);
        cv.put("weight", info.weight);
        cv.put("married", info.married);
        cv.put("update_time", info.update_time);
        cv.put("phone", info.phone);
        cv.put("password", info.password);
        // 执行更新记录动作,该语句返回更新的记录数量
        return mDB.update(TABLE_NAME, cv, condition, null);
    }

    public int update(UserInfo info) {
        // 执行更新记录动作,该语句返回更新的记录数量
        return update(info, "rowid=" + info.rowid);
    }

    // 根据指定条件查询记录,并返回结果数据列表
    public List<UserInfo> query(String condition) {
        String sql = String.format("select rowid,_id,name,age,height," +
                "weight,married,update_time,phone,password " +
                "from %s where %s;", TABLE_NAME, condition);
        Log.d(TAG, "query sql: " + sql);
        List<UserInfo> infoList = new ArrayList<UserInfo>();
        // 执行记录查询动作,该语句返回结果集的游标
        Cursor cursor = mDB.rawQuery(sql, null);
        // 循环取出游标指向的每条记录
        while (cursor.moveToNext()) {
            UserInfo info = new UserInfo();
            info.rowid = cursor.getLong(0); // 取出长整型数
            info.xuhao = cursor.getInt(1); // 取出整型数
            info.name = cursor.getString(2); // 取出字符串
            info.age = cursor.getInt(3); // 取出整型数
            info.height = cursor.getLong(4); // 取出长整型数
            info.weight = cursor.getFloat(5); // 取出浮点数
            //SQLite没有布尔型,用0表示false,用1表示true
            info.married = (cursor.getInt(6) == 0) ? false : true;
            info.update_time = cursor.getString(7); // 取出字符串
            info.phone = cursor.getString(8); // 取出字符串
            info.password = cursor.getString(9); // 取出字符串
            infoList.add(info);
        }
        cursor.close(); // 查询完毕,关闭数据库游标
        return infoList;
    }

    // 根据手机号码查询指定记录
    public UserInfo queryByPhone(String phone) {
        UserInfo info = null;
        List<UserInfo> infoList = query(String.format("phone='%s'", phone));
        if (infoList.size() > 0) { // 存在该号码的登录信息
            info = infoList.get(0);
        }
        return info;
    }
}
2. 编写内容提供器的基础字段类

该类需要实现接口BaseColumns,同时加入几个常量定义。详细代码示例如下:

java 复制代码
package com.example.roomdatabase.entity;

import android.net.Uri;
import android.provider.BaseColumns;

import com.example.roomdatabase.database.UserDBHelper;

public class UserInfoContent implements BaseColumns {
    // 这里的名称必须与AndroidManifest.xml里的android:authorities保持一致
    public static final String AUTHORITIES = "com.example";
    //  内容提供器的外部表名
    public static final String TABLE_NAME = UserDBHelper.TABLE_NAME;
    // 访问内容提供器的URI
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITIES + "/user");
    // 下面是该表的各个字段名称
    public static final String USER_NAME = "name";
    public static final String USER_AGE = "age";
    public static final String USER_HEIGHT = "height";
    public static final String USER_WEIGHT = "weight";
    public static final String USER_MARRIED = "married";
    // 默认的排序方法
    public static final String DEFAULT_SORT_ORDER = "_id desc";
}
3. 通过右键菜单创建内容提供器

右击App模块的包名目录,在弹出的右键菜单中依次选择New→Other→Content Provider,打开如图所示的组件创建对话框。

在创建对话框的Class Name一栏填写内容提供器的名称,比如UserInfoProvider;在URI Authorities一栏填写URI的授权串,比如"com.example",注意这个授权串要跟 UserInfoContent里的一样;然后单击对话框右下角的Finish按钮,完成提供器的创建操作。

上述创建过程会自动修改App模块的两处地方,一处是往AndroidManifest.xml添加内容提供器的注册配置,配置信息示例如下:

XML 复制代码
        <provider
            android:name=".entity.UserInfoProvider"
            android:authorities="com.example"
            android:enabled="true"
            android:exported="true" >
        </provider>

另一处是在包名目录下生成名为UserInfoProvider.java的代码文件,打开一看发现该类继承了ContentProvider,并且提示重写onCreate、insert、delete、query、update、getType等方法,以便对数据进行增删改查等操作。这个提供器代码显然只有一个框架,还需补充详细的实现代码,为此重写onCreate方法,在此获取用户信息表的数据库帮助器实例,其他insert、delete、query等方法也要加入对应的数据库操作代码,修改之后的内容提供器代码如下:

java 复制代码
package com.example.roomdatabase.entity;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

import com.example.roomdatabase.database.UserDBHelper;

public class UserInfoProvider extends ContentProvider {
    private final static String TAG = "UserInfoProvider";
    private UserDBHelper userDB; // 声明一个用户数据库的帮助器对象
    public static final int USER_INFO = 1; // Uri匹配时的代号
    public static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static { // 往Uri匹配器中添加指定的数据路径
        uriMatcher.addURI(UserInfoContent.AUTHORITIES, "/user", USER_INFO);
    }
    public UserInfoProvider() {
    }

    // 根据指定条件删除数据
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Implement this to handle requests to delete one or more rows.
        int count = 0;
        if (uriMatcher.match(uri) == USER_INFO) { // 匹配到了用户信息表
            // 获取SQLite数据库的写连接
            SQLiteDatabase db = userDB.getWritableDatabase();
            // 执行SQLite的删除操作,并返回删除记录的数目
            count = db.delete(UserInfoContent.TABLE_NAME, selection, selectionArgs);
            db.close(); // 关闭SQLite数据库连接
        }
        return count;
    }

    // 获取Uri支持的数据类型,暂未实现
    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    // 插入数据
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        if (uriMatcher.match(uri) == USER_INFO) { // 匹配到了用户信息表
            // 获取SQLite数据库的写连接
            SQLiteDatabase db = userDB.getWritableDatabase();
            // 向指定的表插入数据,返回记录的行号
            long rowId = db.insert(UserInfoContent.TABLE_NAME, null, values);
            if (rowId > 0) { // 判断插入是否执行成功
                // 如果添加成功,就利用新记录的行号生成新的地址
                Uri newUri = ContentUris.withAppendedId(UserInfoContent.CONTENT_URI, rowId);
                // 通知监听器,数据已经改变
                getContext().getContentResolver().notifyChange(newUri, null);
            }
            db.close(); // 关闭SQLite数据库连接
        }
        return uri;
    }

    // 创建ContentProvider时调用,可在此获取具体的数据库帮助器实例
    @Override
    public boolean onCreate() {
        // TODO: Implement this to initialize your content provider on startup.
        userDB = UserDBHelper.getInstance(getContext(), 1);
        return true;
    }

    // 根据指定条件查询数据库
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        Cursor cursor = null;
        if (uriMatcher.match(uri) == USER_INFO) { // 匹配到了用户信息表
            // 获取SQLite数据库的读连接
            SQLiteDatabase db = userDB.getReadableDatabase();
            // 执行SQLite的查询操作
            cursor = db.query(UserInfoContent.TABLE_NAME,
                    projection, selection, selectionArgs, null, null, sortOrder);
            // 设置内容解析器的监听
            cursor.setNotificationUri(getContext().getContentResolver(), uri);
        }
        return cursor; // 返回查询结果集的游标
    }

    // 更新数据,暂未实现
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}
相关推荐
我与岁月的森林4 天前
基于ScriptableObject设计游戏数据表
游戏·unity·数据存储
大数据在线9 天前
打造备份一体机,群晖科技平台化战略再进阶
备份·数据存储·百度智能云·备份一体机·群晖科技
青云交19 天前
大数据新视界 --大数据大厂之HBase深度探寻:大规模数据存储与查询的卓越方案
大数据·数据库·hbase·数据存储·性能优势、问题解决、应用领域·可扩展性、高可靠性·读写性能、集群管理
追梦的鱼儿25 天前
Android之SharedPreferences(SP)
android·数据存储·sp
Trouvaille ~2 个月前
【C语言篇】数据在内存中的存储(超详细)
c语言·数据存储·二进制·进制·整数·浮点数·存储方式
爱桥代码的程序媛2 个月前
鸿蒙语言基础类库:【@system.storage (数据存储)】
华为·harmonyos·数据存储·鸿蒙系统·openharmony·鸿蒙开发·语言
JackieZhengChina4 个月前
区块链简要介绍及运用的技术
区块链·数据安全·数据存储
OceanBase数据库官方博客6 个月前
解析OceanBase v4.2磁盘数据文件支持按需扩展的功能
oceanbase·分布式数据库·数据存储·技术原理·产品特性
Hello-Brand7 个月前
MySQL系列:索引失效场景总结
mysql·数据存储·数据库技术