Android四大组件中ContentProvider组件相对Activity,BroadcastReceiver, Service而言比较独立,而且多数使用的时候都是在用Android系统提供的关于邮件,媒体,短信,联系人等ContentProvider。通常开发的应用较少提供ContentProvider组件,一般会在同家公司的产品或则内容适配方面会用到ContentProvider组件。

 

    本文主要讲述开发和使用ContentProvider组件的通用方式。其中包含:代码模板,权限设置,ContentResolver 等。


1 . 开发ContentProvider基本思路


   1.1.ContentProvider组件需要对外开放的内容

       授权字符串(Authoritis),内容类型,数据字段名称, 访问权限说明等


   1.2. 编写ContentProvider组件类继承ContentProvider类,实现其中的CRUD操作方法


   1.3. AndroidMainifest.xml中配置元素,指定控制属性,其中包含是否对外部应用可用,读写权限,Authorities字符串等


   1.4. 应用本身或者外部应用使用ContentProvider组件


2. 开发一个基于SQLite数据库提供Note表信息的ContentProvider


  2.1  开发一个独立的ContentProvider组件代码框架


     

package secondriver.xprovider;

import android.content.*;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Author : secondriver
 * Date :  2015/11/4
 */
public class XProvider extends ContentProvider {

    private static final String TAG = "XProvider";

    //授权字符串
    
    //授权Uri
    

    //Note公开信息
    public static final class Note implements BaseColumns {

        //Note表名
        
        //Note表的内容Uri
      
	//内容类型
 
        //Note表字段
      
        //Uri匹配码  

    }

    //Uri匹配器
    public static UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        //添加Uri匹配内容
    }

    private XSQLiteHelper helper;

    @Override
    public boolean onCreate() {
        //ContentProvider组件创建时做的工作
		return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return cursor;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
		return null;
    }

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

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return effectRows;
    }

    public static class XSQLiteHelper extends SQLiteOpenHelper {

        public static final String DATA_BASE_NAME = "xprovider.sqlite";
        public static final int DATA_BASE_VERSION = 1;

        public static volatile XSQLiteHelper xsqLiteHelper;

        public static XSQLiteHelper getXsqLiteHelper(Context context) {
            //实例化XSQLiteHelp对象
            return xsqLiteHelper;
        }

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

        @Override
        public void onCreate(SQLiteDatabase db) {
            //数据库初始化
        }

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

        }
    }
}


      上面代码提供了一个SQLiteOpenHelper的实现,实际开发中可个会更具具体应用来提供给ContentProvider组件。

     

      中文注释部分可能需要填充具体代码,比如权限可以更具实际情况来定义,表字段根据要提供的内容信息来公开字段名并且需要在文档中说明。


   2.1 准备Note表信息

     

//Note公开信息
    public static final class Note implements BaseColumns {

        //Note表名
        public static final String TABLE_NAME = "note";

        //Note表的内容Uri
        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "note");

        //建议使用格式如: type/subtype => vnd.android.cursor.dir/vnd..  name=package name  type=table name
        public static final String CONTENT_DIR_TYPE = "vnd.android.cursor.dir/vnd.secondriver.xprovider.note";
        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.secondriver.xprovider.note";

        //ID主键
        public static final String ID_COLUMN = _ID;
        //内容列
        public static final String CONTENT_COLUMN = "CONTENT";
        //创建时间列表
        public static final String CREATED_COLUMN = "CREATED";
        //标识列
        public static final String FLAG_COLUMN = "FLAG";
        //状态列
        public static final String STATUS_COLUMN = "STATUS";

        //Uri匹配码
        private static final int NOTE_ITEM = 0x21;
        private static final int NOTE_DIR = 0x22;

    }


    说明:Uri匹配码声明为Note类的常量这样可以方便对照,一个ContentProvider组件中声明多个表公开类,比如User类,这样就比较容易区分操作的是那个表的内容。


 2.2  Note类外其它的具体代码


 

package secondriver.xprovider;

import android.content.*;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Author : secondriver
 * Date :  2015/11/4
 */
public class XProvider extends ContentProvider {

    private static final String TAG = "XProvider";

