Android 使用 ContentProvider 简单操作数据库

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

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

    AndroidManifest.xml 文件:

    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    <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 是抽象类,按照继承抽象类的要求,需要实现以下几个方法:

复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
@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,通过此类可以进行数据库创建、升级等操作。
    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    private TestSQLiteOpenHelper mOpenHelper; ... @Override public boolean onCreate() { mOpenHelper = new TestSQLiteOpenHelper(getContext()); return false; }
    其中,
    • TestSQLiteOpenHelper 为 继承 SQLiteOpenHelper 后自定义的实现类,将在后文给出说明。
    • 此方法的返回值意义不大,可以忽略。
  • insert():负责在最内部一层将传入的数据插入数据库中。
    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    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 ,在静态块中初始化、添加匹配当前 ContentProviderauthorities、数据库表名 和 与数据库表名绑定的返回码。uriMatcher() 方法中,使用 MATCHER.match(uri) 拿到返回码后,根据返回码再返回与其匹配的表名。
    • 使用 SQLiteOpenHelper 的对象调用 getWritableDatabase() 方法拿到可写的数据库后,对数据库 db 调用 insert() 方法插入传入的属性值,属性值由 ContentValues 代理生成。插入成功后,insert() 函数会返回一个 rowId ;如果插入失败,rowId-1;若其他情况则插入成功。
    • 插入成功时,返回一个 Uri 对象,上层根据这个 Uri 对象,判断是否插入成功。
  • query():负责查询数据库中的数据。
    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    @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()
    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    @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 构造好建表语句后,调用数据库 dbexecSQL() 执行 SQL 语句。
  1. 除了以上两个类,还可以在实际调用 ContentProvider 之前,在数据库表的维度中再抽象出一层 UserTable,用于外界进行实际查询时调用。由于 ContentProvider 只实现了 insert()query() 方法,相对应地,UserTable 也实现两个方法 insert()queryAll()
  • insert():用于实际调用 ContentProvider,插入数据。
    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    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 中的所有数据。
    复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    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 源码:
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
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 源码:
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
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源码:
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
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 源码:
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
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(); });
posted @   wx2020  阅读(507)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开