Android ContentProvider封装数据库和文件读写总结

本文是我各处东拼西凑加上自己实现一个ContentProvider的使用总结,留做后用,主要介绍ContentProvider的集成方法。

一、综述
ContentProvider是Android四大组件之一,其核心功能是提供应用间的统一的数据访问方式,当然也可以用于应用内的数据封装。ContentProvider类似C/S结构,应用A实现ContentProvider向外提供应用内的数据访问,应用B使用Context.getContentResolver()来间接与应用A的Provider交互。

ContentProvider提供一套类似数据库的query/delete/insert/update的方法来操作数据,开发者可以以此来屏蔽管理数据的真正方式 (SharePreference/File/SQLite等)。另外,ContentProvider也提供了直接操作文件的方式(覆写openFile),第三方应用可以通过该方式读写当前应用的私有文件(data/data等)。

二、相关知识
2.1 UriMatcher和ContentUris的使用
UriMatcher主要用户判断Uri是否符合特定格式,用法如下:

private static final int TYPE_ALL = 1;
private static final int TYPE_SINGLE = 2;
private static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        mUriMatcher.addURI(AUTHORITY, "person", TYPE_ALL);
        mUriMatcher.addURI(AUTHORITY, "person/#", TYPE_SINGLE);//#号表示一个数字
    }

以上为其声明与配置,下面是使用方法:

switch (mUriMatcher.match(uri)) {
        case TYPE_ALL:
            //uri匹配到第一种类型
        case TYPE_SINGLE:
            //uri匹配到第二种类型
        default:
            //uri没有匹配到这两种类型
        }

这样我们就可以知道外部传入的Uri的类型了。

2.2 ContentUris用法
ContentUris用于处理Uri后面的id部分(纯数字),常用函数有:
parseId(uri): 解析Uri里的最后的id
withAppendedId(uri, id):在路径后面加上id部分

三、封装ContentProvider的步骤
提供Provider的工程简称应用A。下面是封装ContentProvider的步骤:
3.1 在Manifest中声明ContentProvider:

<provider android:name="org.test.auth.MyDataProvider"
            android:authorities="org.test.auth"
            android:exported="true"/>

声明后,应用安装到设备上第三方应用就能通过ContentResolver访问此Provider。

3.2 ContentProvider的代码

package org.test.auth;

import java.io.File;
import java.io.FileNotFoundException;

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;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;

public class MyDataProvider extends ContentProvider {

    private static final String AUTHORITY = "org.test.auth";
    private static final int TYPE_ALL = 1;
    private static final int TYPE_SINGLE = 2;
    private static final String TAG = "TEST111_DataProvider";

    private MyDBHelper mDbHelper;

    private SQLiteDatabase mDatabase;

    private static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        mUriMatcher.addURI(AUTHORITY, "person", TYPE_ALL);
        mUriMatcher.addURI(AUTHORITY, "person/#", TYPE_SINGLE);
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        switch (mUriMatcher.match(uri)) {
        case TYPE_ALL:
            return mDatabase.delete(MyDBHelper.TABLE_NAME, 
                    selection, 
                    selectionArgs);
        case TYPE_SINGLE:
            long id = ContentUris.parseId(uri);
            String where = MyDBHelper.COLUMN_ID + " = " + id;
            if(!TextUtils.isEmpty(selection)) {
                where = where + " and " + selection;
            }
            return mDatabase.delete(MyDBHelper.TABLE_NAME, 
                    where, 
                    selectionArgs);
        default:
            return -1;
        }
    }

    @Override
    public String getType(Uri uri) {
        switch (mUriMatcher.match(uri)) {
        case TYPE_ALL:
            return "vnd.android.cursor.dir/person";
        case TYPE_SINGLE:
            return "vnd.android.cursor.item/person";
        default:
            return null;
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        //由于此时的values中具体是否含有数据是不确定的,所以此时需要在第二个参数中添加person表中的非主键的一列  
        long id = mDatabase.insert(MyDBHelper.TABLE_NAME, MyDBHelper.COLUMN_NAME, values);
        return ContentUris.withAppendedId(uri, id);
    }

    @Override
    public boolean onCreate() {
        mDbHelper = new MyDBHelper(getContext());

        mDatabase = mDbHelper.getWritableDatabase();
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] columns, 
            String selection, String[] selectionArgs,
            String orderBy) {

        switch (mUriMatcher.match(uri)) {
        case TYPE_ALL:
            return mDatabase.query(MyDBHelper.TABLE_NAME, columns, selection, selectionArgs, null, null, orderBy);
        case TYPE_SINGLE:
            long id = ContentUris.parseId(uri);
            String where = MyDBHelper.COLUMN_ID + " = " + id;
            if(!TextUtils.isEmpty(selection)) {
                where = where + " and " + selection;
            }
            return mDatabase.query(MyDBHelper.TABLE_NAME, columns, where, selectionArgs, null, null, orderBy);
        default:
            return null;
        }
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        switch (mUriMatcher.match(uri)) {
        case TYPE_ALL:
            return mDatabase.update(MyDBHelper.TABLE_NAME, values, 
                    selection, selectionArgs);
        case TYPE_SINGLE:
            long id = ContentUris.parseId(uri);
            String where = MyDBHelper.COLUMN_ID + " = " + id;
            if(!TextUtils.isEmpty(selection)) {
                where = where + " and " + selection;
            }
            return mDatabase.update(MyDBHelper.TABLE_NAME, values, 
                    where, selectionArgs);
        default:
            return -1;
        }
    }

    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
        File root = new File(getContext().getFilesDir() + "/" + uri.getPath());
        root.getParentFile().mkdirs();

        int imode = 0;
        if (mode.contains("w")) {
                imode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
                if (!root.exists()) {
                    try {
                        root.createNewFile();
                    } catch (Exception ex) {
                        Log.d(TAG, "error happened", ex);
                    }
                }
        }
        if (mode.contains("r")) imode |= ParcelFileDescriptor.MODE_READ_ONLY;
        if (mode.contains("+")) imode |= ParcelFileDescriptor.MODE_APPEND;        

        return ParcelFileDescriptor.open(root, imode);
    }
}

