android四大组件之ContentProvider

1、定义
ContentProvider是内容提供者,是Android四大组件之一。在Android中,每一个ContentProvider都会用类似于域名的字符串来注册自己,我们称为授权(authority)。这个唯一标识的字符串是此ContentProvider可提供的一组URI的基础,有了这个基础,才能够向外界提供信息的共享服务。

授权是在AndroidManifest.xml中完成的,每一个ContentProvider必须在此声明并授权,方式如下:

1

2

<provider android:name=".SomeProvider"

  android:authorities="com.your-company.SomeProvider"/>

2、作用
用于进程间数据的交互共享,即跨进程通信。其作为数据源的搬运工的角色。数据源可以是数据库数据如SQLite,文件,XML,网络等等。当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferencesAPI读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。 

3、原理
底层采用Android中的Binder机制。 

4、具体使用
4.1 统一资源标识符(URI)
外界进程通过URI找到对应的ContentProvider和其中的数据,在进行数据操作。
URI分为系统预置和自定义两部分。分别对应系统内置的数据如通讯录日程表等,还有自己定义的数据库。
自定义URI = 主题名 + 授权信息 + 表名 + 记录
通过上述组合就可以定位到具体到数据库中某一表中某一个数据。

Uri代表了要操作的数据,Uri主要包含了两部分信息:1.需要操作的ContentProvider ,2.对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成:
       1.scheme:ContentProvider(内容提供者)的scheme已经由Android所规定为:content://。

       2.主机名(或Authority):用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。

       3.路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:

•         要操作contact表中id为10的记录,可以构建这样的路径:/teacher/10

•         要操作contact表中id为10的记录的name字段, teacher/10/name

•         要操作contact表中的所有记录,可以构建这样的路径:/teacher
值得注意的是,URI模式支持通配符匹配,可以使用通配符匹配任意长度的数据
4.2 MIME数据类型
作用:指定某个扩展名的文件用某种应用程序打开。
MIME类型由类型+子类型组成
例如text/css text/xml application/pdf
4.3 ContentProvider类
此类组织数据的方式主要是表格形式,同时也支持文件数据
主要方法如下
insert,delete,update,query
值得注意的是,如果是在多线程并发访问的时候使用,需要实现同步。
4.4 ContentResolver类
此类用于管理不同的ContentProvider间的操作。通过URI可以操作不同ContentProvider种的数据,外部进程通过ContentResolver类从而与ContentProvider类进行交互,交互方法主要有数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver使用insert、delete、update、query方法,来操作数据。
4.5 ContentUris类
此类用于辅助ContentProvider,用于操作URI。可以通过withAppendedId方法向URI追加一个id。
可以通过parse方法从URL中获取ID。

它有两个比较实用的方法:
•         withAppendedId(uri, id)用于为路径加上ID部分
•         parseId(uri)方法用于从路径中获取ID部分
4.6 UriMatcher类
此类用于辅助ContentProvider,用于在ContentProvider中注册URI。根据URI匹配ContentProvider中对应的数据表。
经过注册后, 就可以在使用uri时通过匹配不同的结果在switch语句里做不同的处理了。
4.7 ContentObserver类
此类用于辅助ContentProvider,用于内容观察者。观察Uri引起ContentProvider中数据变化并通知外界。使用时通过uri注册,当该uri的ContentProvider的数据发生变化时,通知外界。

5、权限设置

可以在代码中通过setReadPermission()和setWritePermission()两个方法来设置ContentProvider的操作权限,也可以在配置文件中通过android:readPermission和android:writePermission属性来控制。 android:protectionlevel="normal"

6,demo代码如下:

自定义MyContentProvider类 
public class MyContentProvider extends ContentProvider {

    private DBHelper dbOpenHelper = null;
    private static UriMatcher uriMatcher;

    static {
        // 常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        // 如果match()方法匹配content://com.example.lcq.myapp.contentProvider/teacher n路径,返回匹配码为TEACHERS
        uriMatcher.addURI(ContentData.AUTHORITY, "teacher", TEACHERS);

        // 如果match()方法匹配content://com.example.lcq.myapp.contentProvider/teacher/230,路径,返回匹配码为TEACHER
        uriMatcher.addURI(ContentData.AUTHORITY, "teacher/#", TEACHER);
    }

    @Override
    public boolean onCreate() {
        //这里会调用 DBOpenHelper的构造函数创建一个数据库;
        dbOpenHelper = new DBHelper(this.getContext(), ContentData.DATABASE_NAME, ContentData.DATABASE_VERSION);
        return true;
    }

    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case TEACHERS:
                return CONTENT_TYPE;
            case TEACHER:
                return CONTENT_TYPE_ITME;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
        switch (uriMatcher.match(uri)) {
            case TEACHERS:
                return db.query("teacher", projection, selection, selectionArgs, null, null, sortOrder);
            case TEACHER:
                long personid = ContentUris.parseId(uri);
                String where = "_ID=" + personid;// 获取指定id的记录
                where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : "";// 把其它条件附加上
                return db.query("teacher", projection, where, selectionArgs, null, null, sortOrder);
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
    }

