ContentProvider 可以用来原生读写 Android 自带的数据库 SQLite。

  1. 使用 Studio 创建一个 ContentProvider, 名字叫 TestContentProvider 。

    AndroidManifest.xml 文件:

    <manifest
    ...>
    <application
    ...>
    <provider
    android:name=".TestContentProvider"
    android:authorities="cn.wx2020.contentprovidertest.database"
    android:enabled="true"
    android:exported="false"/>
    ...
    </application>
    </manifest>

    其中,authorities 是 Uri 结构中的一部分,类似于 Url 中的域名部分,其唯一识别一个 ContentProvider。

  2. 由于 ContentProvider 是抽象类,按照继承抽象类的要求,需要实现以下几个方法:

    @Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
} @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.
throw new UnsupportedOperationException("Not yet implemented");
} @Override
public boolean onCreate() {
// TODO: Implement this to initialize your content provider on startup.
return false;
} @Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO: Implement this to handle query requests from clients.
throw new UnsupportedOperationException("Not yet implemented");
} @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");
}
  1. 本文实际只实现了 onCreate()、insert()、query() 这三个方法。
  • onCreate():负责初始化数据库帮助类 SQLiteOpenHelper,通过此类可以进行数据库创建、升级等操作。
      private TestSQLiteOpenHelper mOpenHelper;
    ... @Override
    public boolean onCreate() {
    mOpenHelper = new TestSQLiteOpenHelper(getContext());
    return false;
    }

    其中,

    • TestSQLiteOpenHelper 为 继承 SQLiteOpenHelper 后自定义的实现类,将在后文给出说明。
    • 此方法的返回值意义不大,可以忽略。
  • insert():负责在最内部一层将传入的数据插入数据库中。
      public static final String AUTHORITY = "cn.wx2020.contentprovidertest.database";
    
      private static final UriMatcher MATCHER;
    
      static {
    MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
    MATCHER.addURI(AUTHORITY, "user", 1);
    } private String uriMatcher(Uri uri) {
    switch (MATCHER.match(uri)) {
    case 1:
    return "user";
    default:
    return "";
    }
    }
    ... @Override
    public Uri insert(Uri uri, ContentValues values) {
    String tableName = uriMatcher(uri);
    if (TextUtils.isEmpty(tableName)) {
    return null;
    }
    SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    long rowName = db.insert(tableName, null, values);
    if (rowName >= 0) {
    Uri returnUri = ContentUris.withAppendedId(uri, rowName);
    getContext().getContentResolver().notifyChange(uri, null);
    return returnUri;
    }
    return null;
    }

    其中,

    • 方法 uriMatcher() 使用静态的 UriMatcher ,在静态块中初始化、添加匹配当前 ContentProvider 的 authorities、数据库表名 和 与数据库表名绑定的返回码。uriMatcher() 方法中,使用 MATCHER.match(uri) 拿到返回码后,根据返回码再返回与其匹配的表名。
    • 使用 SQLiteOpenHelper 的对象调用 getWritableDatabase() 方法拿到可写的数据库后,对数据库 db 调用 insert() 方法插入传入的属性值,属性值由 ContentValues 代理生成。插入成功后,insert() 函数会返回一个 rowId ;如果插入失败,rowId 为 -1;若其他情况则插入成功。
    • 插入成功时,返回一个 Uri 对象,上层根据这个 Uri 对象,判断是否插入成功。
  • query():负责查询数据库中的数据。
      @Override
    public Cursor query(Uri uri, String[] projection, String selection,
    String[] selectionArgs, String sortOrder) {
    String tableName = uriMatcher(uri);
    if (TextUtils.isEmpty(tableName)) {
    return null;
    } Cursor cursor = null;
    try {
    SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
    builder.setTables(tableName);
    cursor = builder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
    } catch (SQLException e) {
    Log.e(TAG, "query SQLiteException");
    } if (cursor != null) {
    cursor.setNotificationUri(getContext().getContentResolver(), uri);
    } else {
    Log.e(TAG, "cursor = null");
    }
    return cursor;
    }

    其中,

    • 拿到可读的数据库后,使用 SQLiteQueryBuilder 的对象 builder 构造查询。使用 builder.setTables() 设定要查询的数据库表名。使用 builder.query() 执行查询,按照查询条件依次输入7个参数,其中第一个参数为数据库对象 db。
    • 查询得到的游标 Cursor 对象如果不为 null, 说明查询成功,否则则为查询失败。返回 Cursor 对象给调用方。
  1. 上文中还提到了一个类 SQLiteOpenHelper ,其内部有两个方法:onCreate() 在数据库创建后调用;onUpgrade() 升级数据库版本时使用。
  • onCreate() :
      @Override
    public void onCreate(SQLiteDatabase db) {
    createDatabaseTable(db);
    } private void createDatabaseTable(SQLiteDatabase db) {
    String userSQL = "CREATE TABLE IF NOT EXISTS user (" +
    " _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," +
    " user_name TEXT NOT NULL," +
    " age TEXT," +
    " phone_number INTEGER" +
    ");";
    db.execSQL(userSQL);
    }

    此方法中,要使用 SQL 语句在指定数据库创建表。使用 String 构造好建表语句后,调用数据库 db 的 execSQL() 执行 SQL 语句。

  1. 除了以上两个类,还可以在实际调用 ContentProvider 之前,在数据库表的维度中再抽象出一层 UserTable,用于外界进行实际查询时调用。由于 ContentProvider 只实现了 insert() 和 query() 方法,相对应地,UserTable 也实现两个方法 insert() 和 queryAll()。
  • insert():用于实际调用 ContentProvider,插入数据。
      private static Uri getContextUri() {
    return TestContentProvider.URI_TEST_USER;
    } public static Uri insert(Context context, User user) {
    if (TextUtils.isEmpty(user.getUserName())) {
    return null;
    }
    ContentValues contentValues = new ContentValues();
    contentValues.put("user_name", user.getUserName());
    contentValues.put("age", user.getAge());
    contentValues.put("phone_number", user.getPhoneNumber());
    Uri uri = null;
    try {
    uri = context.getContentResolver().insert(getContextUri(), contentValues);
    } catch (SQLException e) {
    Log.e(TAG, "insert SQLiteException");
    }
    if (uri == null) {
    Log.e(TAG, "insert uri is null");
    }
    return uri;
    }

    其中,

    • 静态方法getContextUri() 用于返回与数据库当前表对应的 Uri ,操作 ContentProvider 也就是通过 Uri 操作的。
    • 方法参数中,实体类 User 的对象中的属性值正是插入数据库时所使用的字段值,不过需要将实体类对象转换为 ContentValues 对象后,使其作为 ContentResolver 类对象的 insert() 方法参数之一进行插入。
  • queryAll(): 用于查询数据库表 user 中的所有数据。
      public static ArrayList<User> queryAll(Context context) {
    ArrayList<User> result = new ArrayList<>();
    Cursor cursor = null;
    try {
    cursor = context.getContentResolver().query(getContextUri(), null, null, null, null);
    while (cursor != null && cursor.moveToNext()) {
    User user = new User();
    user.setId(cursor.getInt(cursor.getColumnIndexOrThrow("_id")));
    user.setUserName(cursor.getString(cursor.getColumnIndexOrThrow("user_name")));
    user.setAge(cursor.getInt(cursor.getColumnIndexOrThrow("age")));
    user.setPhoneNumber(cursor.getLong(cursor.getColumnIndexOrThrow("phone_number")));
    result.add(user);
    }
    } catch (SQLException e) {
    Log.e(TAG, "queryAll SQLiteException");
    } finally {
    if (cursor != null && !cursor.isClosed()) {
    cursor.close();
    }
    }
    return result;
    }

    其中,

    • 使用 ContentResolver 类对象的 query() 方法,除了 Uri 以外不传任何参数,这样获取的 cursor 就是

      在整个链上连续且整体代表全部表数据的 cursor 。
    • 使用 cursor != null && cursor.moveToNext() 作为循环条件,判断链上是否有下一个数据。若没有下一个数据可以终止循环。
    • 每一行的数据是从 cursor 中取得的,其中 cursor 要先通过 getColumnIndexOrThrow() 传入字段名,拿到代表这一字段的 index ,再通过对 index 调用 cursor.getInt() 或 cursor.getString() 拿到这一 index 代表的字段的 INT 或者 TEXT 值。
    • 每拿到一个 cursor 对应的数据后,将数据加入 ArrayList 中,最后将 ArrayList 对象作为整体返回。

