自定义 ContentProvider

在这个示例中,我们创建一个 ContentProvider 用于“旅游景点”的数据共享,具体步骤:

  1. 创建一个继承自 SQLiteOpenHelper 的子类,用于数据库的管理;
  2. 创建一个常量工具类,用于管理“旅游景点”的数据相关的常量,提高代码的简洁性和可维护性;
  3. 创一个 ContentProvider的子类,实现增、删、改、查等方法,实现“旅游景点”的数据共享;
  4. AndroidManifest.xml 清单文件中注册该ContentProvider
  5. 创建一个用于“旅游景点”数据交互的 Activity,实现写入数据、读取数据的功能
  6. 运行项目,实验操作

在项目 ContentProviderSample代码基础上,

1. 创建一个继承自 SQLiteOpenHelper 的子类,用于数据库的管理

创建一个名为 TSDatabaseHelper的类,继承 SQLiteOpenHelper ,并实现数据库的管理的代码。
Java 代码:

public class TSDatabaseHelper extends SQLiteOpenHelper {
    
    
    // sql语句,创建一个名为 TouristSpot 数据库表,该表包含三个字段:_id、spot、detail
    private static final String createTouristSpot = "create table TouristSpot (" +
            " _id integer primary key autoincrement," +
            "spot text," +
            "detail text)";

    public TSDatabaseHelper(@Nullable Context context, @Nullable String name, int version) {
    
    
        this(context, name, null, version);
    }

    public TSDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
    
    
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
    
    
        sqLiteDatabase.execSQL(createTouristSpot);// 执行创建"表"的操作
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
    
    
    }
}

Kotlin 代码:

class TSDatabaseHelper(context: Context, name: String, version: Int): SQLiteOpenHelper(context, name, null, version) {
    
    
    // sql语句,创建一个名为 TouristSpot 数据库表,该表包含三个字段:_id、spot、detail
    private val createTouristSpot = "create table TouristSpot (" +
            " _id integer primary key autoincrement," +
            "spot text," +
            "detail text)"

    override fun onCreate(db: SQLiteDatabase?) {
    
    
        db?.execSQL(createTouristSpot)// 执行创建"表"的操作
    }

    override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
    
    
    }
}

2. 创建一个常量工具类,用于管理“旅游景点”的数据相关的常量

创建一个名为 TouristSpot 的类,
Java 代码:

public final class TouristSpot {
    
    
    // 定义ContentProvider的 authority
    public final static String AUTHORITY = "com.guagua.providers.tourists";
    public final static String TABLE = "TouristSpot";

    public static final class Spot implements BaseColumns {
    
    
        // 数据列
        public static final String _ID = "_id";
        public static final String SPOT = "spot";
        public static final String DETAIL = "detail";

        // 定义ContentProvider 提供的 Uri
        public static final Uri TOURISTS_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/tourists");
        public static final Uri SPOT_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/spot");
    }
}

Kotlin 代码:

class TouristSpot {
    
    
    companion object {
    
    
        // 定义ContentProvider的 authority
       const val AUTHORITY = "com.guagua.providers.tourists"
       const val TABLE = "TouristSpot"
    }

    object Spot : BaseColumns {
    
    
        // 数据列
        const val _ID = "_id"
        const val SPOT = "spot"
        const val DETAIL = "detail"

        // 定义ContentProvider 提供的 Uri
        val TOURISTS_CONTENT_URI = Uri.parse("content://$AUTHORITY/tourists")
        val SPOT_CONTENT_URI = Uri.parse("content://$AUTHORITY/spot")
    }
}

3. 创一个 ContentProvider的子类,实现增、删、改、查等方法,实现“旅游景点”的数据共享;

创建一个名为 TouristSpotContentProvider的类,继承 ContentProvider.
其中函数 insert() delete() update() query() 分别对应增、删、改、查方法。

Java 代码:

public class TouristSpotContentProvider extends ContentProvider {
    
    
    private final static int TOURISTS = 1;
    private final static int SPOT = 2;
    private static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
    
