Android APP完整基础教程(08)四大组件-ContentProvider

42 篇文章 14 订阅

1 ContentProvider相关概念

ContentProvider是跨应用数据交换的标准,主要用于在许可的情况下获取其他应用的数据。为了更好的了解ContentProvider,我们需要了解URI和MIME。

1.1 URI(Uniform Resource Identifier)统一资源标识符

这个标识符主要用于唯一标识 ContentProvider和其中的数据。URI 为系统中的每一个资源赋予一个名字。

1.1.1 URI格式解读

每一个 ContentProvider 都拥有一个公共的 URI,用于表示 ContentProvider 所提供的数据。URI 的格式如下:


// contentprovider规则与案例
[scheme:][//host:port][path][?query]
content://com.ags.myprovider/tablename/id:

// 网页案例对比
[协议][主机][唯一资源名]
https://blog.csdn.net/vviccc?type=blog

URI格式内容解读:

  1. 标准前缀(scheme)对应content://,用来说明这个Content Provider控制数据。
  2. URI 的标识(host:port)对应com.ags.myprovider,用于唯一标识ContentProvider,外部调用可以根据这个标识来找到它(注意:对于第三方应用程序,为了保证 URI 标识的唯一性,它必须是一个完整的、小写的类名)在Androidmanifest.xml配置文件中Contentprovider组件中的authorities属性中进行说明。

  3. 路径(path)对应tablename,可以理解为要操作的数据库中的表名(比如:操作tablename表中id为7的记录,构建路径为:/tablename/7;操作tablename表中id为7的记录的name字段,构建路径:/tablename/7/name)

  4. 记录ID(query)对应id,如果URI中包含表示需要获取的记录的 ID,则返回该id对应的数据,如果没有ID,就表示返回全部。

如果需要将一个字符串转换成Uri,可以使用Uri类中的parse()方法,如:

Uri uri = Uri.parse("content://com.ags.myprovider/tablename");

1.1.2 Uri 的工具类

Android 提供了两个用于操作 Uri 的工具类,分别为 UriMatcher 和 ContentUris 。

@1 UriMatcher:用于匹配 Uri,它的使用步骤如下:

创建端使用如下:

//@创建端
//定义常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

//注册uri内容,如果有调用match()方法,发现匹配“content:...”的路径,则返回匹配码为1
uriMatcher.addURI("content://com.ags.myprovider", " tablename ", 1);

调用端使用如下:

//@调用端
//使用uriMatcher.match(Uri) 方法对输入的 Uri 进行匹配,如果匹配就返回对应的匹配码
switch (uriMatcher.match(Uri.parse("content://..."))) {
    case 1:
        //match 1, todo something
        break;
    //...
    default:
        //match nothing, todo something
        break;
}

@2 ContentUris

用于操作Uri路径后面的ID,它的关键方法是withAppendedId(Uri uri, long id) 和 parseId(Uri uri)。使用解读如下:

//通过字符串生成Uri
Uri uri = Uri.parse("content://cn.ags.myprovider/user")

//withAppendedId:为路径加上ID
//添加ID后缀,执行后Uri为:content://cn.scu.myprovider/user/7
Uri resultUri = ContentUris.withAppendedId(uri, 7);

//parseId:从路径中获取ID
//获取的结果为:7
long personid = ContentUris.parseId(uri);

1.2 MIME

MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。简而言之,就是根据MIME标识位判定 数据/文件 的类别,进而做出不同的操作。

多用途互联网邮件扩展,它是一个互联网标准,在1992年最早应用于电子邮件系统,但后来也应用到浏览器。服务器会将它们发送的多媒体数据的类型告诉浏览器,而通知手段就是说明该多媒体数据的MIME类型,从而让浏览器知道接收到的信息哪些是MP3文件,哪些是Shockwave文件等等。服务器将MIME标志符放入传送的数据中来告诉浏览器使用哪种插件读取相关文件。

@1 MIME Type包括许多文件类型,比如:图片、视频、音频等,如下所示:

MIME字段对应文件类型
text/plain纯文本
image/gifgif图像
image/jpegjpeg图像
video/mpegMPEG动画
audio/mp3mp3音乐

更多格式可以参考文档:MIME 参考手册

@2 Android中ContentProvider根据URI来返回MIME类型解读

ContentProvider 会返回一个包含两部分的字符串,每个内容类型的 Android MIME 类型有两种形式:多条记录(集合)和单条记录。如下:

  • vnd.android.cursor.dir/自定义,集合类型(自定义部分可自己填写)
  • vnd.android.cursor.item/自定义,单条记录(自定义部分可自己填写)

注意:vnd 表示这些类型和子类型具有非标准的、供应商特定的形式。Android中类型已经固定好了,因此只能区别是集合还是单条具体记录。

ContentProvider在使用Intent时会用到MIME类型,根据 Mimetype 打开符合条件的活动。

1.3 ContentProvider的关键方法

有了前面Uri和MIME的了解,再来看Contentprovider类的关键方法有创建回调、CRUD操作(增删改查)、类型获取,对于Contentprovider的子类而言,关键的几个方法如下所示:

2 ContentProvider配置

在AndroidManifest.xml里声明,如下所示:

<provider android:name="MyProvider"
	android:exported="true"
    android:authorities="com.ags.myprovider" 
	/>

常见属性一般有name、exported(是否允许被其他应用调用),permission(相当于配置了readPermission和writePermission两个权限),这里针对关键参数进行说明:

  • authorities:指定了ContentProvider的Uri,一般是provider所在包包名+provider名。
  • permission:这里会细分,可单独配置readPermission表示读ContentProvider的权限,writePermission表示写ContentProvider的权限,permission属性。

3 ContentProvider & ContentResolver

ContentProvider的执行涉及两个应用(一个通过ContentProvider来提供数据,另一个通过ContentResolver来获取数据),关系图如下所示:

可以这样理解,应用/进程B调用ContentResolver中的CRUD方法实际上就是调用了应用/进程A中对应Uri的ContentProvider的CRUD方法。

4 ContentProvider使用

4.1 提供端ContentProvider

@1 自定义ContentProvider

先定义一个辅助类DbOpenHelper,代码如下:

public class DbOpenHelper extends SQLiteOpenHelper {
    final String CREATE_SQL = "CREATE TABLE test(_id INTEGER PRIMARY KEY AUTOINCREMENT,name)";

    public DbOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,int version) {
        super(context, name, null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_SQL);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO Auto-generated method stub
    }
}