部分源码

  1. TestContentProvider 源码:
package cn.wx2020.contentprovidertest;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log; import java.util.Optional; public class TestContentProvider extends ContentProvider { public static final String TAG = "TestContentProvider"; public static final String AUTHORITY = "cn.wx2020.contentprovidertest.database"; public static final Uri URI_TEST_USER = Uri.parse("content://" + AUTHORITY + '/' + "user"); private static final UriMatcher MATCHER; static {
MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
MATCHER.addURI(AUTHORITY, "user", 1);
} private String uriMatcher(Uri uri) {
switch (MATCHER.match(uri)) {
case 1:
return "user";
default:
return "";
}
} private TestSQLiteOpenHelper mOpenHelper; public TestContentProvider() {
} @Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
} @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) {
String tableName = uriMatcher(uri);
if (TextUtils.isEmpty(tableName)) {
return null;
}
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowName = db.insert(tableName, null, values);
if (rowName >= 0) {
Uri returnUri = ContentUris.withAppendedId(uri, rowName);
getContext().getContentResolver().notifyChange(uri, null);
return returnUri;
}
return null;
} @Override
public boolean onCreate() {
mOpenHelper = new TestSQLiteOpenHelper(getContext());
return false;
} @Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
String tableName = uriMatcher(uri);
if (TextUtils.isEmpty(tableName)) {
return null;
} Cursor cursor = null;
try {
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(tableName);
cursor = builder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
} catch (SQLException e) {
Log.e(TAG, "query SQLiteException");
} if (cursor != null) {
cursor.setNotificationUri(getContext().getContentResolver(), uri);
} else {
Log.e(TAG, "cursor = null");
}
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");
}
}
  1. TestSqLiteOpenHelper 源码:
package cn.wx2020.contentprovidertest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; public class TestSQLiteOpenHelper extends SQLiteOpenHelper { private static final String DB_NAME = "test.db"; private static final int DB_VERSION = 1; public TestSQLiteOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
} @Override
public void onCreate(SQLiteDatabase db) {
createDatabaseTable(db);
} private void createDatabaseTable(SQLiteDatabase db) {
String userSQL = "CREATE TABLE IF NOT EXISTS user (" +
" _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," +
" user_name TEXT NOT NULL," +
" age TEXT," +
" phone_number INTEGER" +
");";
db.execSQL(userSQL);
} @Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { }
}
  1. UserTable源码:
package cn.wx2020.contentprovidertest;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log; import java.util.ArrayList; public class UserTable {
private static final String TAG = "UserTable"; private static Uri getContextUri() {
return TestContentProvider.URI_TEST_USER;
} public static Uri insert(Context context, User user) {
if (TextUtils.isEmpty(user.getUserName())) {
return null;
}
ContentValues contentValues = new ContentValues();
contentValues.put("user_name", user.getUserName());
contentValues.put("age", user.getAge());
contentValues.put("phone_number", user.getPhoneNumber());
Uri uri = null;
try {
uri = context.getContentResolver().insert(getContextUri(), contentValues);
} catch (SQLException e) {
Log.e(TAG, "insert SQLiteException");
}
if (uri == null) {
Log.e(TAG, "insert uri is null");
}
return uri;
} public static ArrayList<User> queryAll(Context context) {
ArrayList<User> result = new ArrayList<>();
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(getContextUri(), null, null, null, null);
while (cursor != null && cursor.moveToNext()) {
User user = new User();
user.setId(cursor.getInt(cursor.getColumnIndexOrThrow("_id")));
user.setUserName(cursor.getString(cursor.getColumnIndexOrThrow("user_name")));
user.setAge(cursor.getInt(cursor.getColumnIndexOrThrow("age")));
user.setPhoneNumber(cursor.getLong(cursor.getColumnIndexOrThrow("phone_number")));
result.add(user);
}
} catch (SQLException e) {
Log.e(TAG, "queryAll SQLiteException");
} finally {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
return result;
}
}
  1. MainActivity 源码:
        Button queryAllButton = findViewById(R.id.button_1);
TextView textView = findViewById(R.id.text_1);
queryAllButton.setOnClickListener(v -> {
ArrayList<User> users = UserTable.queryAll(this);
if (users.isEmpty()) {
Log.e(TAG, "users is empty");
Toast.makeText(this, "没有数据", Toast.LENGTH_SHORT).show();
return;
}
StringBuilder sb = new StringBuilder();
for (User user: users) {
sb.append(user.toString()).append("\n");
}
textView.setText(sb.toString());
}); Button insertButton = findViewById(R.id.button_2);
insertButton.setOnClickListener(v -> {
User user = new User();
int userBack = (int) (Math.random() * 99 + 1);
user.setUserName("wx2020" + userBack);
user.setPhoneNumber(110);
user.setAge((int) (Math.random() * 30 + 23));
Uri insertUri = UserTable.insert(this, user);
if (insertUri == null) {
Toast.makeText(this, "插入失败", Toast.LENGTH_SHORT).show();
return;
}
Log.d(TAG, "insertUri = " + insertUri);
Toast.makeText(this, "插入成功", Toast.LENGTH_SHORT).show();
});

