Android 使用 ContentProvider 简单操作数据库

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 ,在静态块中初始化、添加匹配当前 ContentProviderauthorities、数据库表名 和 与数据库表名绑定的返回码。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 构造好建表语句后,调用数据库 dbexecSQL() 执行 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();
        });
posted @ 2022-08-30 10:38  wx2020  阅读(412)  评论(0编辑  收藏  举报