栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

7-3 创建自己的 ContentProvider

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

7-3 创建自己的 ContentProvider

文章目录
  • 一、创建 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()
 
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String seletion, @Nullable String[] selectionArgs, @Nullable String sortOrder) {}
  • insert()
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        return null;
    }
  • delete()
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
  • update()
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
  • getType()
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
    	return null;
    }
Uri 参数:

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

这个参数由两个部分组成 authority 和 path

  • 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"

1、Id 结尾的内容 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
    @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;
    }

    
    @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;
    }

    
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        return null;
    }

    
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    
    @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文件中自动声明了该 ContentProvider。authorities对应创建时填入的值。enabled及exported属性对应了创建时勾选的两个选项。


...
	
    

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() {
    }

    
    @Override
    public boolean onCreate() {
        // Implement this to initialize your content provider on startup.
        mDbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
        return true;
    }

    
    @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 布局文件



    
逻辑代码
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 有没有错误。
  • DatabaseTest中 UriMatcher静态代码块中 addURI存储的匹配类型是不是有误。
  • DatabaseTest中AndroidManifest.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库的项目中使用。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/841334.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号