Android 使用 ContentProvider 简单操作数据库的更多相关文章

  1. Spring_boot简单操作数据库

    Spring_boot搭配Spring Data JPA简单操作数据库 spring boot 配置文件可以使用yml文件,默认spring boot 会加载resources目录的下的applica ...

  2. android: SQLite使用 SQL 操作数据库

    虽然 Android 已经给我们提供了很多非常方便的 API 用于操作数据库,不过总会有一些 人不习惯去使用这些辅助性的方法,而是更加青睐于直接使用 SQL 来操作数据库.这种人 一般都是属于 SQL ...

  3. php 简单操作数据库

    <?php header("content-type:text/html;charset=utf-8"); /*//造一个连接 $connect = @mysql_conne ...

  4. pymysql 简单操作数据库

    #!/usr/bin/env python #-*- coding:utf-8 -*- # author:leo # datetime:2019/4/24 15:22 # software: PyCh ...

  5. Android使用命令行操作数据库

    所有的应用程序本地文件都存放在/data/data/目录下 C:\Users\nicole>adb shell * daemon not running. starting it now on ...

  6. mybatis_02简单操作数据库

    模糊查询用户信息 <!-- [${}]:表示拼接SQL字符串 [${value}]:表示要拼接的是简单类型参数. 注意: 1.如果参数为简单类型时,${}里面的参数名称必须为value 2.${ ...

  7. SQLiteDatabase里面的简单操作数据库的方法

    1.使用insert方法插入记录SQLiteDatabase的insert方法的签名为long insert(String table,String nullColumnHack,ContentVal ...

  8. spring框架整合hibernate框架简单操作数据库

    1.配置文件: <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http:/ ...

  9. pyqt5最简单操作数据库

    要先安一个包才能使用QtSql通过新立得安装 import PyQt5.QtSql as sql db=sql.QSqlDatabase.addDatabase('QMYSQL') db.setDat ...

  10. spring-boot-route(七)整合jdbcTemplate操作数据库

    在一部分内容中,我们学习了Restful接口的编写,及接口文档的生成.我们需要将接口数据进行持久化存储,这一部分我们主要学习几种持久化框架将数据进行存储.本部分内容中,我们都将使用mysql为例来做为 ...

随机推荐

  1. Hugging News #0731: 新课程重磅发布、用户交流群邀请你加入、真实图像编辑方法 LEDTIS 来啦!

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...

  2. KVM下windows由IDE模式改为virtio模式蓝屏 开不开机

    KVM安装Windows默认使用的是qemu虚拟化IDE硬盘模式,在这种情况下,IO性能比较低,如果使用virtio的方式可以提高虚拟机IO性能. 于是我想将这台虚拟机迁移到openstack中管理 ...

  3. 《Web安全基础》02. 信息收集

    @ 目录 1:CDN 绕过 1.1:判断是否有 CDN 服务 1.2:常见绕过方法 1.3:相关资源 2:网站架构 3:WAF 4:APP 及其他资产 5:资产监控 本系列侧重方法论,各工具只是实现目 ...

  4. 2023-08-30:用go语言编写。两个魔法卷轴问题。 给定一个数组arr,其中可能有正、负、0, 一个魔法卷轴可以把arr中连续的一段全变成0,你希望数组整体的累加和尽可能大。 你有两个魔法卷轴,

    2023-08-30:用go语言编写.两个魔法卷轴问题. 给定一个数组arr,其中可能有正.负.0, 一个魔法卷轴可以把arr中连续的一段全变成0,你希望数组整体的累加和尽可能大. 你有两个魔法卷轴, ...

  5. 拼多多sku详情的获取分析以及应用

    一.拼多多sku详情获取方式 要获取拼多多SKU详情,需要使用拼多多的API接口,以下是获取拼多多SKU详情的步骤: 1.注册拼多多开放平台账户并创建应用 拼多多创建开发者账户并创建应用,获得应用ID ...

  6. 关于前后端交互,取header的尴尬

    背景: 最近在写一个接口的时候,需求是这样的,上传excel,匹配项目有多少个字段匹配上了,如果匹配上了在单元格上标注绿色背景,然后返回excel文件和匹配的详细. 首先这个excel文件,后端是不会 ...

  7. Deep Transfer Learning综述阅读笔记

    这是一篇linkedin发表的深度迁移学习综述, 里面讲了一些对于search/recommend system中的迁移学习应用. 有不少指导性的方法, 看完后摘录出来 对于ranking方向的TL, ...

  8. jquery实现表格导出Excel

    使用jQuery,jszip.js,FileSaver.js,excel-gen.js插件直接将网页中的table表格导出到本地Excel文件,而不需要经过后台. 导出结果: 实现步骤: 1.进入相关 ...

  9. Java开发面试--nacos专区

    1. Nacos是什么? 请简要介绍Nacos是什么以及它的主要功能和用途. 答: 简介: Nacos是一个开源的.高性能.动态服务发现.配置和服务管理平台,通常用于微服务架构中.Nacos的名称来源 ...

  10. 深信服 9-15笔试 C卷

    深信服 9-15笔试 C卷 15题填空,4道编程 填空 填空比较基础,考察了数据结构(队列,二叉树),和简单编程,二进制等. 1.1 2 3 4 5 入队,然后队首弹出,6 7 8 9 10 入队,然 ...