    /**
     * 当执行这个方法的时候,如果没有数据库,他会创建,同时也会创建表,但是如果没有表,下面在执行insert的时候就会出错
     * 这里的插入数据也完全可以用sql语句书写,然后调用 db.execSQL(sql)执行。
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {

        //多线程加类锁
        synchronized (MyContentProvider.class) {
            //获得一个可写的数据库引用,如果数据库不存在,则根据onCreate的方法里创建;
            SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
            long id = 0;
            int uu = uriMatcher.match(uri);
            switch (uriMatcher.match(uri)) {
                case TEACHERS:
                    id = db.insert("teacher", null, values);    // 返回的是记录的行号,主键为int,实际上就是主键值
                    if(id > 0) {
                        notifyChanged();
                    }
                    return ContentUris.withAppendedId(uri, id);
                case TEACHER:
                    id = db.insert("teacher", null, values);
                    if(id > 0 ) {
                        notifyChanged();
                    }
                    String path = uri.toString();
                    return Uri.parse(path.substring(0, path.lastIndexOf("/")) + id); // 替换掉id
                default:
                    throw new IllegalArgumentException("Unknown URI " + uri);
            }
        }
    }

    //通知指定URI数据已改变
    private void notifyChanged() {
        getContext().getContentResolver().notifyChange(ContentData.UserTableData.CONTENT_URI,null);
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        //多线程加类锁
        synchronized (MyContentProvider.class) {
            SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
            int count = 0;
            switch (uriMatcher.match(uri)) {
                case TEACHERS:
                    count = db.delete("teacher", selection, selectionArgs);
                    if(count > 0 ) {
                        notifyChanged();
                    }
                    break;
                case TEACHER:
                    // 下面的方法用于从URI中解析出id,对这样的路径content://hb.android.teacherProvider/teacher/10
                    // 进行解析,返回值为10
                    long personid = ContentUris.parseId(uri);
                    String where = "_ID=" + personid;    // 删除指定id的记录
                    where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : "";    // 把其它条件附加上
                    count = db.delete("teacher", where, selectionArgs);
                    if(count > 0 ) {
                        notifyChanged();
                    }
                    break;
                default:
                    throw new IllegalArgumentException("Unknown URI " + uri);
            }
            db.close();
            return count;
        }
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        //多线程加类锁
        synchronized (MyContentProvider.class) {
            SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
            int count = 0;
            switch (uriMatcher.match(uri)) {
                case TEACHERS:
                    count = db.update("teacher", values, selection, selectionArgs);
                    if(count > 0 ) {
                        notifyChanged();
                    }
                    break;
                case TEACHER:
                    // 下面的方法用于从URI中解析出id,对这样的路径content://com.ljq.provider.personprovider/person/10
                    // 进行解析,返回值为10
                    long personid = ContentUris.parseId(uri);
                    String where = "_ID=" + personid;// 获取指定id的记录
                    where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : "";// 把其它条件附加上
                    count = db.update("teacher", values, where, selectionArgs);
                    if(count > 0 ) {
                        notifyChanged();
                    }
                    break;
                default:
                    throw new IllegalArgumentException("Unknown URI " + uri);
            }
            db.close();
            return count;
        }
    }
}

外部调用activity类

public class ThirdActivity extends BaseActivity implements View.OnClickListener {
    private static final String tag = "ThirdActivity";
    private int count = 0;
    private boolean sex = false;
    Button insert;
    Button query;
    Button update;
    Button delete;
    Button querys;
    Button deleteAll;
    Uri uri = Uri.parse("content://com.example.lcq.myapp.contentProvider/teacher");

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

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //update records.
            requery();
        };
    };

    private void initView() {
        insert = findViewById(R.id.insert);
        query = findViewById(R.id.query);
        update = findViewById(R.id.update);
        delete = findViewById(R.id.delete);
        deleteAll = findViewById(R.id.delete_all);
        querys = findViewById(R.id.querys);
        insert.setOnClickListener(this);
        query.setOnClickListener(this);
        querys.setOnClickListener(this);
        update.setOnClickListener(this);
        delete.setOnClickListener(this);
        deleteAll.setOnClickListener(this);
        //注册变化通知
        getContentResolver().registerContentObserver(ContentData.UserTableData.CONTENT_URI,true,new TeacherObserver(handler));
    }

    @Override
    public void onClick(View v) {
        ContentResolver cr = getContentResolver();
        Cursor c;
        ContentValues cv = new ContentValues();
        switch (v.getId()) {
            case R.id.insert:
                cv.put("title", "教书"+count);
                cv.put("name", "教师"+count);
                cv.put("sex", !sex);
                Uri uri2 = cr.insert(uri, cv);
                Log.e(tag, uri2.toString());
                Toast.makeText(this, "插入的内容是: "+uri2.toString(), Toast.LENGTH_SHORT).show();
                break;
            case R.id.query:
                // 查找id为1的数据
                c = cr.query(uri, null, "_ID=?", new String[]{"1"}, null);
                //这里必须要调用 c.moveToFirst将游标移动到第一条数据,不然会出现index -1 requested , with a size of 1错误;cr.query返回的是一个结果集。
                if (c.moveToFirst() == false) {
                    // 为空的Cursor
                    return;
                }
                int name = c.getColumnIndex("name");
                System.out.println(c.getString(name));
                Toast.makeText(this, c.getString(name), Toast.LENGTH_SHORT).show();
                c.close();
                break;
            case R.id.update:
                cv.put("name", "曾老师");
                cv.put("date_added", (new Date()).toString());
                int uri3 = cr.update(uri, cv, "_ID=?", new String[]{"3"});
                System.out.println("updated" + ":" + uri3);
                Toast.makeText(this, "updated" + ":" + uri3, Toast.LENGTH_SHORT).show();
                break;
            case R.id.delete:
                cr.delete(uri, "_ID=?", new String[]{"2"});
                break;
            case R.id.querys:
                // 查找id为1的数据
                c = cr.query(uri, null, null, null, null);
                Log.e(tag, String.valueOf(c.getCount()));
                Toast.makeText(this, String.valueOf(c.getCount()), Toast.LENGTH_SHORT).show();
                c.close();
                break;
            case R.id.delete_all:
                cr.delete(uri,null,null);
                break;
            default:
                break;
        }

    }

    /**
     * 重新查询
     */
    private void requery() {
        //实际操作中可以查询集合信息后Adapter.notifyDataSetChanged();
//        query(null);
    }

对外的各种常量类

public class ContentData {
    public static final String AUTHORITY = "com.example.lcq.myapp.contentProvider";
    public static final String DATABASE_NAME = "teacher.db";
    //创建 数据库的时候,都必须加上版本信息;并且必须大于4
    public static final int DATABASE_VERSION = 4;
    public static final String USERS_TABLE_NAME = "teacher";

