在Android中,我们的应用有的时候需要对外提供数据接口,可以有如下几种方法:1)AIDL 2)Broadcast 3)ContentProvider。
使用AIDL需要我们编写AIDL接口以及实现,而且对方也要有相应的接口描述,有点麻烦;使用Broadcast,我们不需要任何接口描述,只要协议文档就可以了,但是有点不好就是,这种方式不直接而且是异步的;使用ContentProvider我们不需要接口描述,只需要知道协议,同时这种方式是同步的,使用方便。
Android提供了ContentProvider,一个程序可以通过实现一个Content provider的抽象接口将自己的数据完全暴露出去,而且Content providers是以类似数据库中表的方式将数据暴露。Content providers存储和检索数据,通过它可以让所有的应用程序访问到,这也是应用程序之间唯一共享数据的方法。要想使应用程序的数据公开化,可通过2种方法:创建一个属于你自己的Content provider或者将你的数据添加到一个已经存在的Content provider中,前提是有相同数据类型并且有写入Content provider的权限。
如何通过一套标准及统一的接口获取其他应用程序暴露的数据?Android提供了ContentResolver,外界的程序可以通过ContentResolver接口访问ContentProvider提供的数据。
当前篇主要说明,如何获取其它应用程序共享的数据,比如获取Android 手机电话薄中的信息。
什么是URI?
在学习如何获取ContentResolver前,有个名词是必须了解的:URI。URI是网络资源的定义,在Android中赋予其更广阔的含义,先看个例子,如下: 将其分为A,B,C,D 4个部分: A:标准前缀,用来说明一个Content Provider控制这些数据,无法改变的; B:URI的标识,它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的 类名。这个标识在<provider> 元素的 authorities属性中说明: <provider name=”.TransportationProvider” authorities=”com.example.transportationprovider” . . . > C:路径,Content Provider使用这些路径来确定当前需要生什么类型的数据,URI中可能不包括路径,也可能包括多个; D:如果URI中包含,表示需要获取的记录的ID;如果没有ID,就表示返回全部; 由于URI通常比较长,而且有时候容易出错,切难以理解。所以,在Android当中定义了一些辅助类,并且定义了一些常量来代替这些长字符串,例如:People.CONTENT_URI
在上面已经说明了Android是如何实现应用程序之间数据共享的,并详细解析了如何获取其他应用程序共享的数据。ContentProviders存储和检索数据,通过它可以让所有的应用程序访问到,这也是应用程序之间唯一共享数据的方法。那么如何将应用程序的数据暴露出去?
通过以前文章的学习,知道ContentResolver是通过ContentProvider来获取其他与应用程序共享的数据,那么ContentResolver与ContentProvider的接口应该差不多的。
其中ContentProvider负责
- 组织应用程序的数据;
- 向其他应用程序提供数据;
ContentResolver则负责
- 获取ContentProvider提供的数据;
- 修改/添加/删除更新数据等;
ContentProvider 是如何向外界提供数据的?
Android提供了ContentProvider,一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据完全暴露出去,而且ContentProviders是以类似数据库中表的方式将数据暴露,也就是说ContentProvider就像一个“数据库”。那么外界获取其提供的数据,也就应该与从数据库中获取数据的操作基本一样,只不过是采用URI来表示外界需要访问的“数据库”。至于如何从URI中识别出外界需要的是哪个“数据库”,这就是Android底层需要做的事情了,不在此详细说。
下面的代码是ContentProvider示例程序。
1. 先创建本地的数据库。
Common类为公共数据。
package com.demo.contentprovider;import android.net.Uri; public class Common { public static final String DBNAME = "BageDb"; public static final String TNAME = "friendsInfo"; public static final int VERSION = 3; public static String TID = "tid"; public static final String USERNAME = "username"; public static final String EMAIL = "email"; public static final String AUTOHORITY = "com.demo.contentprovider"; public static final int ITEM = 1; public static final int ITEM_ID = 2; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.bage.login"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.bage.login"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTOHORITY + "/"+TNAME); }创建本地数据库:
2.为SQLite示例程序添加ContentProvider类package com.demo.contentprovider;import android.content.ContentValues;import android.content.Context;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;import android.database.sqlite.SQLiteDatabase.CursorFactory;public class DBLite extends SQLiteOpenHelper{ public DBLite(Context context) { super(context, Common.TNAME, null, Common.VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create table "+Common.TNAME+"(" + Common.TID+" integer primary key autoincrement not null,"+ Common.EMAIL+" text not null," + Common.USERNAME+" text not null);"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } public void add(String email,String username){ SQLiteDatabase db = getWritableDatabase(); ContentValues values = new ContentValues(); values.put(Common.EMAIL, email); values.put(Common.USERNAME, username); db.insert(Common.TNAME,"",values); } }实现ContentProvider这个抽象类的抽象方法,具体如下所示:
public boolean onCreate(),当ContentProvider生成的时候调用此方法。
public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) ,此方法返回一个Cursor 对象作为查询结果集。
public Uri insert(Uri uri, ContentValues initialValues),此方法负责往数据集当中插入一列,并返回这一列的Uri。
public int delete(Uri uri, String where, String[] whereArgs),此方法负责删除指定Uri的数据。
public int update(Uri uri, ContentValues values, String where,String[] whereArgs),此方法负责更新指定Uri的数据。
public String getType(Uri uri),返回所给Uri的MIME类型。
下面为该类的具体代码:
package com.demo.contentprovider;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.text.TextUtils;import android.util.Log;/** * ContentProvider是什么时候创建的,是谁创建的?访问某个应用程序共享的数据,是否需要启动这个应用程序? * 这个问题在 Android SDK中没有明确说明,但是从数据共享的角度出发,ContentProvider应该是Android在系统启动时就创建了,否则就谈不上数据共享了。 * 这就要求在AndroidManifest.XML中使用元素明确定义。 * 可通过2种方法:创建一个属于你自己的 ContentProvider或者将你的数据添加到一个已经存在的ContentProvider中,当然前提是有相同数据类型并且有写入 Content provider的权限。 * * */ public class MyContentProvider extends ContentProvider{ private DBLite dBlite; private SQLiteDatabase db; private static final UriMatcher sMatcher ; //注册需要匹配的Uri static{ //实例化 sMatcher = new UriMatcher(UriMatcher.NO_MATCH); //实例化后调用addURI方法注册URI,该方法有三个参数,分别需要传入URI字符串的authority部分、path部分以及自定义的整数code三者; sMatcher.addURI(Common.AUTOHORITY,Common.TNAME, Common.ITEM); sMatcher.addURI(Common.AUTOHORITY, Common.TNAME+"/#", Common.ITEM_ID); } @Override public boolean onCreate() { // TODO Auto-generated method stub dBlite = new DBLite(this.getContext()); return true; } @Override public String getType(Uri uri) { // TODO Auto-generated method stub switch (sMatcher.match(uri)) { case Common.ITEM: return Common.CONTENT_TYPE; case Common.ITEM_ID: return Common.CONTENT_ITEM_TYPE; default: throw new IllegalArgumentException("Unknown URI"+uri); } } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { db = dBlite.getWritableDatabase(); int count = 0; switch (sMatcher.match(uri)) { case Common.ITEM: count = db.delete(Common.TNAME,selection, selectionArgs); break; case Common.ITEM_ID: String id = uri.getPathSegments().get(1); count = db.delete(Common.TID, Common.TID+"="+id+(!TextUtils.isEmpty(Common.TID="?")?"AND("+selection+')':""), selectionArgs); break; default: throw new IllegalArgumentException("Unknown URI"+uri); } getContext().getContentResolver().notifyChange(uri, null); return count; } @Override public Uri insert(Uri uri, ContentValues values) { db = dBlite.getWritableDatabase(); long rowId; if(sMatcher.match(uri)!=Common.ITEM){ throw new IllegalArgumentException("Unknown URI"+uri); } rowId = db.insert(Common.TNAME,Common.TID,values); if(rowId>0){ Uri noteUri=ContentUris.withAppendedId(Common.CONTENT_URI, rowId); getContext().getContentResolver().notifyChange(noteUri, null); return noteUri; } throw new IllegalArgumentException("Unknown URI"+uri); } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { db = dBlite.getWritableDatabase(); Cursor c; Log.d("-------", String.valueOf(sMatcher.match(uri))); switch (sMatcher.match(uri)) { case Common.ITEM: c = db.query(Common.TNAME, projection, selection, selectionArgs, null, null, null); break; case Common.ITEM_ID: String id = uri.getPathSegments().get(1); c = db.query(Common.TNAME, projection, Common.TID+"="+id+(!TextUtils.isEmpty(selection)?"AND("+selection+')':""),selectionArgs, null, null, sortOrder); break; default: Log.d("!!!!!!", "Unknown URI"+uri); throw new IllegalArgumentException("Unknown URI"+uri); } c.setNotificationUri(getContext().getContentResolver(), uri); return c; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; }}并且要牢记添加ContentProvider配置:
android:name要写ContentProvider继承类的全名。android:authorities要写和CONTENT_URI常量的B部分(见下图)。注意不要把上图C和D部分加到authorities中去。authorities是用来识别ContentProvider的,C和D部分实际上是ContentProvider内部使用的。
3.测试SQLite示例程序的ContentProvider
ContentProvider即然是提供给其他应用访问本应用数据的,所以我们需要另创建一个Android应用,来测试SQLite示例程序的ContentProvider。
//查询代码 Cursor cursor = contentResolver.query( Common.CONTENT_URI,null, null, null, null); Log.i(TAG, "count:"+cursor.getCount()+""); StringBuffer buffer = new StringBuffer(); while(cursor.moveToNext()){ buffer.append(cursor.getString(cursor.getColumnIndex(Common.EMAIL))+"/"); buffer.append(cursor.getString(cursor.getColumnIndex(Common.USERNAME))); Log.i(TAG,buffer.toString()); buffer.delete(0, buffer.length()); } startManagingCursor(cursor); //查找后关闭游标该程序的完整代码下载地址:http://download.csdn.net/source/3578495-------------------------------------------------------------------------------------------------------------------------------------------------------------------
补充:
上面的那个ContentProvider,如果不设置权限,无论ContentResolver是否在一个程序里,都可以直接访问。
但是为了数据安全,我们一般都会给ContentProvider设置权限。如果是别的程序要访问我们的ContentProvider的话,需要修改一下配置,增加权限。
我们在provider中增加一个访问权限。代码如下:
在另一个程序里,我们定义的读取ContentProvider的程序里的manifest文件增加权限,如下:
这样,两个程序可以共享数据了。
ContentProvider是对各种数据源进行统一包装并外露的工具——不只是我们这里所用到的典型的数据库式的数据源,其他如文件等数据源也可以通过ContentProvider来包装并实现对其他有需求的消费者的数据供应。虽然对于初学者来说ContentProvider是较为难以理解的,但当你用熟后,你便定会感叹于Android系统的细节精妙性!