7-3 创建自己的 ContentProvider

7-3 创建自己的 ContentProvider,第1张

文章目录
  • 一、创建 ContentProvider
      • Uri 参数:
      • getType() 方法中返回的 MIME 类型
      • 解析请求内容的 Uri
      • UriMatcher 类
  • 二、代码示例
    • 安全性
  • 三、实现跨程序数据共享
    • 1、创建 DatabaseProvider
    • 2、创建 ProviderTest
      • 布局文件
      • 逻辑代码
    • 3、运行 ProviderTest
      • 解决运行报错
    • 运行结果:


一、创建 ContentProvider
  1. 创建类继承 ContentProvider

  2. 重写继承的 6 个抽象方法

  • onCreate()
	// 初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等 *** 作,
   // 返回true表示内容提供器初始化成功,返回false则表示失败。
   @Override
   public boolean onCreate() {
       return false;
   }
  • query()
 /**
     * 从内容提供器中查询数据
     *
     * @param uri           uri参数来确定查询哪张表
     * @param projection    用于确定查询哪些列
     * @param seletion      用于约束查询哪些行
     * @param selectionArgs
     * @param sortOrder     用于对结果进行排序
     * @return              查询的结果存放在Cursor对象中返回
     */
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String seletion, @Nullable String[] selectionArgs, @Nullable String sortOrder) {}
  • insert()
/**
     * 向内容提供器中添加一条数据
     *
     * @param uri               uri参数来确定要添加到的表
     * @param contentValues     待添加的数据保存在values参数中
     * @return                  返回一个用于表示这条新记录的URI
     */
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        return null;
    }
  • delete()
/**
     * 从内容提供器中删除数据
     *
     * @param uri               uri参数来确定删除哪一张表中的数据
     * @param selection         用于约束删除哪些行
     * @param selectionArgs
     * @return                  被删除的行数将作为返回值返回
     */
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
  • update()
/**
     * 更新内容提供器已有的数据
     *
     * @param uri               使用 uri 参数来确定更新哪一张表中的数据
     * @param contentValues     新数据保存在values参数中
     * @param selection         用于约束更新哪些行
     * @param selectionArgs     用于约束更新哪些行
     * @return                  受影响的行数将作为返回值返回
     */
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
  • getType()
/**
     * 根据传入的内容 URI 来返回相应的 MIME 类型。
     * MIME 类型:
     * 例如:
     * 1、Uri 为 cotent://com.example.app.provider/table1
     * 则对应的 MIME 类型为:vdn.android.cursor.dir/vdn.com.example.app.provider.table1
     *
     * 2、Uri 为 cotent://com.example.app.provider/table1/1
     * 则对应的 MIME 类型为:vdn.android.cursor.item/vdn.com.example.app.provider.table1
     *
     * @param uri
     * @return 返回 MIME 类型
     */
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
    	return null;
    }
Uri 参数:

相当于数据库 CRUD *** 作中传入的表名,指明要访问哪个程序的哪个数据库文件

这个参数由两个部分组成 authoritypath

  • authority 用于对不同应用程序进行区分,为避免冲突,一般用应用程序包名
  • path 对同一应用程序中不同的表进行区分,添加到 authority的后面
  • 字符串头部添加协议声明,以区分普通字符串和内容 Uri 字符串
  • 示例:
1、路径结尾:表示调用方期望访问该表中的全部数据
"content://com.example.app.provider/table1"
"content://com.example.app.provider/table2"

2、id 结尾:表示调用方期望访问该表中拥有相应 Id 的数据
"content://com.example.app.provider/table1/1"


可见,内容URI可以非常清楚地表达出我们想要访问哪个程序中哪张表里的数据。也正是因此,ContentResolver中的增删改查方法才都接收Uri对象作为参数,因为如果使用表名的话,系统将无法得知我们期望访问的是哪个应用程序里的表。

示例中的字符串目前也还只是单纯的字符串而已,要经过解析才能成为真正的 Uri 对象进行参数传递。

解析字符串:

Uri uri = Uri.prase("content://com.example.app.provider/table1");
getType() 方法中返回的 MIME 类型
  • 该方法根据传入的内容 URI 来返回相应的 MIME 类型。
  • 不同于 URI authority / path,MIME类型以 authority . path 结尾
1、路径结尾的内容 URI
例如:
content://com.example.app.provider/table1
对应的 MIME 类型为:
"vnd.android.cursor.dir/vnd.com.example.app.provider.table1"

1Id 结尾的内容 URI
例如:
content://com.example.app.provider/table1/1
对应的 MIME 类型为:
"vnd.android.cursor.item/vnd.com.example.app.provider.table1"
解析请求内容的 Uri
  • 创建我们自己的 ContentProvider需要对调用方传过来的 uri参数进行解析,从中分析出调用方希望访问的表。
1、解析 路径 结尾格式的内容 Uri,使用通配符 *,能够匹配任意表的内容 URI 格式
   "content://com.example.app.provider/*""content://com.example.app.provider"

2、解析 Id 结尾格式的内容 Uri,使用通配符 #,匹配 table1 表中任意一行数据的内容 URI 格式
"content://com.example.app.provider/table1/#"

UriMatcher 类

实现匹配内容 Uri 的功能

addURI()方法

  • 保存可以匹配的 Uri 类型
  • 3 个参数:authoritypath自定义代码(自定义代码用于和传入的 URI 进行匹配,匹配成功则返回对应类型的自定义代码)
// 访问 table1 表中所有的数据
    public static final int TABLE1_DIR = 0;
    // 访问 table1 表中的单条数据
    public static final int TABLE1_ITEM = 1;
    // 访问 table2 表中所有的数据
    public static final int TABLE2_DIR = 2;
    // 访问 table2 表中的单条数据
    public static final int TABLE2_ITEM = 3;
    private static final UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
        uriMatcher.addURI("com.example.app.provider", "table1/#", TABLE1_ITEM);
        uriMatcher.addURI("com.example.app.provider", "table2", TABLE2_DIR);
        uriMatcher.addURI("com.example.app.provider", "table2/#", TABLE2_ITEM);
    }

match() 方法

  • 用于和已保存的可解析的 URI格式进行匹配,即 addURI()方法中添加的那些 URI,匹配成功则返回 addURI()中对应条目的自定义代码,然后我们就知道调用方具体想访问什么数据。
  • 常在增删改查中使用
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String seletion, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        // 对传入的 uri 进行匹配,update(), insert(), delete(),相似
        switch (uriMatcher.match(uri)) {
            case TABLE1_DIR:
                // 查询 table1 表中所有的数据
                break;
            case TABLE1_ITEM:
                // 查询 table1 表的单条数据
                break;
            case TABLE2_DIR:
                // 查询 table1 表中所有的数据
                break;
            case TABLE2_ITEM:
                // 查询 table1 表中的单条数据
                break;
            default:
                break;
        }
        return null;
    }