     // 向UriMatcher对象注册Uri
        matcher.addURI(TouristSpot.AUTHORITY, "tourists", TOURISTS);
        matcher.addURI(TouristSpot.AUTHORITY, "spot/#", SPOT);
    }

    private TSDatabaseHelper dbHelper;

    @Override
    public boolean onCreate() {
    
    // ContentProvider创建时调用此函数
        dbHelper = new TSDatabaseHelper(this.getContext(), "tourist_spots.db3", 1);
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
    
    
        SQLiteDatabase db = dbHelper.getReadableDatabase();

        switch (matcher.match(uri)) {
    
    
            case TOURISTS:
                // 查询
                return db.query(TouristSpot.TABLE, projection, selection, selectionArgs, null, null, sortOrder);

            case SPOT:
                // 解析所要查询记录的 ID
                long id = ContentUris.parseId(uri);
                // 拼接 where 语句
                StringBuilder where = new StringBuilder(TouristSpot.Spot._ID);
                where.append("=").append(id);
                if (!TextUtils.isEmpty(selection)) {
    
    
                    where.append(" and ").append(selection);
                }
                return db.query(TouristSpot.TABLE, projection, where.toString(), selectionArgs, null, null, sortOrder);

            default:
                return null;
            // throw new IllegalArgumentException("unknown Uri: " + uri);
        }
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
    
    
        switch (matcher.match(uri)) {
    
    
            case TOURISTS: // 数据是多项记录
                return "vnd.android.cursor.dir/com.guagua.tourist_spot";
            case SPOT: // 数据是单项记录
                return "vnd.android.cursor.item/com.guagua.tourist_spot";
        }
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
    
    
        // 获取数据库实例
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        // 插入数据
        long rowId = db.insert(TouristSpot.TABLE, TouristSpot.Spot._ID, contentValues);
        if (0 < rowId) {
    
    
            //在Uri后追加 ID数据
            Uri spotUri = ContentUris.withAppendedId(uri, rowId);
            // 通知数据发生了改变
            getContext().getContentResolver().notifyChange(spotUri, null);
            return spotUri;
        }
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
    
    
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count = 0;// 删除记录的数量
        switch (matcher.match(uri)) {
    
     // 匹配 uri
            case TOURISTS:
                count = db.delete(TouristSpot.TABLE, s, strings);
                break;
            case SPOT:
                // 解析所要删除记录的 ID
                long id = ContentUris.parseId(uri);
                // 拼接 where 语句
                StringBuilder where = new StringBuilder(TouristSpot.Spot._ID);
                where.append("=").append(id);
                if (!TextUtils.isEmpty(s)) {
    
    
                    where.append(" and ").append(s);
                }
                count = db.delete(TouristSpot.TABLE, where.toString(), strings);
                break;
            // default:
            //   throw new IllegalArgumentException("unknown Uri: " + uri);
        }

        // 通知数据发生了改变
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
    
    
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count = 0;// 修改记录的数量
        switch (matcher.match(uri)) {
    
    
            case TOURISTS:
                count = db.update(TouristSpot.TABLE, contentValues, s, strings);
                break;
            case SPOT:
                // 解析所要修改记录的 ID
                long id = ContentUris.parseId(uri);
                // 拼接 where 语句
                StringBuilder where = new StringBuilder(TouristSpot.Spot._ID);
                where.append("=").append(id);
                if (!TextUtils.isEmpty(s)) {
    
    
                    where.append(" and ").append(s);
                }
                count = db.update(TouristSpot.TABLE, contentValues, where.toString(), strings);
                break;
            // default:
            //   throw new IllegalArgumentException("unknown Uri: " + uri);
        }

        // 通知数据发生了改变
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }
}

Kotlin 代码:

class TouristSpotContentProvider : ContentProvider() {
    
    
    private val TOURISTS = 1
    private val SPOT = 2
    private val matcher = UriMatcher(UriMatcher.NO_MATCH)

