Android四大组件之Content Provider

本文详细介绍了Android中的ContentProvider组件,包括其基本概念、开发步骤、配置方法及如何使用ContentResolver进行数据操作。此外还展示了如何创建数据库、实现数据共享以及监听数据变更的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Content Provider简介

1.ContentProvider是android四大组件之一,需要在AndroidManifest.xml中进行配置.
2.为了在应用程序之间交换数据,android提供了ContentProvider,是不同应用程序之间进行数据交换的标准API.
3.当应用程序需要把自己数据暴露给其他程序时,就可以通过提供的ContentProvider来实现.
4.其他程序通过ContentResolver根据Uri去访问操作ContentProvider暴露的数据.

Content Provider开发

  • 实现步骤
1.继承ContentProvider,实现query(),insert(),update()和delete()方法
2.在AndroidManifest.xml文件中注册该ContentProvider,指定android:authorities
  • 配置ContentProvider
<application>    
    <provider 
        android:name=".xxxProvider"
        android:authorities="com.example.provider"
        android:exported="true"/>
</application>

Content Provider使用

  • Uri介绍
Andriod Application Study Note Url 9 uri.jpg
scheme:协议,不仅包括传统的http等网络协议,还有content://来表示本地ContentProvider所提供的数据。
authority:域名部分,包括host和port,host是主机名称,port是通信端口。Android系统就是由这个部分来找到操作哪个ContentProvider。
path:资源路径(数据部分),当访问者需要访问不同资源时,这个部分是动态改变的。
Android为多媒体提供的ContentProvider的Uri如下:
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI:存储在外置SD卡上音频内容的URI
MediaStore.Audio.Media.INTERNAL_CONTENT_URI:存储在内置SD卡上音频内容的URI
MediaStore.Images.Media.EXTERNAL_CONTENT_URI:存储在外置SD卡上图片文件的URI
MediaStore.Images.Media.INTERNAL_CONTENT_URI:存储在内置SD卡上图片文件的URI
MediaStore.Video.Media.EXTERNAL_CONTENT_URI:存储在外置SD卡上视频内容的URI
MediaStore.Video.Media.INTERNAL_CONTENT_URI:存储在内置SD卡上视频内容的URI

Android系统对联系人管理ContentProvider的几个Uri如下:
ContactsContract.Contacts.CONTENT_URI:管理联系人的Uri
ContactsContract.CommonDataKinds.Phone.CONTENT_URI:管理联系人电话的Uri
ContactsContract.CommonDataKinds.Email.CONTENT_URI:管理联系人Email的Uri
  • ContentResolver根据Uri去访问操作ContentProvider数据,ContentResolver方法如下:
insert(Uri url, ContentValues values): 向Uri对应的ContentProvider中插入values对应的数据
delete(Uri url, String where, String[] selectionArgs): 删除Uri对应的ContentProvider中where提交匹配的数据
update(Uri uri, ContentValues values, String where, String[] selectionArgs): 更新Uri对应的ContentProvider中where提交匹配的数据
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder): 查询Uri对应的ContentResolver中where提交匹配的数据
  • ContentResolver的增删改查方法中会调用ContentProvider对应的增删改查方法,ContentProvider方法如下:
onCreate(): 在创建ContentProvider时调用
insert(Uri uri, ContentValues values): 用于添加数据到指定Uri的ContentProvider中
delete(Uri uri, String selection, String[] selectionArgs): 用于从指定Uri的ContentProvider中删除数据
update(Uri uri, ContentValues values, String selection, String[] selectionArgs): 用于更新指定Uri的ContentProvider中的数据
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder): 用于查询指定Uri的ContentProvider,返回一个Cursor
getType(Uri uri): 用于返回指定的Uri中的数据的MIME类型
  • ContentResolver与ContentProvider关系图
Andriod Application Study Note Url 9 1.jpg

CRUD(增删改查)方法的第一个参数都是Uri
Uri是ContentResolver和ContentProvider进行数据交换的标识

Content Provider监听

  • 实现步骤
1.继承ContentObserver基类,并重写onChange(boolean selfChange)方法
2.通过ContentResolver向指定Uri注册ContentObserver监听器,在不需要时,需要对监听器取消注册

Content Provider应用实例

  • 创建数据库
package com.example.provider;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DatabaseHelper extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "example.db";
    private static final int DATABASE_VERSION = 1;

    public DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE user(_id integer primary key autoincrement,name varchar(10),phone varchar(11) NULL)";
        db.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
  • 创建ContentProvider 来对数据库进行共享
package com.example.provider;

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