    //授权字符串
    public static final String AUTHORITY = "secondriver.xprovider.X_PROVIDER";
    //授权Uri
    public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);


    //Uri匹配器
    public static UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, "note/#", Note.NOTE_ITEM);
        uriMatcher.addURI(AUTHORITY, "note", Note.NOTE_DIR);
    }

    private XSQLiteHelper helper;

    @Override
    public boolean onCreate() {
        helper = XSQLiteHelper.getXsqLiteHelper(getContext());
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
        switch (uriMatcher.match(uri)) {
            case Note.NOTE_DIR:
                break;
            case Note.NOTE_ITEM:
                builder.appendWhere(Note.ID_COLUMN + "=" + uri.getPathSegments().get(1));
                break;
            default:
                throw new IllegalArgumentException("Unsupported URI :" + uri);
        }
        builder.setTables(Note.TABLE_NAME);
        Cursor cursor = builder.query(helper.getReadableDatabase(), projection, selection, selectionArgs, null,
                null, sortOrder);
        return cursor;
    }

    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case Note.NOTE_ITEM:
                return Note.CONTENT_ITEM_TYPE;
            case Note.NOTE_DIR:
                return Note.CONTENT_DIR_TYPE;
            default:
                throw new IllegalArgumentException("Unsupported URI :" + uri);
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = helper.getWritableDatabase();
        switch (uriMatcher.match(uri)) {
            case Note.NOTE_DIR:
                long noteId = db.insert(Note.TABLE_NAME, null, values);
                if (noteId > 0) {
                    //插入成功
                    Uri newUri = ContentUris.withAppendedId(uri, noteId);
                    getContext().getContentResolver().notifyChange(newUri, null);
                    return newUri;
                } else {
                    //插入失败
                    return null;
                }
            default:
                throw new IllegalArgumentException("Unsupported URI :" + uri);
        }
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = helper.getWritableDatabase();
        int effectRows = 0;
        switch (uriMatcher.match(uri)) {
            case Note.NOTE_DIR:
                effectRows = db.delete(Note.TABLE_NAME, selection, selectionArgs);
                break;
            case Note.NOTE_ITEM:
                long id = ContentUris.parseId(uri);
                String whereClause = Note.ID_COLUMN + "=" + String.valueOf(id);
                if (!TextUtils.isEmpty(selection)) {
                    whereClause = whereClause + " AND " + selection;
                }
                effectRows = db.delete(Note.TABLE_NAME, whereClause, selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unsupported URI :" + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return effectRows;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        SQLiteDatabase db = helper.getWritableDatabase();
        int effectRows = 0;
        switch (uriMatcher.match(uri)) {
            case Note.NOTE_DIR:
                effectRows = db.update(Note.TABLE_NAME, values, selection, selectionArgs);
                break;
            case Note.NOTE_ITEM:
                long nodeId = ContentUris.parseId(uri);
                String whereClause = Note.ID_COLUMN + "=" + String.valueOf(nodeId);
                if (!TextUtils.isEmpty(selection)) {
                    whereClause = whereClause + " AND " + selection;
                }
                effectRows = db.update(Note.TABLE_NAME, values, whereClause, selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unsupported URI :" + uri);
        }
        return effectRows;
    }

    public static class XSQLiteHelper extends SQLiteOpenHelper {

        public static final String DATA_BASE_NAME = "xprovider.sqlite";
        public static final int DATA_BASE_VERSION = 1;

        public static volatile XSQLiteHelper xsqLiteHelper;

        public static XSQLiteHelper getXsqLiteHelper(Context context) {
            if (null == xsqLiteHelper) {
                synchronized (XSQLiteHelper.class) {
                    if (null == xsqLiteHelper) {
                        xsqLiteHelper = new XSQLiteHelper(context, DATA_BASE_NAME, null, DATA_BASE_VERSION);
                    }
                }
            }
            return xsqLiteHelper;
        }

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

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.beginTransaction();
            try {
                db.execSQL(
                        new StringBuilder("create table if not exists ")
                                .append(Note.TABLE_NAME)
                                .append("(")
                                .append(Note.ID_COLUMN)
                                .append(" integer primary key autoincrement, ")
                                .append(Note.CONTENT_COLUMN)
                                .append(" varchar, ")
                                .append(Note.CREATED_COLUMN)
                                .append(" varchar,")
                                .append(Note.FLAG_COLUMN)
                                .append(" varchar,")
                                .append(Note.STATUS_COLUMN)
                                .append(" varchar")
                                .append(")")
                                .toString()
                );
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                for (int i = 0; i < 50; i++) {
                    ContentValues cv = new ContentValues();
                    cv.put(Note.CONTENT_COLUMN, "Note 内容 " + i);
                    cv.put(Note.CREATED_COLUMN, simpleDateFormat.format(new Date()));
                    cv.put(Note.FLAG_COLUMN, i);
                    cv.put(Note.STATUS_COLUMN, i);
                    db.insert(Note.TABLE_NAME, null, cv);
                }
                db.setTransactionSuccessful();
            } catch (SQLiteException e) {
                Log.e(TAG, e.getMessage());
            } finally {
                db.endTransaction();
            }
        }

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

        }
    }
}


    

    3. 在AndroidMainfest.xml清单文件中声明ContentProvider组件


     3.1  声明XProvider


        
            
        


    3.2 定义权限

         清单文件中声明XProvider的时候使用到了内容读写权限

         “secondriver.xprovider.permission.X_PROVIDER” 因此该权限需要清单文件中定义。

        


   


     权限定义时label属性的值通常为”XXX的权限“,description属性的值通常是”允许该应用干什么,授权有XXX的危害“。比如:label =发送持久广播权限 description=允许该应用发送持久广播,此类消息在广播结束后仍会保留。过多使用会占用手机过多内容,从而降低其速度或稳定性。 


        3.3 权限说明

        默认情况下Provider是没有权限控制的,因此一旦exported=true,那么外部其它应用都可以访问到Provider提供的内容,为了更加安全,有效,范围合适的控制需要添加权限控制。

        Provider权限分为: 

  •  读写的Provider层权限

  •  读写分开的Provider层权限

  •  Path层权限

  •  临时授权

       四种权限从上往下优先级越高。


      Path层权限:是对于Uri的更具细粒度的权限控制,provider元素的子元素中可以配置grant-ui-permission和path-permission 。

      

      临时授权:provider元素属性grantUriPermissions=true时系统将授予应用临时权限访问完整的Provider,覆盖掉Provider和Path层的权限;grantUriPermissions=false时需要在provider元素的子元素中配置一个或者多个grant-uri-permission元素来为指定的Uri的临时访问授权。

      另外应用在使用临时授权访问Provider时Provider应用会在返回的Intent中通过的setFlags方法指定FLAG_GRANT_READ_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSION标识,携带一个具有临时授权的Uri供外部应用完成本次内容访问操作。


  4. 在本应用和其它应用中使用ContentProvider提供的内容


      这里提供在其它应用中使用ContentProvider。在使用外部提供的ContentProvider通常需要了解的内容便是文字1.1部分提到。


    4.1 下面是通过一个ListView来展示Note中的”CONTENT“和”CREATED“字段信息