    init {
    
     // 向UriMatcher对象注册Uri
        matcher.addURI(TouristSpot.AUTHORITY, "tourists", TOURISTS)
        matcher.addURI(TouristSpot.AUTHORITY, "spot/#", SPOT)
    }

    private var dbHelper: TSDatabaseHelper? = null

    override fun onCreate(): Boolean {
    
    
        context?.let {
    
    
            dbHelper = TSDatabaseHelper(it, "tourist_spots.db3", 1)
            return true
        } ?: return false
    }

    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
    
    
        val db = dbHelper?.readableDatabase
        if (dbHelper == null) {
    
    
            return null
        }
        when (matcher.match(uri)) {
    
    
            TOURISTS -> {
    
    
                // 查询
                return db!!.query(
                    TouristSpot.TABLE,
                    projection,
                    selection,
                    selectionArgs,
                    null,
                    null,
                    sortOrder
                )
            }
            SPOT -> {
    
    
                // 解析所要查询记录的 ID
                val id = ContentUris.parseId(uri)
                // 拼接 where 语句
                val where = StringBuilder(TouristSpot.Spot._ID)
                where.append("=").append(id)
                if (!TextUtils.isEmpty(selection)) {
    
    
                    where.append(" and ").append(selection)
                }
                return db!!.query(
                    TouristSpot.TABLE,
                    projection,
                    where.toString(),
                    selectionArgs,
                    null,
                    null,
                    sortOrder
                )
            }
        }
        return null
    }

    override fun getType(uri: Uri): String? {
    
    
        when (matcher.match(uri)) {
    
    
            TOURISTS -> return "vnd.android.cursor.dir/com.guagua.tourist_spot"
            SPOT -> return "vnd.android.cursor.item/com.guagua.tourist_spot"
        }
        return null
    }

    override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
    
    
        if (dbHelper == null) {
    
    
            return null
        }
        // 获取数据库实例
        val db = dbHelper!!.writableDatabase
        // 插入数据
        val rowId = db.insert(TouristSpot.TABLE, TouristSpot.Spot._ID, contentValues)
        if (0 < rowId) {
    
    
            //在Uri后追加 ID数据
            val spotUri = ContentUris.withAppendedId(uri, rowId)
            // 通知数据发生了改变
            context!!.contentResolver.notifyChange(spotUri, null)
            return spotUri
        }
        return null
    }

    override fun delete(uri: Uri, s: String?, strings: Array<out String>?): Int {
    
    
        if (dbHelper == null) return 0
        val db = dbHelper!!.writableDatabase
        var count = 0 // 删除记录的数量

        when (matcher.match(uri)) {
    
    
            TOURISTS -> count = db.delete(TouristSpot.TABLE, s, strings)
            SPOT -> {
    
    
                // 解析所要删除记录的 ID
                val id = ContentUris.parseId(uri)
                // 拼接 where 语句
                val where = StringBuilder(TouristSpot.Spot._ID)
                where.append("=").append(id)
                if (!TextUtils.isEmpty(s)) {
    
    
                    where.append(" and ").append(s)
                }
                count = db.delete(TouristSpot.TABLE, where.toString(), strings)
            }
        }

        // 通知数据发生了改变
        context!!.contentResolver.notifyChange(uri, null)
        return count
    }

    override fun update(
        uri: Uri,
        contentValues: ContentValues?,
        s: String?,
        strings: Array<out String>?
    ): Int {
    
    
        if (dbHelper == null) return 0
        val db = dbHelper!!.writableDatabase
        var count = 0 // 修改记录的数量

        when (matcher.match(uri)) {
    
    
            TOURISTS -> count =
                db.update(TouristSpot.TABLE, contentValues, s, strings)
            SPOT -> {
    
    
                // 解析所要修改记录的 ID
                val id = ContentUris.parseId(uri)
                // 拼接 where 语句
                val where = StringBuilder(TouristSpot.Spot._ID)
                where.append("=").append(id)
                if (!TextUtils.isEmpty(s)) {
    
    
                    where.append(" and ").append(s)
                }
                count = db.update(TouristSpot.TABLE, contentValues, where.toString(), strings)
            }
        }

        // 通知数据发生了改变
        context!!.contentResolver.notifyChange(uri, null)
        return count
    }
}