public class UserProvider extends ContentProvider {
    private DatabaseHelper mHelper;
    private static final UriMatcher MARCHER = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int USERS = 1;
    private static final int USER = 2;
    private static final String AUTHORITY = "com.example.provider";
    private static final String DATABASE_TABLE = "user";

    /*
    UriMatcher工具类提供了2个方法用来确定内容提供者实际能处理的Uri:
      1>.void addRUI(String authority,String path,int code):用于向UriMathcher对象注册Uri.authority和path组合成一个Uri,而code则代表该Uri对应的标识符.
      2>.int match(Uri uri):根据前面注册的Uri来判断指定uri对应的标识符,如果找不到匹配的标识码就返回-1.
    */
    static {
        MARCHER.addURI(AUTHORITY, "user", USERS);
        MARCHER.addURI(AUTHORITY, "user/#", USER);
    }

    // 该方法用于返回当前Uri所代表数据的MIME类型
    // 如果操作的数据数据集合类型,MIME类型字符串应该以vnd.android.cursor.dir/开头
    // 如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头
    @Override
    public String getType(Uri uri) {
        switch (MARCHER.match(uri)) {
            case USERS:
                return "vnd.android.cursor.dir/user";
            case USER:
                return "vnd.android.cursor.item/user";
            default:
                throw new IllegalArgumentException("Wrong Uri!");
        }
    }

    @Override
    public boolean onCreate() {
        mHelper = new DatabaseHelper(getContext());
        return true;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = mHelper.getWritableDatabase();
        long _id = db.insert(DATABASE_TABLE, null, values);
        if (_id > 0)
            sendNotifyChange(uri);
        return ContentUris.withAppendedId(uri, _id);
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = mHelper.getWritableDatabase();
        int count = db.delete(DATABASE_TABLE, selection + "=?", selectionArgs);
        if (count > 0)
            sendNotifyChange(uri);
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        SQLiteDatabase db = mHelper.getWritableDatabase();
        int count = db.update(DATABASE_TABLE, values, selection + "=?", selectionArgs);
        if (count > 0) {
            String name = values.getAsString("name");
            sendNotifyChange(Uri.withAppendedPath(uri, name));
        }
        return count;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        SQLiteDatabase db = mHelper.getReadableDatabase();
        Cursor mCursor = db.query(DATABASE_TABLE, projection, selection + "=?", selectionArgs,
                null, null, sortOrder);
        return mCursor;
    }

    private void sendNotifyChange(Uri uri) {
        getContext().getContentResolver().notifyChange(uri, null);
    }
}
  • 配置ContentProvider
<application
        <provider
            android:name="com.example.provider.UserProvider"
            android:authorities="com.example.provider"
            android:exported="true" />
    </application>
  • 使用Content Provider操作并监听数据
