contentProvider作为android四大组件其地位也是不可忽视的。contentProvider实现是在Sqlite数据库基础上实现的。其数据存储使用Sqlite实现,上层使用URI进行数据操作,是数据存储变得更简单些。在简化数据存储的同时,contentProvider也提供数据共享功能,数据可以为其它程序提供读写功能。下面看一下具体使用。
创建数据源DBContent(通常是数据库进行存储)
import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; public class DBContent extends SQLiteOpenHelper { private final static String DB_NAME = "emcElevator"; public final static String TABLE_SHUACARD = "shuaCard"; public final static int DB_VERSION = 1; public final static String SID = "_id"; public final static String CARD_IDNUM="card_id_number"; public final static String CARD_PICTRUE="card_picture_path"; public DBContent(Context context){ super(context, DB_NAME, null, DB_VERSION); } public DBContent(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } @Override public void onCreate(SQLiteDatabase db) { final String sql="CREATE TABLE " + TABLE_SHUACARD + "(" + SID + " INTEGER PRIMARY KEY AUTOINCREMENT," + CARD_IDNUM+" TEXT,"+CARD_PICTRUE+" TEXT"+");"; db.execSQL(sql); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + TABLE_SHUACARD); } }
contentProvider实现类:
import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.text.TextUtils; public class CardContentProvider extends ContentProvider { public static final String AUTHORITY = "com.ctsing.provider.shuacarddata"; public static final String ReadContentPermission="com.permission.allow.readcontent"; public static final int ITEM = 1; public static final int ITEM_ID = 2; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/" + DBContent.TABLE_SHUACARD; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/" + DBContent.TABLE_SHUACARD; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/item"); //匹配器 private static final UriMatcher mMatcher; static{ mMatcher = new UriMatcher(UriMatcher.NO_MATCH); mMatcher.addURI(AUTHORITY, "item", ITEM); mMatcher.addURI(AUTHORITY, "item/#", ITEM_ID); } private DBContent dbHelper=null; @Override public boolean onCreate() { dbHelper=new DBContent(getContext()); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteDatabase sdb = dbHelper.getReadableDatabase(); Cursor cur; switch (mMatcher.match(uri)) { case ITEM: cur = sdb.query(DBContent.TABLE_SHUACARD, projection, selection, selectionArgs, null, null, sortOrder); break; case ITEM_ID: long id = ContentUris.parseId(uri); cur = sdb.query(DBContent.TABLE_SHUACARD, projection, DBContent.SID + "=" + id + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ")" : ""), selectionArgs, null, null, sortOrder); break; default: // Log.i("sMatcher.match(uri)", String.valueOf(mMatcher.match(uri))); throw new IllegalArgumentException("Query with unknown URI: " + uri); } /* sdb.close(); sdb=null; */ cur.setNotificationUri(getContext().getContentResolver(), uri); return cur; } @Override public String getType(Uri uri) { String matchType=null; switch(mMatcher.match(uri)){ case ITEM: matchType=CONTENT_TYPE; break; case ITEM_ID: matchType=CONTENT_ITEM_TYPE; break; default: throw new IllegalArgumentException("Unknown URI " + uri); } return matchType; } @Override public Uri insert(Uri uri, ContentValues values) { SQLiteDatabase db = dbHelper.getWritableDatabase(); if (mMatcher.match(uri) != ITEM) { db.close(); db=null; throw new IllegalArgumentException("Unknow URI " + uri); } long rowId = db.insert(DBContent.TABLE_SHUACARD, DBContent.SID, values); if (rowId > 0) { Uri noteUri = ContentUris.withAppendedId(CONTENT_URI, rowId); this.getContext().getContentResolver().notifyChange(noteUri, null); return noteUri; } db.close(); db=null; throw new SQLException("Failed to insert row into " + uri); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { SQLiteDatabase sdb = dbHelper.getWritableDatabase(); int count; switch (mMatcher.match(uri)) { case ITEM: count = sdb.delete(DBContent.TABLE_SHUACARD, selection, selectionArgs); break; case ITEM_ID: //String id = uri.getPathSegments().get(1); long id = ContentUris.parseId(uri); count = sdb.delete(DBContent.TABLE_SHUACARD, DBContent.SID + "=" + id + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ")" : ""), selectionArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } sdb.close(); sdb=null; this.getContext().getContentResolver().notifyChange(uri, null); return count; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { SQLiteDatabase db = dbHelper.getWritableDatabase(); int count; switch (mMatcher.match(uri)) { case ITEM: count = db.update(DBContent.TABLE_SHUACARD, values, selection, selectionArgs); break; case ITEM_ID: String id = uri.getPathSegments().get(1); count = db.update(DBContent.TABLE_SHUACARD, values, DBContent.SID + "=" + id + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ")" : ""), selectionArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } db.close(); db=null; this.getContext().getContentResolver().notifyChange(uri, null); return count; } }
配置ContentProvider
<provider android:name="com.ctsing.content.CardContentProvider" android:authorities="com.ctsing.provider.shuacarddata" />
单元测试进行功能测试:
import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.test.AndroidTestCase; import android.util.Log; public class TestUse extends AndroidTestCase { public void testInsert()throws Exception{ //插入数据 Uri uri = Uri.parse("content://" + CardContentProvider.AUTHORITY + "/item"); ContentResolver resolver = this.getContext().getContentResolver(); ContentValues values1 = new ContentValues(); values1.put(DBContent.CARD_IDNUM, "1001"); values1.put(DBContent.CARD_PICTRUE, "pro/elevater/11.jpeg"); resolver.insert(uri, values1); ContentValues values2 = new ContentValues(); values2.put(DBContent.CARD_IDNUM, "1002"); values2.put(DBContent.CARD_PICTRUE, "pro/elevater/12.jpeg"); resolver.insert(uri, values2); ContentValues values3 = new ContentValues(); values3.put(DBContent.CARD_IDNUM, "1003"); values3.put(DBContent.CARD_PICTRUE, "pro/elevater/13.jpeg"); resolver.insert(uri, values3); } public void testUpdate()throws Exception{ //更新id=2的记录 Uri uri = Uri.parse("content://" + CardContentProvider.AUTHORITY + "/item/2"); ContentResolver resolver = this.getContext().getContentResolver(); ContentValues values = new ContentValues(); values.put(DBContent.CARD_IDNUM, "1012"); values.put(DBContent.CARD_PICTRUE, "pro/elevater/102.jpeg"); resolver.update(uri, values, null, null); } public void testDelete()throws Exception{ //删除id=1的记录 Uri uri = Uri.parse("content://" + CardContentProvider.AUTHORITY + "/item/1"); //删除id=1的记录 ContentResolver resolver = this.getContext().getContentResolver(); resolver.delete(uri, null, null); } public void testQuery()throws Exception{ //查询数据并显示 Uri uri = Uri.parse("content://" + CardContentProvider.AUTHORITY + "/item"); //查询所有记录 ContentResolver resolver = this.getContext().getContentResolver(); Cursor cursor = resolver.query(uri, null, null, null, null); while(cursor.moveToNext()){ // StudentData person = new StudentData(cursor.getInt(cursor.getColumnIndex("id")),cursor.getString(cursor.getColumnIndex("name")),cursor.getInt(cursor.getColumnIndex("age"))); Log.v("ContentProvider", " NO.= "+cursor.getInt((cursor.getColumnIndex("_id")))+ " card_id= "+cursor.getString(cursor.getColumnIndex(DBContent.CARD_IDNUM))+ " pic_path= "+cursor.getString(cursor.getColumnIndex(DBContent.CARD_PICTRUE))); } } }
这里简单说一下单元测试实现:
1,在配置文件内声明权限,测试目标工具(在application外部声明):
<uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.ctsing.emca" >
</instrumentation>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
注意!在android:targetPackage所指的的包是应用程序包名,而不是测试类所在的包名称。
在application内部添加使用测试工具:
<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <uses-library android:name="android.test.runner" />
实现继承AndroidTestCase类,再要测试的方法名称前加test,例如测试Insert方法,则编写测试方法testInsert(),在右侧outline窗口选择要测试功能方法,
想要测试哪个方法,则在哪个测试方法上右键鼠标,选择Run As,然后再选择Android JUnit Test即可,如果有异常或者错误,左边测试台会显示红色,下边是错误提示。测试通过,测试条为绿色。
下面在介绍一下Content权限问题,为了数据安全性我们需要添加适当的读写权限
如果contentProvider要被第三方使用读写则需要添加android:exported="true"属性,设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。默认为false。当被第三方访问时我们可以添加权限来控制局部或者全部数据使用。
例如设置读取数据权限
声明权限:
<permission android:name="com.permission.allow.readcontent"
android:label="Allow read contentProvider"
android:protectionLevel="normal" />
添加权限到contentProvider:
<provider android:name="com.ctsing.content.CardContentProvider" android:authorities="com.ctsing.provider.shuacarddata" android:exported="true" android:readPermission="com.permission.allow.readcontent" />
t同样我们还可以设置写入权限,write android:witePermission权限,或者两者兼有permission。数据访问者需要添加权限声明
<uses-permission android:name="com.permission.allow.readcontent" />,才能正常访问数据内容,这在一定程度上起到保护数据功能。
部分权限功能:
实际使用中,provider可以只开放部分URI的权限,例如,我们可以只开放content://com.ctsing.provider.shuacarddata/item路径下权限,不允许访问其他路径,如下声明
<provider android:name="com.ctsing.content.CardContentProvider" android:name="com.ctsing.content.CardContentProvider" android:authorities="com.ctsing.provider.shuacarddata" android:exported="true" android:readPermission="com.permission.allow.readcontent"> <path-permission android:pathPrefix="/item" android:readPermission="READ_ITEM_CONTENTPROVIDER" /> </provider>
除了android:pathPrefix,还可以有android:path和android:pathPatten,例如android:pathPattern="/hello/.*"(注意,通配符*之前有个‘.’)。
如果要读取content:content://com.ctsing.content.CardContentProvider/item/1则需要声明整个provider的权限com.permission.allow.readcontent或者该路径的权限READ_ITM_CONTENTPROVIDER。
14.11.20更新,下面说一下数据变化监听问题:
当数据发生变化时,我们需要知道当数据变化时,希望能够得到通知,在android中已经设计了这种监听是用ContentObserver接口实现。联系人和短信内容发生变化时我们能够实现监听变化就是ContentObserver实现的。上述例子中我已经加入notifyChange方法进行内容变化通知,我们只有实现ContentObserver进行注册监听即可。
实现数据监听类:
public class ShuaCardContentObserver extends ContentObserver{ private Context mContext; final String SHUACARD_ACTION="com.shuaCard.action"; final String SHUACARD_DATA="getData"; final String SHUACARD_KEYCARDNUM="card_num"; final String SHUACARD_KEYCARDADDRESS="card_path"; public ShuaCardContentObserver(Handler handler,Context ctx) { super(handler); this.mContext=ctx; } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void onChange(boolean selfChange, Uri uri) { super.onChange(selfChange, uri); dealChange(); } void dealChange(){ Uri uri = Uri.parse("content://" + CardContentProvider.AUTHORITY + "/item"); ContentResolver resolver = mContext.getContentResolver(); Cursor cursor = resolver.query(uri, null, null, null, null); List<Map<String,String>> cardList=null; if(cursor.getCount()>0) cardList=new ArrayList<Map<String,String>>(); while(cursor.moveToNext()){ String cardNum=cursor.getString(cursor.getColumnIndex(DBContent.CARD_IDNUM)); String cardAddress=cursor.getString(cursor.getColumnIndex(DBContent.CARD_PICTRUE_PATH)); Map<String,String> map=new HashMap<String, String>(); map.put(SHUACARD_KEYCARDNUM, cardNum); map.put(SHUACARD_KEYCARDADDRESS, cardAddress); cardList.add(map); } if(cursor!=null) cursor.close(); if(cardList!=null){ Intent it=new Intent(SHUACARD_ACTION); Bundle b=new Bundle(); b.putSerializable(SHUACARD_DATA, (Serializable)cardList); it.putExtras(b); mContext.sendBroadcast(it); } }
注册监听:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); cardChangeObserver=new ShuaCardContentObserver(new android.os.Handler(), this); getContentResolver().registerContentObserver(Uri.parse("content://" + CardContentProvider.AUTHORITY + "/item"), true,cardChangeObserver); }
退出取消监听:
@Override protected void onDestroy() { if(null!=cardChangeObserver) getContentResolver().unregisterContentObserver(cardChangeObserver); super.onDestroy(); }
虽然我们可以知道数据发生变化,但是我们并不知道具体发生变化数据具体情况(删除哪些数据或是添加哪些),如何实现这一个需求呢?android并没有实现,这就需要我们自己实现了。有待后续研究,更新中。。。