URI(Universal Resource Identifier),又被称为”通用资源标志符”。
URI由许多部分所组成,示例及解说如下:
Android遵循URI的标准,定义了一套专用的Uri(即,Content URIs)。并且,Android提供了ContentUris、UriMatcher等类用于操作Content URIs。
Content URIs的语法如下:
content://authority/path/id
Content URIs的示例及说明如下:
说明:
content: Content URIs前缀,它对应与标准URI的scheme。它的值为ContentResolver.SCHEME_CONTENT(即,content://)。
authority: 一个唯一的标识符,Google建议使用类的全名来作为authority。外部调用者可以根据这个标识来找到它。
path: 它可以用来表示我们要操作的数据,外部调用者根据这个路径信息来判断要返回什么类型的数据。这个后缀路径可以自由定义。
id: 唯一的数字标识符。它表示要具体操作的数据类型中的具体某一项。
ContentUris中包含了三个静态函数:
long parseId(Uri uri): 解析Uri中的末尾id。成功返回id,失败则返回-1。
Uri withAppendedId(Uri uri, long id): 将id追加到uri中,并返回追加id后的uri。
Uri.Builder appendId(Uri.Builder builder, long id): 将id追加到builder中,并返回追加id后的builder。
UriMatcher用于匹配Uri。它的用法如下:
(01) 创建UriMatcher对象。
(02) 把你需要匹配Uri路径通过addURI()注册到UriMatcher对象上。
(03) 注册成功后,ContentProvider就可以通过UriMatcher监听你注册的Uri。当有匹配的Uri动作(如插入)时,再就可以通过UriMatcher的match()函数来获取Uri的一个标识,该标识是在addURI()时传入的。这个标识的作用是方便在switch语句中对不同的Uri进行处理。
UriMatcher的主要API说明:
void addURI(String authority, String path, int code): 将”authority”+”path”注册的Uri注册到UriMatcher中,code是该Uri对应的标识。
int match(Uri uri): 匹配Uri,并返回Uri对应的标识。这里返回的标识与addURI中的标识对应。
ContentProvider通常用于共享数据。
当其他程序需要访问本程序的数据,并且数据的结构比较复杂时,就可以使用ContentProvider来共享数据。如果数据不需要跨程序访问,使用数据库即可;如果数据结构比较简单,可以考虑前面提到的通过Intent共享文本等简单数据,或者通过FileProvider共享文件。
接下来,实现一个ContentProvider。该ContentProvider包括两部分:ContentProvider提供者APK 和 ContentProvider测试APK。
(01) ContentProvider提供者:自定义一个ContentProvider,并监听相应的URI。客户可以通过URI插入/删除/更新/查询数据。ContentProvider中的数据记录的是人的信息,包括”姓名,出生年月,email,性别”等信息。
(02) ContentProvider测试APK:通过URI向ContentProvider发起插入/删除/更新/查询等操作。
下面介绍ContentProvider的实现步骤。
根据ContentProvider的数据特性,我们建立一张表,表格包括”id/姓名/出生年月/email/性别”这些信息。表对应的类如下:
public final class MyContract {
public MyContract() {}
/** * BaseColumns类中有两个属性:_ID 和 _COUNT */
public static abstract class Entry implements BaseColumns {
public static final String TABLE_NAME = "mytable01";
public static final String NAME = "name";
public static final String BIRTH_DAY = "birthday";
public static final String EMAIL = "email";
public static final String GENDER = "gender";
}
}
说明:BaseColumns是Android自带的类,它集成了”_ID”和”_COUNT”两个属性。
在manifest中声明我们自定义的ContentProvider。
<application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
<provider android:name="MyProvider" android:authorities="com.skw.myprovider" />
</application>
完成ContentProvider类,主要需要注意以下几点:
(01) ContentProvider的数据一般是以”数据库”或”网络数据”的方式存储的。如果是数据库,则需要实现SQLiteOpenHelper类。通过SQLiteOpenHelper类新建/管理数据库。
(02) ContentProvider主要是以Uri的形式方式访问的(也可以通过Intent)。要通过UriMatcher注册ContentProvider监听的Uri。
(03) ContentProvider是一个抽象类。当我们需要以继承ContentProvider的方式自定义ContentProvider时,需要实现query(), insert(), update(), delete(), getType(), onCreate()这六个函数。
下面,先介绍ContentProvider的数据库的实现。
// 创建表格的SQL语句
private static final String SQL_CREATE_ENTRIES =
"CREATE TABLE " + Entry.TABLE_NAME + " (" +
Entry._ID + " INTEGER PRIMARY KEY," +
Entry.NAME + " TEXT NOT NULL, " +
Entry.BIRTH_DAY + " TEXT, " +
Entry.EMAIL + " TEXT, " +
Entry.GENDER + " INTEGER " +
" )";
private class DBLiteHelper extends SQLiteOpenHelper {
public static final int DATABASE_VERSION = 1;
public static final String DATABASE_NAME = "MyProvider.db";
public DBLiteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_ENTRIES);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
// Uri的authority
public static final String AUTHORITY = "com.skw.myprovider";
// Uri的path
public static final String PATH = "table01";
// UriMatcher中URI对应的序号
public static final int ITEM_ALL = 1;
public static final int ITEM_ID = 2;
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
static {
URI_MATCHER.addURI(AUTHORITY, PATH, ITEM_ALL);
URI_MATCHER.addURI(AUTHORITY, PATH+"/#", ITEM_ID);
}
说明:通过addURI()就可以将URI注册到UriMatcher中,从而实现ContentProvider对URI的监听。这里的AUTHORITY与manifest中的android:authorities一致!
例如, URI_MATCHER.addURI(AUTHORITY, PATH, ITEM_ALL);
意味着ContentProvider对”content://con.skw.myprovider/table01”进行监听。
例如, URI_MATCHER.addURI(AUTHORITY, PATH+"/#", ITEM_ALL);
意味着ContentProvider对”content://con.skw.myprovider/table01/5”进行监听。
@Override
public boolean onCreate() {
mDbHelper = new DBLiteHelper(this.getContext());
Log.d(TAG, "open/create table");
return true;
}
说明:onCreate()中新建SQLiteOpenHelper对象。
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
int count = 0;
switch (URI_MATCHER.match(uri)) {
case ITEM_ALL:
count = db.delete(Entry.TABLE_NAME, selection, selectionArgs);
Log.d(TAG, "delete ITEM uri="+uri+", count="+count);
break;
case ITEM_ID:
// 获取id列的值
String id = uri.getPathSegments().get(1);
count = db.delete(Entry.TABLE_NAME, Entry._ID+"=?", new String[]{id});
Log.d(TAG, "delete ITEM_ID id="+id+", uri="+uri+", count="+count);
break;
default:
throw new IllegalArgumentException("Unknown URI"+uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
说明:delete()中会通过match()获取uri对应的编码。这里的编码就是和addURI()注册uri的编码是相对应的。此外,notifyChange()的作用是通常数据库变化,若有ContentObserver监听该Uri,则notifyChange()最终会将消息传递给监听者。
insert(), update(), query()的实现与delete()类似,就不再说明。
@Override
public String getType(Uri uri) {
switch (URI_MATCHER.match(uri)) {
case ITEM_ALL:
return "skw.myprovider.dir/table01";
case ITEM_ID:
return "skw.myprovider.item/table01";
default:
throw new IllegalArgumentException("Unknown URI"+uri);
}
}
说明:getType()是返回Uri对应的数据类型。
public class ProviderTest extends Activity implements View.OnClickListener {
private static final String TAG = "##ProviderTest##";
// 数据库的属性,与MyProvider的表格属性一致
public static final String NAME = "name";
public static final String BIRTH_DAY = "birthday";
public static final String EMAIL = "email";
public static final String GENDER = "gender";
// 数据库的URI
public static final Uri CONTENT_URI = Uri.parse("content://com.skw.myprovider/table01");
private ContentResolver mContentResolver = null;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
((Button)findViewById(R.id.insert)).setOnClickListener(this);
((Button)findViewById(R.id.deleteFirst)).setOnClickListener(this);
((Button)findViewById(R.id.deleteKate)).setOnClickListener(this);
((Button)findViewById(R.id.deleteAll)).setOnClickListener(this);
((Button)findViewById(R.id.update)).setOnClickListener(this);
((Button)findViewById(R.id.show)).setOnClickListener(this);
// 删除第一行,然后全部打印出来
mContentResolver = getContentResolver();
}
@Override
public void onClick(View view) {
switch(view.getId()) {
case R.id.insert:
// 添加
insert("Jimmy", "20020201", "[email protected]", 1);
insert("Kate", "20030104", "[email protected]", 0);
insert("Li Lei", "20021124", "[email protected]", 1);
insert("Lucy", "20010624", "[email protected]", 0);
break;
case R.id.deleteFirst:
ContentUris cus = new ContentUris();
Uri uri = cus.withAppendedId(CONTENT_URI, 1);
Log.d(TAG, "delete uri="+uri);
mContentResolver.delete(uri, null, null);
break;
case R.id.deleteKate:
// 删除“username=Kate”的行,然后全部打印出来
mContentResolver.delete(CONTENT_URI, NAME+"=?", new String[]{"Kate"});
break;
case R.id.deleteAll:
// 删除全部的行,然后全部打印出来
deleteAll() ;
break;
case R.id.update:
// 更新第1个值,然后全部打印出来
updateItem() ;
break;
case R.id.show:
// 打印全部的值
printAll() ;
break;
default:
// 查找第2个值
//querySecondItem() ;
break;
}
}
/* * 通过ContentResolver,将值插入到MyProvider中 */
private void insert(String name, String date, String email, int gender) {
ContentResolver cr = getContentResolver();
ContentValues cv = new ContentValues();
cv.put(NAME, name);
cv.put(BIRTH_DAY, date);
cv.put(EMAIL, email);
cv.put(GENDER, gender);
Uri uri = cr.insert(CONTENT_URI, cv);
Log.d(TAG, "insert uri="+uri);
}
private void updateItem() {
ContentResolver cr = getContentResolver();
ContentUris cus = new ContentUris();
Uri uri = cus.withAppendedId(CONTENT_URI, 1);
ContentValues cv = new ContentValues();
cv.put(NAME, "update_name");
cv.put(BIRTH_DAY, "update_date");
cv.put(EMAIL, "update_email");
cv.put(GENDER, 1);
cr.update(uri, cv, null, null);
}
/* * 通过ContentResolver,将MyProvider中的值全部删除 */
private void deleteAll() {
Log.d(TAG, "delete all value!");
ContentResolver cr = getContentResolver();
cr.delete(CONTENT_URI, null, null);
}
private void querySecondItem() {
ContentResolver cr = getContentResolver();
ContentUris cus = new ContentUris();
Uri uri = cus.withAppendedId(CONTENT_URI, 2);
String[] proj = new String[] { NAME, BIRTH_DAY, EMAIL, GENDER};
Cursor cursor = cr.query(uri, proj, null, null, null);
int index = 0;
while (cursor.moveToNext()) {
Log.d(TAG, "querySecondItem--"+index+"--"
+", email=" + cursor.getString(cursor.getColumnIndex(EMAIL))
+", username=" + cursor.getString(cursor.getColumnIndex(NAME))
+", date=" + cursor.getString(cursor.getColumnIndex(BIRTH_DAY))
+", gender=" + cursor.getInt(cursor.getColumnIndex(GENDER)));
index++;
}
}
private void printAll() {
//通过contentResolver进行查找
ContentResolver cr = getContentResolver();
Log.d(TAG, "print all value!");
// query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
// 返回的列
String[] proj = new String[] { NAME, BIRTH_DAY, EMAIL, GENDER};
Cursor cursor = cr.query(
CONTENT_URI, proj, null, null, null);
int index = 0;
while (cursor.moveToNext()) {
Log.d(TAG, "printAll--"+index+"--"
+", email=" + cursor.getString(cursor.getColumnIndex(EMAIL))
+", username=" + cursor.getString(cursor.getColumnIndex(NAME))
+", date=" + cursor.getString(cursor.getColumnIndex(BIRTH_DAY))
+", gender=" + cursor.getString(cursor.getColumnIndex(GENDER)));
index++;
}
startManagingCursor(cursor); //查找后关闭游标
}
}
<permission
android:name="com.skw.permission.myprovider"
android:protectionLevel="normal"
android:label="@string/permission_label"
android:description="@string/permission_description"
/>
说明:permission常用的几个属性如下:
(01) android:name: 必需的。权限的名称,通常应遵循android 命名方案(.permission.)。
(02) android:protectionLevel: 必需的。权限的安全级别,共包括”normal, dangerous, signature, signatureOrSystem”四种。
normal 表示权限是低风险的,不会对系统、用户或其他应用程序造成危害;
dangerous 表示权限是高风险的,系统将可能要求用户输入相关信息,才会授予此权限;
signature 表示只有当应用程序所用数字签名与声明引权限的应用程序所用数字签名相同时,才能将权限授给它;
signatureOrSystem 表示将权限授给具有相同数字签名的应用程序或android 包类。
(03) android:permissionGroup: 非必需的。可以将权限放在一个组中,但对于自定义权限,应该避免设置此属性。
(04) android:label: 非必需的。标签。
(05) android:description: 非必需的。描述。
(06) android:icon非必需的。图标。
<application android:exported="true" android:label="@string/app_name" android:icon="@drawable/ic_launcher">
<provider android:name="MyProvider" android:authorities="com.skw.myprovider" android:permission="com.skw.permission.myprovider">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</provider>
</application>
<permission android:name="com.skw.permission.myprovider" android:protectionLevel="normal" android:label="@string/permission_label" android:description="@string/permission_description" />
说明:上面是自定义权限的manifest文件内容。
(01) 首先,在需要定义权限的ContentProvider中声明权限android:permission="com.skw.permission.myprovider"
(02) 接着,再定义permission。permission对应的name与声明的权限对应。
在需要使用ContentProvider的APK的manifest中需要声明使用该权限。声明方法如下:
<uses-permission android:name="com.skw.permission.myprovider" />