package com.example.providerdemo;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {
    private static final Uri USER_URI = Uri.parse("content://com.example.provider/user");
    private ContentResolver mResolver;
    private Context mContext;
    private EditText name, phone;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 0x123) {
                show("Date Changed!");
            }
        }
    };

    private ContentObserver observer = new ContentObserver(mHandler) {
        @Override
        public void onChange(boolean selfChange) {
            mHandler.sendEmptyMessage(0x123);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mResolver = getContentResolver();
        mContext = MainActivity.this;
        name = (EditText) findViewById(R.id.name);
        phone = (EditText) findViewById(R.id.phone);

        Uri uri = Uri.withAppendedPath(USER_URI, "xiaoming");
        mResolver.registerContentObserver(uri, false, observer);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mResolver.unregisterContentObserver(observer);
    }

    public void insert(View view) {
        ContentValues values = new ContentValues();
        values.put("name", name.getText().toString());
        values.put("phone", phone.getText().toString());
        Uri result = mResolver.insert(USER_URI, values);
        if (result != null) {
            show("Insert success!");
        }
    }

    public void delete(View view) {
        String whereClause = "name";
        String[] whereArgs = {name.getText().toString()};
        int result = mResolver.delete(USER_URI, whereClause, whereArgs);
        if (result > 0) {
            show("Delete success!");
        }
    }

    public void update(View view) {
        ContentValues values = new ContentValues();
        values.put("name", name.getText().toString());
        values.put("phone", phone.getText().toString());
        String whereClause = "name";
        String[] whereArgs = {name.getText().toString()};
        int result = mResolver.update(USER_URI, values, whereClause, whereArgs);
        if (result > 0) {
            show("Update success!");
        }
    }

    public void query(View view) {
        String[] columns = {"_id", "name", "phone"};
        String whereClause = "name";
        String[] whereArgs = {name.getText().toString()};
        Cursor result = mResolver.query(USER_URI, columns, whereClause, whereArgs, null);
        if (result.moveToFirst()) {
            String mPhone = result.getString(result.getColumnIndex("phone"));
            show("query success. Phone:" + mPhone);
        }
    }

    private void show(String str) {
        Toast.makeText(mContext, str, Toast.LENGTH_SHORT).show();
    }
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <EditText
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <EditText
        android:id="@+id/phone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="insert"
        android:text="Insert" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="delete"
        android:text="Delete" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="update"
        android:text="Update" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="query"
        android:text="Query" />

</LinearLayout>

Content Provider实现原理

  • Content Provider启动流程
第一次访问Content Provider时启动
Andriod Application Study Note Url 9 SequenceDiagram1.jpg
Andriod Application Study Note Url 9 SequenceDiagram2.jpg
Andriod Application Study Note Url 9 SequenceDiagram3.jpg
  • Content Provider数据共享原理
原理概要
1.Content Provider组件将要传输的共享数据抽象为一个游标
2.Content Provider组件通过Binder进程间通信机制来突破应用程序为边界的权限控制
3.以匿名共享内存作为数据传输媒介,从而提供了一种高效的数据共享方式
概要流程
xxActivity组件在请求xxProvider组件返回信息之前,首先在当前应用程序创建一个CursorWindow对象,CursorWindow内部包含了一块匿名共享内存。

通过Binder进程间通信机制将所创建的CursorWindow对象(连同它内部的匿名共享内存)传递给xxProvider组件。

xxProvider组件获得了xxActivity组件发送过来的CursorWindow对象之后,就会创建一个SQLiteCursor对象
通过调用setWindow将CursorWindow对象保存在父类的成员变量mWindow中。

SQLiteCursor对象创建完成之后,xxProvider组件就会将xxActivity组件所请求的数据保存在这个SQLiteCursor对象中
实际上是保存在与它所关联的CursorWindow对象内部的一块匿名共享内存中
xxActivity可以访问这块匿名共享内存,因此可以通过这块内存来获取xxProvider组件返回给它的数据。

SQLiteCursor对象并不是一个Binder本地对象,xxProvider组件不能直接将它返回给xxActivity组件使用。

xxProvider组件首先会创建一个CursorToBulkCursorAdaptor对象,用来适配前面创建的SQLiteCursor对象,并将这个对象保存在mCursor中
然后将CursorToBulkCursorAdaptor对象返回给xxActivity组件。

xxActivity组件收到xxProvider组件返回数据之前,除了创建CursorWindow对象外,还会创建一个BulkCursorToCursorAdaptor对象
并将CursorWindow对象保存在它的父类AbstractWindowedCursor的成员变量mWindow中。

xxActivity组件收到xxProvider组件返回的CursorToBulkCursorAdaptor对象之后,实际上获得的是CursorToBulkCursorAdaptor的代理对象
并将它保存BulkCursorToCursorAdaptor对象的mBulkCursor中,这时候xxActivity组件就可以通过这个BulkCursorToCursorAdaptor对象来读取xxProvider组件返回的数据了。
SQLiteCursor类实现关系图

Andriod Application Study Note Url 9 ClassDiagram.jpg

注意:
CursorWindow包含了一块匿名共享内存
Content Provider 数据共享模型

Andriod Application Study Note Url 9 ContentProvider-asm.jpg

注意:
CursorWindow引用了同一块匿名共享内存
实现原理结构图

Andriod Application Study Note Url 9 ContentProvider-cp-yl.jpg
  • Content Provider数据监听原理
原理概要
1.ContentProvider组件的数据更新通知机制类似于Android系统的广播机制,都是一种消息发布和订阅的事件驱动模型。
2.内容观察者ContentObserver负责接收数据更新通知,ContentProvider组件负责发送数据更新通知。
3.内容观察者ContentObserver在接收到通知之前,必须要注册到ContentService中,通过URI来描述需要接收什么样的数据更新通知。
注册ContentObserver

Andriod Application Study Note Url 9 SequenceDiagram4.jpg

注意:
1.真正注册到ContentService中的并不是一个ContentObserver,而是与这个ContentObserver所关联的binder本地对象Transport(ContentObserver contentObserver)
2.mRootNode保存内容观察者
发送数据改变通知

Andriod Application Study Note Url 9 SequenceDiagram5.jpg
回调onChange方法

Andriod Application Study Note Url 9 ContentObserver.jpg
数据监听结构图
Andriod Application Study Note Url 9 ContentObserver2.jpg
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值