一. 前言 1. 什么是ContentProvider?
内容提供者,是 Android 的四大组件之一
应用程序(进程)间共享数据的一种方式
为存储和获取数据提供了统一的接口
2. Google是怎样定义ContentProvider的?
内容提供者将一些特定的应用程序数据供给其他应用程序使用。
数据可以存储于文件系统,SQLite数据库或者其他方式。
内容提供者继承于ContentProvider基类,为其它应用程序取用和存储它管理的数据实现了一套标准的方法。
应用程序并不直接调用这些方法,而是使用ConteResolver对象,调用它的方法作为替代。
ContentResolver可以与任意内容提供者进行会话,与其合作来对所有相关交互通讯进行管理。
3. 作用示意图
二. 原理 参考:图文详解 Android Binder跨进程通信的原理
三. ContentProvider的使用 1. 统一资源标识符 URI ① 定义:Uniform Resource Identifier
,即统一资源标识符。
② 作用:唯一标识 ContentProvider 和 其中的数据。(外界通过 URI 找到对应的 ContentProvider其中的数据,再对数据进行操作)
③ 分类 :
1 2 3 4 5 6 7 8 9 系统内置的数据(如通讯录,日程表等),使用的时候查找即可 自定义 URI = content:
④ 注意
2. MIME数据类型 ① 作用:指定某个扩展名的文件用某种应用程序来打开 如指定.html
文件采用text
应用程序打开、指定.pdf
文件采用flash
应用程序打开
② ContentProvider会根据URI返回MIME类型
1 2 3 4 5 @Nullable @Override public String getType (@NonNull Uri uri) { return null ; }
③ MIME类型的组成
每个MIME类型由两部分组成,类型+子类型。是一个包含两部分的字符串。
1 2 3 4 5 text / html text / css text / xml application / pdf
④ MIME类型形式
1 2 3 4 5 6 7 8 9 vnd.android.cursor.item/自定义 vnd.android.cursor.dir/自定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <-- 单条记录 --> vnd.android.cursor.item/vnd.yourcompanyname.contenttype content: vnd.android.cursor.item/vnd.example.rail <-- 多条记录 --> vnd.android.cursor.dir/vnd.yourcompanyname.contenttype content: vnd.android.cursor.dir/vnd.example.rail
3. ContentProvider类 ① 继承ContentProvider需要重写的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class MyContentProvider extends ContentProvider { @Override public boolean onCreate () { return false ; } @Nullable @Override public Cursor query (@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { return null ; } @Nullable @Override public String getType (@NonNull Uri uri) { return null ; } @Nullable @Override public Uri insert (@NonNull Uri uri, @Nullable ContentValues values) { return null ; } @Override public int delete (@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { return 0 ; } @Override public int update (@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { return 0 ; } }
② 核心方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <-- 4 个核心方法 --> public Uri insert (Uri uri, ContentValues values) public int delete (Uri uri, String selection, String[] selectionArgs) public int update (Uri uri, ContentValues values, String selection, String[] selectionArgs) public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) <-- 2个其他方法 --> public boolean onCreate () public String getType (Uri uri)
4. 辅助工具类-ContentResolver类 ① 作用:统一管理不同 ContentProvider 间的操作
即通过 URI 即可操作 不同的 ContentProvider 中的数据
外部进程通过 ContentResolver类 进而与 ContentProvider类 进行交互
② 为什么不直接使用ContentProvider类,非要借助ContentResolver类
一般来说,一款应用要使用多个ContentProvider
,若需要了解每个ContentProvider
的不同实现从而再完成数据交互,操作成本高 & 难度大 。
所以在ContentProvider类上加多了一个
ContentResolver类对所有的
ContentProvider`进行统一管理。
③ 具体使用
1 2 3 4 5 6 7 8 9 10 11 public Uri insert (Uri uri, ContentValues values) public int delete (Uri uri, String selection, String[] selectionArgs) public int update (Uri uri, ContentValues values, String selection, String[] selectionArgs) public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
1 2 3 4 5 6 7 8 9 10 ContentResolver resolver = getContentResolver(); Uri uri = Uri.parse("content://cn.scu.myprovider/user" ); Cursor cursor = resolver.query(uri, null , null , null , "userid desc" );
5.辅助工具类-ContentUris类 ① 作用:操作 URI
。
② 具体使用:
1 2 3 4 5 6 7 8 9 10 11 Uri uri = Uri.parse("content://cn.scu.myprovider/user" ) Uri resultUri = ContentUris.withAppendedId(uri, 7 ); Uri uri = Uri.parse("content://cn.scu.myprovider/user/7" ) long personid = ContentUris.parseId(uri);
6. 辅助工具类-UriMatcher类 ① 作用
在ContentProvider
中注册URI
。
根据 URI
匹配 ContentProvider
中对应的数据表。
② 具体使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); int URI_CODE_a = 1 ;int URI_CODE_b = 2 ;matcher.addURI("cn.scu.myprovider" , "user1" , URI_CODE_a); matcher.addURI("cn.scu.myprovider" , "user2" , URI_CODE_b); @Override public String getType (Uri uri) { Uri uri = Uri.parse(" content://cn.scu.myprovider/user1" ); switch (matcher.match(uri)){ case URI_CODE_a: return tableNameUser1; case URI_CODE_b: return tableNameUser2; } }
7. 辅助工具类-ContentObserver类 ① 定义:内容观察者。
② 作用:观察 Uri
引起 ContentProvider
中的数据变化 & 通知外界(即访问该数据访问者)。也就是说,当ContentProvider
中的数据发生变化(增、删 & 改)时,就会触发该 ContentObserver
类。
③ 具体使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 getContentResolver().registerContentObserver(uri); public class UserContentProvider extends ContentProvider { public Uri insert (Uri uri, ContentValues values) { db.insert("user" , "userid" , values); getContext().getContentResolver().notifyChange(uri, null ); } } getContentResolver().unregisterContentObserver(uri);
四. 实例说明 1. 进程内通信 ① 数据库类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class DatabaseHelper extends SQLiteOpenHelper { public static final String DATABASE_NAME = "xl.db" ; public static final int DATABASE_VERSION = 1 ; public static final String USER_TABLE_NAME = "user" ; public static final String USER_COLUMN_NAME = "username" ; public static final String USER_COLUMN_PASSWORD = "password" ; public static final String LESSON_TABLE_NAME = "lesson" ; public static final String LESSON_COLUMN_NAME = "lesson_name" ; public static final String LESSON_COLUMN_COUNT = "lesson_count" ; public DatabaseHelper (@Nullable Context context) { super (context, DATABASE_NAME, null , DATABASE_VERSION); } @Override public void onCreate (SQLiteDatabase db) { db.execSQL("create table " + USER_TABLE_NAME + "(" + USER_COLUMN_NAME + " varchar(20) not null, " + USER_COLUMN_PASSWORD + " varchar(20) not null);" ); db.execSQL("create table " + LESSON_TABLE_NAME + "(" + LESSON_COLUMN_NAME + " varchar(20) not null, " + LESSON_COLUMN_COUNT + " varchar(20) not null);" ); } @Override public void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) { } }
② URI 辅助类
1 2 3 4 5 6 7 8 public class URIList { public static final String CONTENT = "content://" ; public static final String AUTHORITY = "swu.xl.contentprovider" ; public static final String URI_USER = CONTENT + AUTHORITY + "/" + DatabaseHelper.USER_TABLE_NAME; public static final String URI_LESSON = CONTENT + AUTHORITY + "/" + DatabaseHelper.LESSON_TABLE_NAME; }
③ 自定义的 ContentProvider 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 public class MyContentProvider extends ContentProvider { private static UriMatcher uriMatcher; public static final int URI_MATCH_USER = 1 ; public static final int URI_MATCH_LESSON = 2 ; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(URIList.AUTHORITY,DatabaseHelper.USER_TABLE_NAME,URI_MATCH_USER); uriMatcher.addURI(URIList.AUTHORITY,DatabaseHelper.LESSON_TABLE_NAME,URI_MATCH_LESSON); } private SQLiteDatabase database; @Override public boolean onCreate () { DatabaseHelper databaseHelper = new DatabaseHelper(getContext()); database = databaseHelper.getWritableDatabase(); return false ; } @Nullable @Override public Cursor query (@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { String tableName = getTableName(uri); if (TextUtils.isEmpty(tableName)){ return null ; } Cursor cursor = database.query(tableName,projection,selection,selectionArgs,null ,null ,sortOrder); return cursor; } @Nullable @Override public String getType (@NonNull Uri uri) { return null ; } @Nullable @Override public Uri insert (@NonNull Uri uri, @Nullable ContentValues values) { String tableName = getTableName(uri); if (TextUtils.isEmpty(tableName)){ return null ; } long id = database.insert(tableName, null , values); return ContentUris.withAppendedId(uri,id); } @Override public int delete (@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { String tableName = getTableName(uri); if (TextUtils.isEmpty(tableName)){ return 0 ; } int count = database.delete(tableName, selection, selectionArgs); return count; } @Override public int update (@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { String tableName = getTableName(uri); if (TextUtils.isEmpty(tableName)){ return 0 ; } int count = database.update(tableName, values, selection, selectionArgs); return count; } private String getTableName (Uri uri) { System.out.println(uri.toString()); int type = uriMatcher.match(uri); String table_name; switch (type){ case URI_MATCH_USER: table_name = DatabaseHelper.USER_TABLE_NAME; break ; case URI_MATCH_LESSON: table_name = DatabaseHelper.LESSON_TABLE_NAME; break ; default : throw new IllegalStateException("Unexpected value: " + type); } return table_name; } }
④ 自定义的 ContentProvider 类 进行 注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android ="http://schemas.android.com/apk/res/android" package ="swu.xl.contentprovider" > <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name =".MainActivity" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity > <provider android:authorities="swu.xl.contentprovider" android:name=".MyContentProvider"/> </application > </manifest >
⑤ 调用操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class MainActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); ContentResolver contentResolver = getContentResolver(); } public void find (ContentResolver contentResolver) { Cursor cursor = contentResolver.query(Uri.parse(URIList.URI_USER), null , null , null , null ); } public void insert (ContentResolver contentResolver) { ContentValues contentValues = new ContentValues(); contentValues.put(DatabaseHelper.USER_COLUMN_NAME,"xl" ); contentValues.put(DatabaseHelper.USER_COLUMN_PASSWORD,"0000" ); contentResolver.insert(Uri.parse(URIList.URI_USER),contentValues); } public void update (ContentResolver contentResolver) { ContentValues contentValues = new ContentValues(); contentValues.put(DatabaseHelper.USER_COLUMN_PASSWORD,"1234" ); contentResolver.update(Uri.parse(URIList.URI_USER),contentValues,DatabaseHelper.USER_COLUMN_PASSWORD+"= ?" ,new String[]{"0000" }); } public void delete (ContentResolver contentResolver) { contentResolver.delete(Uri.parse(URIList.URI_USER),DatabaseHelper.USER_COLUMN_PASSWORD+"= ?" , new String[]{"0000" }); } }
源码地址:ContentProvider
2.进程间通信 参考:Android:关于ContentProvider的知识都在这里了!
五. 优点 1. 安全 ContentProvider
为应用间的数据交互提供了一个安全的环境:允许把自己的应用数据根据需求开放给 其他应用 进行 增、删、改、查 ,而不用担心因为直接开放数据库权限而带来的安全问题。
2. 访问简单 & 高效 对比于其他对外共享数据的方式,数据访问方式会因数据存储的方式而不同:
采用 文件方式 对外共享数据,需要进行文件操作读写数据;
采用 Sharedpreferences
共享数据,需要使用 sharedpreferences API
读写数据
这使得访问数据变得复杂 & 难度大。
而采用ContentProvider
方式,其 解耦了 底层数据的存储方式,使得无论底层数据存储采用何种方式,外界对数据的访问方式都是统一的,这使得访问简单 & 高效
如一开始数据存储方式 采用 SQLite
数据库,后来把数据库换成 MongoDB
,也不会对上层数据ContentProvider
使用代码产生影响。 (md info supported)
参考文章 Android:关于ContentProvider的知识都在这里了!