3.1 代码说明

UriMatcher工具类有两个函数:

  • void addURI(String authority, String path, int code),向UriMatcher对象注册Uri。参数 authoritypath合成一个Uri,参数code代表该Uri的标志码。

  • int match(Uri uri),根据Uri对象来判定其之前注册的标志码,如果不存在则返回 -1.

ContentUris字符串工具类:

  • long parseId(Uri contentUri)函数,从Uri中解析出 ID.
    例如 Uri com.guagua.providers.tourists/spot/3,它的 ID3.

  • Uri withAppendedId(Uri contentUri, long id) 函数,将 id 附加到 Uri 路径上。

4. 在AndroidManifest.xml 清单文件中注册该ContentProvider

<provider
            android:name=".TouristSpotContentProvider"
            android:authorities="com.guagua.providers.tourists" />

<provider/> 标签与 <activity/> 标签同级。

5. 创建一个用于“旅游景点”数据交互的 Activity,实现写入数据、读取数据的功能

5.1 创建一个名为 TouristSpotActivity 的页面( Activity),用于“旅游景点”数据交互

5.2 编辑TouristSpotActivity 的布局文件 activity_tourist_spot.xml

activity_tourist_spot.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    android:padding="20dp"
    tools:context=".TouristSpotActivity">

    <EditText
        android:id="@+id/spotEt"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:hint="spot"
        android:lines="1"/>

    <EditText
        android:id="@+id/detailEt"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:hint="detail"
        android:maxLines="3"/>

    <Button
        android:id="@+id/insertBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="insert" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#888888" />

    <TextView
        android:id="@+id/spotsTv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="Tourist Spot"
        android:textSize="15sp" />

</LinearLayout>

5.3 编辑TouristSpotActivity,实现写入数据、读取数据的功能

进入到这个页面,读取“旅游景点数据”并在界面上呈现;插入数据后,会执行读取“旅游景点数据”的操作。

Java 代码:

public class TouristSpotActivity extends AppCompatActivity {
    
    
    private TextView spotsTv, spotEt, detailEt;
    private final List<String> spotsList = new LinkedList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tourist_spot);
        spotEt = findViewById(R.id.spotEt);
        detailEt = findViewById(R.id.detailEt);
        spotsTv = findViewById(R.id.spotsTv);

        findViewById(R.id.insertBtn).setOnClickListener(view -> insertTouristSpot());

        readTouristSpot();
    }

    /**
     * 读取共享的旅游景点数据
     */
    private void readTouristSpot() {
    
    
        spotsList.clear();

        // 查询数据
        Cursor cursor = getContentResolver().query(TouristSpot.Spot.TOURISTS_CONTENT_URI, null, null, null, null);
        while (cursor.moveToNext()) {
    
    
            String spot = cursor.getString(cursor.getColumnIndex(TouristSpot.Spot.SPOT));
            String detail = cursor.getString(cursor.getColumnIndex(TouristSpot.Spot.DETAIL));
            spotsList.add(spot + "\n" + detail);// 将读取到的数据放入集合中
        }
        cursor.close();

        // 将数据在界面上呈现出来
        updateUI();
    }

    private void updateUI() {
    
    
        spotsTv.setText("");
        for (String s : spotsList) {
    
    // 简化的操作,用TextView显示数据
            spotsTv.append("\n\n");
            spotsTv.append(s);
        }
    }

    /**
     * 写入共享的旅游景点数据
     */
    private void insertTouristSpot() {
    
    
        String spot = spotEt.getText().toString();
        String detail = detailEt.getText().toString();
        if (TextUtils.isEmpty(spot) || TextUtils.isEmpty(detail)) return;

        // 插入数据
        ContentValues values = new ContentValues();
        values.put(TouristSpot.Spot.SPOT, spot);
        values.put(TouristSpot.Spot.DETAIL, detail);
        getContentResolver().insert(TouristSpot.Spot.TOURISTS_CONTENT_URI, values);

        // 提示插入数据完成
        Toast.makeText(this, " 添加景点记录完成 ", Toast.LENGTH_SHORT).show();
        spotEt.setText("");
        detailEt.setText("");

        // 重新读取数据,检验上述操作是否成功
        readTouristSpot();
    }
}

