[Android基础]四大组件之ContentProvider
URI简介
URI(Universal Resource Identifier),又被称为”通用资源标志符”。
URI由许多部分所组成,示例及解说如下:
Content URIs介绍
Android遵循URI的标准,定义了一套专用的Uri(即,Content URIs)。并且,Android提供了ContentUris、UriMatcher等类用于操作Content URIs。
1. Content URIs语法
Content URIs的语法如下:
content://authority/path/id
Content URIs的示例及说明如下:
说明:
content: Content URIs前缀,它对应与标准URI的scheme。它的值为ContentResolver.SCHEME_CONTENT(即,content://)。
authority: 一个唯一的标识符,Google建议使用类的全名来作为authority。外部调用者可以根据这个标识来找到它。
path: 它可以用来表示我们要操作的数据,外部调用者根据这个路径信息来判断要返回什么类型的数据。这个后缀路径可以自由定义。
id: 唯一的数字标识符。它表示要具体操作的数据类型中的具体某一项。
2. Content URIs相关类介绍
2.1 ContentUris
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。
2.2 UriMatcher
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
1.1ContentProvider简介
ContentProvider通常用于共享数据。
当其他程序需要访问本程序的数据,并且数据的结构比较复杂时,就可以使用ContentProvider来共享数据。如果数据不需要跨程序访问,使用数据库即可;如果数据结构比较简单,可以考虑前面提到的通过Intent共享文本等简单数据,或者通过FileProvider共享文件。
1.2ContentProvider示例
接下来,实现一个ContentProvider。该ContentProvider包括两部分:ContentProvider提供者APK 和 ContentProvider测试APK。
(01) ContentProvider提供者:自定义一个ContentProvider,并监听相应的URI。客户可以通过URI插入/删除/更新/查询数据。ContentProvider中的数据记录的是人的信息,包括”姓名,出生年月,email,性别”等信息。
(02) ContentProvider测试APK:通过URI向ContentProvider发起插入/删除/更新/查询等操作。
下面介绍ContentProvider的实现步骤。
2. ContentProvider提供者APK
2.1 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”两个属性。
2.2 ContentProvider对应的manifest
在manifest中声明我们自定义的ContentProvider。
<application android:label="@string/app_name" android:icon="@drawable/ic_launcher"> <provider android:name="MyProvider" android:authorities="com.skw.myprovider" /></application>
2.3 自定义的ContentProvider类
完成ContentProvider类,主要需要注意以下几点:
(01) ContentProvider的数据一般是以”数据库”或”网络数据”的方式存储的。如果是数据库,则需要实现SQLiteOpenHelper类。通过SQLiteOpenHelper类新建/管理数据库。
(02) ContentProvider主要是以Uri的形式方式访问的(也可以通过Intent)。要通过UriMatcher注册ContentProvider监听的Uri。
(03) ContentProvider是一个抽象类。当我们需要以继承ContentProvider的方式自定义ContentProvider时,需要实现query(), insert(), update(), delete(), getType(), onCreate()这六个函数。
2.3.1 数据库
下面,先介绍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) { } }
3.3.2 注册Uri
// 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”进行监听。
3.3.2 实现ContentProvider的抽象函数
onCreate()
@Override public boolean onCreate() { mDbHelper = new DBLiteHelper(this.getContext()); Log.d(TAG, "open/create table"); return true; }
说明:onCreate()中新建SQLiteOpenHelper对象。
delete()
@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()类似,就不再说明。
getType()
@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对应的数据类型。
4. ContentProvider测试APK
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", "Jimmy20020201@126.com", 1); insert("Kate", "20030104", "kate20030104@126.com", 0); insert("Li Lei", "20021124", "lilei20101124@126.com", 1); insert("Lucy", "20010624", "lucy20101124@126.com", 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介绍
<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非必需的。图标。
自定义Permission示例
1. 设置权限
<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与声明的权限对应。
2. 获取权限
在需要使用ContentProvider的APK的manifest中需要声明使用该权限。声明方法如下:
<uses-permission android:name="com.skw.permission.myprovider" />
更多相关文章
- Android应用程序获取ROOT权限的方法
- Android中SQLite数据库介绍
- Android权限设置大全
- 【android】listview动态更新(与数据库之间的交道)
- Android系统源码数据库(mmssms.db)(转)
- Android Jetpack ViewModel管理数据
- Android中的数据结构解析(四)SparseArray和ArrayMap
- Android默认给予USB读写权限,去掉授权对话框