    public static final class UserTableData implements BaseColumns {
        public static final String TABLE_NAME = "teacher";
        //Uri,外部程序需要访问就是通过这个Uri访问的,这个Uri必须的唯一的。
        public static final Uri CONTENT_URI = Uri.parse("content://"+ AUTHORITY + "/teacher");
        // 数据集的MIME类型字符串则应该以vnd.android.cursor.dir/开头
        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.scott.teachers";
        // 单一数据的MIME类型字符串应该以vnd.android.cursor.item/开头
        public static final String CONTENT_TYPE_ITME = "vnd.android.cursor.item/vnd.scott.teacher";
        /* 自定义匹配码 */
        public static final int TEACHERS = 1;
        /* 自定义匹配码 */
        public static final int TEACHER = 2;
        public static final String TITLE = "title";
        public static final String NAME = "name";
        public static final String DATE_ADDED = "date_added";
        public static final String SEX = "SEX";
        public static final String DEFAULT_SORT_ORDER = "_id desc";
    }
}

数据库类

public class DBHelper extends SQLiteOpenHelper {
    private static final String tag = "DBHelper";
    public DBHelper( Context context,  String name,  SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    public DBHelper( Context context,  String name, SQLiteDatabase.CursorFactory factory, int version,  DatabaseErrorHandler errorHandler) {
        super(context, name, factory, version, errorHandler);
    }

    public DBHelper(Context context, String databaseName, int databaseVersion) {
        this(context,databaseName,null,databaseVersion);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        Log.e(tag,"create table");
        db.execSQL("create table " + ContentData.UserTableData.TABLE_NAME
                + "(" + ContentData.UserTableData._ID
                + " INTEGER PRIMARY KEY autoincrement,"
                + ContentData.UserTableData.NAME + " varchar(20),"
                + ContentData.UserTableData.TITLE + " varchar(20),"
                + ContentData.UserTableData.DATE_ADDED + " long,"
                + ContentData.UserTableData.SEX + " boolean)" + ";");
    }

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

观察更改通知类

public class TeacherObserver extends ContentObserver {
    public static final String TAG = "TeacherObserver";
    private Handler handler;
    /**
     * Creates a content observer.
     *
     * @param handler The handler to run {@link #onChange} on, or null if none.
     */
    public TeacherObserver(Handler handler) {
        super(handler);
        this.handler = handler;
    }

    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
        Log.i(TAG, "data changed, try to requery.");
        //向handler发送消息,更新查询记录
        Message msg = new Message();
        handler.sendMessage(msg);
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值