Kotlin 代码:

class TouristSpotActivity : AppCompatActivity() {
    
    
    private lateinit var spotsTv: TextView
    private lateinit var spotEt: EditText
    private lateinit var detailEt: EditText
    private val spotsList: LinkedList<String> = LinkedList()

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_tourist_spot)
        spotEt = findViewById(R.id.spotEt)
        detailEt = findViewById(R.id.detailEt)
        spotsTv = findViewById(R.id.spotsTv)

        findViewById<View>(R.id.insertBtn).setOnClickListener {
    
     insertTouristSpot() }

        readTouristSpot()
    }

    /**
     * 读取共享的旅游景点数据
     */
    private fun readTouristSpot() {
    
    
        spotsList.clear()

        // 查询数据
        contentResolver.query(
            TouristSpot.Spot.TOURISTS_CONTENT_URI,
            null, null, null, null
        )?.apply {
    
    
            while (moveToNext()) {
    
    
                val spot = getString(getColumnIndex(TouristSpot.Spot.SPOT))
                val detail = getString(getColumnIndex(TouristSpot.Spot.DETAIL))
                spotsList.add("$spot \n$detail")
            }
            close()
        }

        // 将数据在界面上呈现出来
        updateUI()
    }

    private fun updateUI() {
    
    
        spotsTv.text = ""
        for (s in spotsList) {
    
     // 简化的操作,用TextView显示数据
            spotsTv.append("\n\n")
            spotsTv.append(s)
        }
    }

    /**
     * 写入共享的旅游景点数据
     */
    private fun insertTouristSpot() {
    
    
        val spot = spotEt.text.toString()
        val detail = detailEt.text.toString()
        if (TextUtils.isEmpty(spot) || TextUtils.isEmpty(detail)) return

        // 插入数据
        val values = ContentValues()
        values.put(TouristSpot.Spot.SPOT, spot)
        values.put(TouristSpot.Spot.DETAIL, detail)
        contentResolver.insert(TouristSpot.Spot.TOURISTS_CONTENT_URI, values)

        // 提示插入数据完成
        Toast.makeText(this, " 添加景点记录完成 ", Toast.LENGTH_SHORT).show()
        spotEt.setText("")
        detailEt.setText("")

        // 重新读取数据,检验上述操作是否成功
        readTouristSpot()

    }
}

5.4 在 MainActivity 页面中增加一个按钮——进入TouristSpotActivity 页面的的入口

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    android:padding="30dp">

    ......

    <Button
        android:id="@+id/spotBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tourist Spot" />

</LinearLayout>

MainActivity 代码,
Java 代码:

    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ......

        Button spotBtn = findViewById(R.id.spotBtn);
        spotBtn.setOnClickListener(view -> {
    
    
            Intent intent = new Intent(this, TouristSpotActivity.class);
            startActivity(intent);
        });
    }

Kotlin 代码:

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ......

        val spotBtn = findViewById<Button>(R.id.spotBtn)
        spotBtn.setOnClickListener {
    
    
            val intent = Intent(this, TouristSpotActivity::class.java)
            startActivity(intent)
        }
    }

6. 运行项目,实验操作

运行项目,点击按钮,进入TouristSpotActivity 页面,在文本框中输入景点的名称和介绍,然后点击按钮插入数据;
然后就可以看到界面刷新,显示出刚刚输入的景点数据。

spot

项目代码地址

  • Java 版:
    https://github.com/BethelDEV/shaguaAndroid/tree/main/javaSource/ContentProviderSample

  • Kotlin 版:
    https://github.com/BethelDEV/shaguaAndroid/tree/main/kotlinSource/ContentProviderSample

猜你喜欢

转载自blog.csdn.net/flyingyajun/article/details/127055874