这里定义一个MyContentProvider,代码如下所示:

public class MyContentprovider extends ContentProvider {
    private static String TAG = "MyContentprovider";
    private static final int MATCH_SUCCESS_VALUE = 1;

    //初始化UriMatcher和DbOpenHelper
    private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
    private DbOpenHelper dbOpenHelper;

    static{
        matcher.addURI("com.ags.myprovider", "test", MATCH_SUCCESS_VALUE);
    }

    @Override //被创建后调用
    public boolean onCreate() {
        Log.d(TAG, "onCreate: ");
        dbOpenHelper = new DbOpenHelper(this.getContext(), "test.db", null, 1);
        return false;
    }

    @Nullable
    @Override //返回Uri所代表的MIME数据类型,结果是多条记录/单条记录
    public String getType(@NonNull Uri uri) {
        Log.d(TAG, "getType: ");
        return null;
    }

    @Nullable
    @Override //C(create):根据Uri插入value值
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.d(TAG, "insert: ");
        switch(matcher.match(uri))
        {
            case MATCH_SUCCESS_VALUE:
                SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
                long rowId = db.insert("test", null, values);
                if(rowId > 0)
                {
                    Uri nameUri = ContentUris.withAppendedId(uri, rowId);
                    getContext().getContentResolver().notifyChange(nameUri, null);
                    Log.d(TAG, "insert: notifyChange");
                    return nameUri;
                }
                break;
            default:
                throw new IllegalStateException("Unexpected value: " + matcher.match(uri));
        }
        return null;
    }

    @Nullable
    @Override //R(Retrieve):根据Uri查询selection条件多匹配的所有记录
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.d(TAG, "query: ");
        return null;
    }

    @Override //U(update):根据Uri更新selection条件多匹配的所有记录
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d(TAG, "update: ");
        return 0;
    }
	
    @Override //D(delete):根据Uri删除selection条件多匹配的所有记录
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d(TAG, "delete: ");
        return 0;
    }
}

之后在AndroidManifest中注册即可,上面的配置直接copy过来,如下:

<provider android:name="MyProvider"
	android:exported="true"
    android:authorities="com.ags.myprovider" 
	/>

作为提供数据端的ContentProvider该部分就完成了。

@2 系统ContentProvider

系统提供ContentProvider很多,详细参考文档:android系统支持的provider集合概览

4.2 使用端ContentResolver

@1 针对自定义ContentProvider的处理

通过ContentResolver来获取自定义MyContentProvider提供的数据,ContentResolver部分的代码实现如下所示:

public class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity";
    private Button btn_start;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_start = findViewById(R.id.btn_start);
        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //1 构建value值
                ContentValues values = new ContentValues();
                values.put("name", "test");
                //2 根据Contentprovider获取对应的Uri
                Uri uri = Uri.parse("content://com.ags.myprovider/test");
                Log.d(TAG, "onClick: insert operation");
                //3 获取ContentResolver对象,根据Uri找到对应的ContentProvider,插入values
                getContentResolver().insert(uri, values);
            }
        });
    }
}

@2 针对系统ContentProvider的处理

这里以获取联系人信息为例。通过ContentResolver来获取联系人应用对应的Provider提供的数据(联系人名称、联系人电话号码、联系人ID号),ContentResolver部分的代码实现如下所示:

首先需要在AndroidManifest.xml文件中添加权限,如下:

<uses-permission android:name="android.permission.READ_CONTACTS" />

其次,ContentResolver部分的代码实现如下所示:

public class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity";
    private Button btn_start;
    private Uri uri;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_start = findViewById(R.id.btn_start);
        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String contactId = "";
                uri = Uri.parse("content://com.android.contacts/raw_contacts");
                Cursor cursor= getContentResolver().query(uri, null, null, null, null);
                while(cursor.moveToNext())
                {
                    //获取联系人ContactID和姓名
                    contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
                    String cName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                    Log.d(TAG, "Contact:ID:" + contactId);
                    Log.d(TAG, "Contact:name:" + cName);
                    Cursor phoneCursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                            null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + contactId, null, null);
                    while (phoneCursor.moveToNext()) {
                        //根据ContactID获取联系人电话号码
                        String phone = phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                        phone = phone.replace("-", "").replace(" ", "");
                        //phone = phone.replace(" ", "");
                        Log.d(TAG, "Contact: TelNum:"+phone);
                        Log.d(TAG, "Contact:=================");
                    }
                    phoneCursor.close();
                }
                cursor.close();
            }
        });
    }
}

注意:电话号码要单独查询,一方面有多个,另一方面因为有空格需要特殊处理下。

5 ContentObserver监听机制

如果ContentProvider的数据获取者要知道数据变化,则在ContentProvider数据提供者发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的数据获取者。比如在4.1部分中关于insert操作中就有该实现,如下所示:

public class MyContentprovider extends ContentProvider {
{
    //....
    public Uri insert(Uri uri, ContentValues values) {
        switch(matcher.match(uri))
        {
            case MATCH_SUCCESS_VALUE:
                SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
                long rowId = db.insert("test", null, values);
                if(rowId > 0)
                {
                    Uri nameUri = ContentUris.withAppendedId(uri, rowId);
                    getContext().getContentResolver().notifyChange(nameUri, null);
                    return nameUri;
                }
                break;
                //...
        }
        return null;
    }
    //...
}

数据提供者在数据变化时给出了通知,接下来在数据获取者ContentResolver部分中需要以下关键步骤:

  • 自定义ContentObserver类并实现onChange方法,实现监听器类。
  • 调用ContentResolver的registerContentObserver方法注册监听器。

相关代码实现如下:

public class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity";
    private Button btn_start;
    
    //1 创建ContentObserver的子类MyObserver并实现onChange方法
    private final class MyObserver extends ContentObserver{
        public MyObserver(Handler handler) {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange) { 
            getContentResolver().query( \
               Uri.parse("content://com.ags.myprovider/test"),null,null,null,null);
            Log.d(TAG, "onChange: effected");
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_start = findViewById(R.id.btn_start);
        //2 注册MyObserver监听器
		getContentResolver().registerContentObserver( \ 
            Uri.parse("content://com.ags.myprovider/test"),true,new MyObserver(new Handler()));

        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //1 构建value值
                ContentValues values = new ContentValues();
                values.put("name", "test");
                //2 根据Contentprovider获取对应的Uri
                Uri uri = Uri.parse("content://com.ags.myprovider/test");
                Log.d(TAG, "onClick: insert operation");
                //3 获取ContentResolver对象,根据Uri找到对应的ContentProvider,插入values
                getContentResolver().insert(uri, values);
            }
        });
    }
}

6 官方文档索引

关于ContentProvider更多内容查看文档:Android组件之ContentProvider组件

总结

  • 了解Uri和MIME、ContentProvider和ContentResolver基本概念、Androidmanifest配置、常见系统ContentProvider、ContentObserver监听机制。
  • 自定义ContentProvider和ContentResolver交互、系统ContentProvider获取数据方式、ContentObserver监听实现。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图王大胜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值