package secondriver.oapp;

import android.app.Activity;
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Author : secondriver
 * Date :  2015/11/5
 */
public class XResolverActivity extends Activity {

    private SimpleAdapter mAdapter;
    private ListView mListView;
    private List> mData;

    private final int xproviderLoad = 0x01;
    private LoaderManager loaderManager;
    private LoaderManager.LoaderCallbacks callbacks = new LoaderManager.LoaderCallbacks() {
        @Override
        public Loader onCreateLoader(int id, Bundle args) {
            CursorLoader cursorLoader = new CursorLoader(getApplicationContext(),
                    Uri.parse("content://secondriver.xprovider.X_PROVIDER/note"),
                    new String[]{
                            "_id",
                            "CONTENT",
                            "CREATED"
                    }, null, null, null);
            return cursorLoader;
        }

        @Override
        public void onLoadFinished(Loader loader, Cursor data) {
            int idIndex = data.getColumnIndexOrThrow("_id");
            int contentIndex = data.getColumnIndexOrThrow("CONTENT");
            int createdIndex = data.getColumnIndexOrThrow("CREATED");
            mData.clear();
            while (data.moveToNext()) {
                HashMap m = new HashMap<>();
                m.put("CONTENT", data.getString(contentIndex));
                m.put("CREATED", data.getString(createdIndex));
                m.put("_id", data.getString(idIndex));
                mData.add(m);
            }
            mAdapter.notifyDataSetChanged();
        }

        @Override
        public void onLoaderReset(Loader loader) {

        }
    };


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

        mListView = (ListView) findViewById(android.R.id.list);
        mData = new ArrayList<>();
        mAdapter = new SimpleAdapter(this, mData, android.R.layout.simple_list_item_2,
                new String[]{
                        "CONTENT",
                        "CREATED"
                },
                new int[]{
                        android.R.id.text1,
                        android.R.id.text2
                });
        mListView.setAdapter(mAdapter);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView parent, View view, int position, long id) {
                HashMap m = (HashMap) mData.get(position);
                String noteId = (String) m.get("_id");
                String noteContent = (String) m.get("CONTENT");
                getContentResolver().delete(Uri.withAppendedPath(Uri.parse("content://secondriver.xprovider.X_PROVIDER/note"), noteId), null, null);
                mData.remove(position);
                mAdapter.notifyDataSetChanged();
                Toast.makeText(XResolverActivity.this, "Delete :" + noteContent, Toast.LENGTH_LONG).show();
            }
        });

        loaderManager = getLoaderManager();
        loaderManager.initLoader(xproviderLoad, new Bundle(), callbacks);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (null != loaderManager) {
            loaderManager.restartLoader(xproviderLoad, new Bundle(), callbacks);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (null != loaderManager) {
            loaderManager.destroyLoader(xproviderLoad);
        }
    }
}


     说明:需要额外注意的是代码中的硬编码字符串内容,这些内容正是XProvider类和Note类公开的信息,这些内容通常在ContentProvider组件的使用文档中公开说明的。如果是在应用内部使用XProvider的话,那么就可以直接使用变量名而避免硬编码。如下代码片段所示:

 

 mAdapter = new SimpleAdapter(this, mData, android.R.layout.simple_list_item_2,
                new String[]{
                        XProvider.Note.CONTENT_COLUMN,
                        XProvider.Note.CREATED_COLUMN
                },
                new int[]{
                        android.R.id.text1,
                        android.R.id.text2
                });


   由于XProvider的访问需要读写权限,因此需要在清单文件中声明使用的权限。

   


  5. ContentProvider组件小结

     在开发ContentProvider时尽可能使其具备以下特点:

     提供恰当的内容访问范围;ContentProvider组件独立封装;详细的权限,Uri,提供内容的使用说明。