二、代码示例
public class MyProvider extends ContentProvider {
    // 访问 table1 表中所有的数据
    public static final int TABLE1_DIR = 0;
    // 访问 table1 表中的单条数据
    public static final int TABLE1_ITEM = 1;
    // 访问 table2 表中所有的数据
    public static final int TABLE2_DIR = 2;
    // 访问 table2 表中的单条数据
    public static final int TABLE2_ITEM = 3;
    private static final UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
        uriMatcher.addURI("com.example.app.provider", "table1/#", TABLE1_ITEM);
        uriMatcher.addURI("com.example.app.provider", "table2", TABLE2_DIR);
        uriMatcher.addURI("com.example.app.provider", "table2/#", TABLE2_ITEM);
    }

    // 初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等 *** 作,
    // 返回true表示内容提供器初始化成功,返回false则表示失败。
    @Override
    public boolean onCreate() {
        return false;
    }

    /**
     * 从内容提供器中查询数据
     *
     * @param uri           uri参数来确定查询哪张表
     * @param projection    用于确定查询哪些列
     * @param seletion      用于约束查询哪些行
     * @param selectionArgs
     * @param sortOrder     用于对结果进行排序
     * @return              查询的结果存放在Cursor对象中返回
     */
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String seletion, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        // 对传入的 uri 进行匹配,update(), insert(), delete(),相似
        switch (uriMatcher.match(uri)) {
            case TABLE1_DIR:
                // 查询 table1 表中所有的数据
                break;
            case TABLE1_ITEM:
                // 查询 table1 表的单条数据
                break;
            case TABLE2_DIR:
                // 查询 table1 表中所有的数据
                break;
            case TABLE2_ITEM:
                // 查询 table1 表中的单条数据
                break;
            default:
                break;
        }
        return null;
    }

    /**
     * 根据传入的内容 URI 来返回相应的 MIME 类型。
     * MIME 类型:
     * 例如:
     * 1、Uri 为 cotent://com.example.app.provider/table1
     * 则对应的 MIME 类型为:vdn.android.cursor.dir/vdn.com.example.app.provider.table1
     *
     * 2、Uri 为 cotent://com.example.app.provider/table1/1
     * 则对应的 MIME 类型为:vdn.android.cursor.item/vdn.com.example.app.provider.table1
     *
     * @param uri
     * @return 返回 MIME 类型
     */
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)) {
            case TABLE1_DIR:
            // 返回 MIME 字符串
                return "vnd.android.cursor.dir/vdn.com.example.app.provider.table1";
            case TABLE1_ITEM:
                return "vdn.android.cursor.item/vdn.com.example.app.provider.table1";
            case TABLE2_DIR:
                return "vnd.android.cursor.dir/vdn.com.example.app.provider.table2";
            case TABLE2_ITEM:
                return "vdn.android.cursor.item/vdn.com.example.app.provider.table2";
            default:
                break;
        }
        return null;
    }

    /**
     * 向内容提供器中添加一条数据
     *
     * @param uri               uri参数来确定要添加到的表
     * @param contentValues     待添加的数据保存在values参数中
     * @return                  返回一个用于表示这条新记录的URI
     */
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        return null;
    }

    /**
     * 从内容提供器中删除数据
     *
     * @param uri               uri参数来确定删除哪一张表中的数据
     * @param selection         用于约束删除哪些行
     * @param selectionArgs
     * @return                  被删除的行数将作为返回值返回
     */
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    /**
     * 更新内容提供器已有的数据
     *
     * @param uri               使用 uri 参数来确定更新哪一张表中的数据
     * @param contentValues     新数据保存在values参数中
     * @param selection         用于约束更新哪些行
     * @param selectionArgs     用于约束更新哪些行
     * @return                  受影响的行数将作为返回值返回
     */
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}

安全性
  • 所有的 CRUD *** 作都必须要匹配到想要获取内容的应用所提供的内容 URI 格式才能进行。
  • 自己创建的内容提供器不要添加隐私数据的 URI,即存放了隐私数据的数据库,这样外部程序就访问不到这些隐私数据。

三、实现跨程序数据共享 1、创建 DatabaseProvider
	我们在之前的 DatabaseTest 项目中创建 Provider。
	右键 -> new -> Others -> ContentProvider,输入类名,指定该内容提供器的 authority,标识该程序。


点击 finish 后会生成 DatabaseProvider.java文件,IDE 已经自动帮我们继承了 ContentProvider类以及导入了该类的 6 个抽象方法。同时AndroidManifest.xml文件中自动声明了该 ContentProviderauthorities对应创建时填入的值。enabledexported属性对应了创建时勾选的两个选项。

<application>
...
	<provider
            android:name="com.example.contentprovider.DatabaseProvider"
            android:authorities="com.example.databasetest.provider"
            android:enabled="true"  
            android:exported="true">
    provider>
application>

DatabaseProvider的代码:

package com.example.contentprovider;

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

import com.example.databasetest.MyDatabaseHelper;

public class DatabaseProvider extends ContentProvider {
    public static final int BOOK_DIR = 0;
    public static final int BOOK_ITEM = 1;
    public static final int CATEGORY_DIR = 2;
    public static final int CATEGORY_ITEM = 3;
    public static final String AUTHORITY = "com.example.databasetest.provider";
    private static UriMatcher sUriMatcher;
    private MyDatabaseHelper mDbHelper;