此示例中使用的数据操作方式是数据库,onCreate执行ContentProvider启动时的初始化操作,query/insert/delete/update操作数据,getType返回指定Uri对应的数据类型。“vnd.android.cursor.dir”表示cursor集合,“vnd.android.cursor.item”表示cursor条目。
覆写openFile方法提供私有文件的对外访问,在该方法内可以控制是否提供文件读写,本例代码来自网络,支持读写操作。

附上数据库帮助类代码:

package org.test.auth;

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

public class MyDBHelper extends SQLiteOpenHelper {

    public static final String DB_NAME = "test.db";
    public static final String TABLE_NAME = "person";
    public static final String COLUMN_ID = "_id";
    public static final String COLUMN_AGE = "age";
    public static final String COLUMN_NAME = "name";
    public static final int DB_VERSION = 1;

    public MyDBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase database) {
        String sql = "create table if not exists " + TABLE_NAME + "( " +
                COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                COLUMN_NAME +" varchar(100), " +
                COLUMN_AGE + " integer" + 
                " )" ;
        database.execSQL(sql);
    }

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

}

四、测试代码
测试代码在另一个工程中,简称应用B。

package com.example.testcontentprovider;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.util.Log;

public class MainActivity extends Activity {

    private static final String URI = "content://org.test.auth/person";
    private static final String TAG = "TEST111_";

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

        doTest();
    }

    private void doTest() {
        ContentResolver cr = getContentResolver();

        String typeAll = cr.getType(Uri.parse(URI));
        Log.d(TAG, "get all type= " + typeAll);

        String typeOne = cr.getType(Uri.parse(URI + "/1"));
        Log.d(TAG, "get one type= " + typeOne);

        insertValue(cr, "张三", 12);
        insertValue(cr, "李四", 23);
        //查询所有数据
        query(cr, -1);

        //查询单条数据
        query(cr, 1);

        //更改id=1的数据
        updateValue(cr, 1, "王五", 20, null, null);
        //查询所有数据
        query(cr, -1);

        //更改name=李四的数据
        updateValue(cr, 2, "哈哈", 34, "name=?", new String[] {"李四"});
        //查询所有数据
        query(cr, -1);

        //更改所有数据
        updateValue(cr, -1, "小明", 15, null, null);
        //查询所有数据
        query(cr, -1);

        //删除id=1数据
        int affectedColumnCountOne = cr.delete(Uri.parse(URI + "/1"), null, null);
        Log.d(TAG, "delete one affectedColumnCount= " + affectedColumnCountOne);
        //查询所有数据
        query(cr, -1);

        //删除所有数据
        int affectedColumnCountAll = cr.delete(Uri.parse(URI), null, null);
        Log.d(TAG, "delete all affectedColumnCount= " + affectedColumnCountAll);
        //查询所有数据
        query(cr, -1);

        //测试文件读写
        try {
            OutputStream os = cr.openOutputStream(Uri.parse(URI+"/provider_file.txt"));
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
            bw.append("think who am I?!");
            bw.flush();
            bw.close();

            InputStream is = cr.openInputStream(Uri.parse(URI+"/provider_file.txt"));
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String result = br.readLine();
            br.close();
            Log.d(TAG, "read file result : " + result);
        } catch (Exception e) {
            Log.d(TAG, "file op failed", e);
        }
    }

    private void query(ContentResolver cr, int id) {
        String idCause = "";
        if(id >= 0) {
            idCause = "/" + id;
        }
        Cursor cursorResultAll = cr.query(Uri.parse(URI + idCause), null, null, null, null);
        if(null != cursorResultAll) {
            while(cursorResultAll.moveToNext()) {
                int result = cursorResultAll.getInt(0);
                String name = cursorResultAll.getString(1);
                int age = cursorResultAll.getInt(2);
                Log.d(TAG, "query()| originid= " + id +"; id = " + result + " name= " + name + " age= " + age);
            }
        }
    }

    private void insertValue(ContentResolver cr, String name, int age) {
        ContentValues value = new ContentValues();
        value.put("name", name);
        value.put("age", age);
        Uri insertColumn = cr.insert(Uri.parse(URI), value);
        Log.d(TAG, "insertValue() insert at column: " + insertColumn);
    }

    private void updateValue(ContentResolver cr, int id, String name, int age, 
            String selection, String[] selectionArgs) {
        if (id >= 0) {
            ContentValues value = new ContentValues();
            value.put("name", name);
            value.put("age", age);
            int affectedColumnCount = cr.update(Uri.parse(URI + "/" + id), value, selection, selectionArgs);
            Log.d(TAG, "updateValue() affectedColumnCount: " + affectedColumnCount);
        } else {
            ContentValues value = new ContentValues();
            value.put("name", name);
            value.put("age", age);
            int affectedColumnCount = cr.update(Uri.parse(URI), value, 
                    selection, selectionArgs);
            Log.d(TAG, "updateValue() affectedColumnCount: " + affectedColumnCount);
        }
    }
}

以上,就是实现一个ContentProvider的方法,后续需要补充ContentProvider的原理部分,据说进程间通信是基于共享内存的,以后有时间需要分析一下。

你可能感兴趣的:(Android技术)