    // 初始化 ContentProvider 可供访问的数据,以 URI 形式封装进 UriMatcher 中,
    // 以便其他程序访问数据时利用 UriMatcher 进行 match ,得到返回的自定义代码,我们就知道其他程序想要访问什么数据了,
    // 并执行相应的 *** 作
    static {
        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        sUriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
        sUriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        sUriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        sUriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
    }

    public DatabaseProvider() {
    }

    /**
     * 在此创建或升级数据库
     *
     * @return 返回 true 表示内容提供器初始化成功,这时数据库完成了创建和升级 *** 作
     */
    @Override
    public boolean onCreate() {
        // Implement this to initialize your content provider on startup.
        mDbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
        return true;
    }

    /**
     * 1、先获取到 SQLiteDatabase 的实例
     * 2、根据传入的 uri 参数,利用 UriMatcher.match() 判断出用户想要访问哪张表
     * uri.getPathSegments().get(1); 方法
     * 此方法会对 uri 参数 authority 之后的部分,”/“符号进行分割,分割结果会放到一个字符串列表中,
     * 所以这个列表的第 0 个位置存储的是路径(数据库),第 1 个位置存放的是 id
     * 3、如果传入的是 id 则根据 id 查询单条数据
     *
     * @param uri
     * @param projection
     * @param selection
     * @param selectionArgs
     * @param sortOrder
     * @return  返回 Cursor 对象
     */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // Implement this to handle query requests from clients.
        // 查询数据
        SQLiteDatabase db = mDbHelper.getReadableDatabase();
        Cursor cursor = null;
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIR:
                cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                cursor = db.query("Book", projection, "id = ?", new String[]{bookId}, null, null, sortOrder);
                break;
            case CATEGORY_DIR:
                cursor = db.query("Category", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                cursor = db.query("Category", projection, "id = ?", new String[]{categoryId}, null, null, sortOrder);
                break;
            default:
                break;
        }
        return cursor;
    }

    // 返回删除的行数
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Implement this to handle requests to delete one or more rows.
        // 删除数据
        SQLiteDatabase db = mDbHelper.getWritableDatabase();
        int deleteRaws = 0;
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIR:
                deleteRaws = db.delete("Book", selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                deleteRaws = db.delete("Book", "id = ?", new String[]{bookId});
                break;
            case CATEGORY_DIR:
                deleteRaws = db.delete("Category", selection, selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                deleteRaws = db.delete("Category", "id = ?", new String[]{categoryId});
                break;
            default:
                break;
        }
        return deleteRaws;
    }


    // 返回一个能表示这条新增数据的 URI,插入的 id 结尾
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // Implement this to handle requests to insert a new row.
        // 添加数据
        SQLiteDatabase db = mDbHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIR:
            case BOOK_ITEM:
                long newBookId = db.insert("Book", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
                break;
            case CATEGORY_DIR:
            case CATEGORY_ITEM:
                long newCategoryId = db.insert("Category", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId);
                break;
            default:
                break;
        }
        return uriReturn;
    }

    // 返回受影响的行数
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // Implement this to handle requests to update one or more rows.
        // 更新数据
        SQLiteDatabase db = mDbHelper.getWritableDatabase();
        int updateRows = 0;
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIR:
                updateRows = db.update("Book", values, selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                updateRows = db.update("Book", values, "id = ?", new String[]{bookId});
                break;
            case CATEGORY_DIR:
                updateRows = db.update("Category", values, selection, selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                updateRows = db.update("Category", values, "id = ?", new String[]{categoryId});
                break;
            default:
                break;
        }
        return updateRows;
    }

    // 返回 uri 对应的 MIME 类型字符串
    @Override
    public String getType(Uri uri) {
        // Implement this to handle requests for the MIME type of the data
        // at the given URI.
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
            case CATEGORY_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";
        }
        return null;
    }
}

运行该程序,接着新建一个新项目 ProviderTest ,测试访问我们新建的 ContentProvider 的数据。

2、创建 ProviderTest 布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/add_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add To Book"
        android:textAllCaps="false" />

    <Button
        android:id="@+id/query_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Query From Book"
        android:textAllCaps="false" />

    <Button
        android:id="@+id/update_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Update Book"
        android:textAllCaps="false" />

    <Button
        android:id="@+id/delete_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Delete From Book"
        android:textAllCaps="false" />

</LinearLayout>
逻辑代码
package com.example.providertest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    public static final String DATABASE_URI = "content://com.example.databasetest.provider";
    private String newId;

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

        providerTest();
    }

    private void providerTest() {
        // 添加数据
        Button addData = findViewById(R.id.add_data);
        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("MainActivity", "click add");
                Uri uri = Uri.parse(DATABASE_URI + "/book");
                ContentValues values = new ContentValues();
                values.put("name", "A Clash of Kings");
                values.put("author", "George Martin");
                values.put("pages", 1040);
                values.put("price", 55.55);
                Uri newUri = getContentResolver().insert(uri, values);
                newId = newUri.getPathSegments().get(1);
            }
        });

        // 查询数据
        Button queryData = findViewById(R.id.query_data);
        queryData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("MainActivity", "click query");
                Uri uri = Uri.parse(DATABASE_URI + "/book");
                Cursor cursor = getContentResolver().query(uri, null, null, null, null);
                if (cursor != null) {
                    while (cursor.moveToNext()) {
                        String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
                        String author = cursor.getString(cursor.getColumnIndexOrThrow("author"));
                        int pages = cursor.getInt(cursor.getColumnIndexOrThrow("pages"));
                        double price = cursor.getDouble(cursor.getColumnIndexOrThrow("price"));
                        Log.d("MainActivity", "book name is " + name);
                        Log.d("MainActivity", "book author is " + author);
                        Log.d("MainActivity", "book pages is " + pages);
                        Log.d("MainActivity", "book price is " + price);
                    }
                    cursor.close();
                }
            }
        });

        // 更新数据
        Button updateData = findViewById(R.id.update_data);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("MainActivity", "click update");
                Uri uri = Uri.parse(DATABASE_URI + "/book/" + newId);
                ContentValues values = new ContentValues();
                values.put("name", "A Storm of Swords");
                values.put("pages", 1216);
                values.put("price", 24.05);
                getContentResolver().update(uri, values, null, null);
            }
        });

        // 删除数据
        Button deleteData = findViewById(R.id.delete_data);
        deleteData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse(DATABASE_URI + "/book/" + newId);
                getContentResolver().delete(uri, null, null);
            }
        });

    }
}
3、运行 ProviderTest

点击 Add To Book 向我们创建的 Provider 添加数据。
应用竟然闪退报错了。查看 Logcat 的错误信息。

看到这条错误信息,

  • 第一反应是检查 ProviderTest中 增删改查四个方法传入的 Uri 有没有错误。
  • DatabaseTestUriMatcher静态代码块中 addURI存储的匹配类型是不是有误。
  • DatabaseTestAndroidManifest.xml文件中声明 Provider中的authority属性是否一致。

以上三个地方均没有问题,运行还是出现了上述错误。所以可能是运行的安卓版本过高,用低版本 API 写的教程有些内容过时了。

解决运行报错

解决 java.lang.IllegalArgumentException: Unknown URL content://authority 的问题

由于我使用的手机是 Android 10,所以运行 ProviderTest之前先打开 DatabaseTest应用,再点击 ProviderTest项目中的增删改查方法,就不会闪退了。

运行结果:


直接在手机上查看数据库内容用的是郭霖大神开发的开源框架 Glance
使用教程:郭霖 Glance

使用 Glance的方法也很简单,直接添加该依赖即可

repositories {
  google()
  mavenCentral()
}

dependencies {
    debugImplementation 'com.guolindev.glance:glance:1.1.0'
}

需要注意的是 glance只能在使用 androidx库的项目中使用。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/786947.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-05
下一篇 2022-05-05

发表评论

登录后才能评论

评论列表(0条)

保存