ContentProvider
屬於 Android
的四大組件之一ContentProvider
,包括ContentProvider
原理、使用方法 & 實例講解,但願大家會喜歡。即 內容提供者,是 Android
四大組件之一css
進程間 進行數據交互 & 共享,即跨進程通訊html
ContentProvider
的底層原理 = Android
中的Binder
機制關於ContentProvider
的使用主要介紹如下內容:java
Uniform Resource Identifier
,即統一資源標識符ContentProvider
& 其中的數據外界進程經過
URI
找到對應的ContentProvider
& 其中的數據,再進行數據操做android
URI
分爲 系統預置 & 自定義,分別對應系統內置的數據(如通信錄、日程表等等)和自定義數據庫
- 關於 系統預置
URI
此處不做過多講解,須要的同窗可自行查看- 此處主要講解 自定義
URI
// 設置URI Uri uri = Uri.parse("content://com.carson.provider/User/1") // 上述URI指向的資源是:名爲 `com.carson.provider`的`ContentProvider` 中表名 爲`User` 中的 `id`爲1的數據 // 特別注意:URI模式存在匹配通配符* & # // *:匹配任意長度的任何有效字符的字符串 // 如下的URI 表示 匹配provider的任何內容 content://com.example.app.provider/* // #:匹配任意長度的數字字符的字符串 // 如下的URI 表示 匹配provider中的table表的全部行 content://com.example.app.provider/table/#
做用:指定某個擴展名的文件用某種應用程序來打開
如指定.html
文件採用text
應用程序打開、指定.pdf
文件採用flash
應用程序打開git
具體使用:github
4.2.1 ContentProvider
根據 URI
返回MIME
類型數據庫
ContentProvider.geType(uri) ;
4.2.2 MIME
類型組成
每種MIME
類型 由2部分組成 = 類型 + 子類型安全
MIME類型是 一個 包含2部分的字符串多線程
text / html // 類型 = text、子類型 = html text/css text/xml application/pdf
4.2.3 MIME
類型形式
MIME
類型有2種形式:併發
// 形式1:單條記錄 vnd.android.cursor.item/自定義 // 形式2:多條記錄(集合) vnd.android.cursor.dir/自定義 // 注: // 1. vnd:表示父類型和子類型具備非標準的、特定的形式。 // 2. 父類型已固定好(即不能更改),只能區別是單條仍是多條記錄 // 3. 子類型可自定義
<-- 單條記錄 --> // 單個記錄的MIME類型 vnd.android.cursor.item/vnd.yourcompanyname.contenttype // 若一個Uri以下 content://com.example.transportationprovider/trains/122 // 則ContentProvider會經過ContentProvider.geType(url)返回如下MIME類型 vnd.android.cursor.item/vnd.example.rail <-- 多條記錄 --> // 多個記錄的MIME類型 vnd.android.cursor.dir/vnd.yourcompanyname.contenttype // 若一個Uri以下 content://com.example.transportationprovider/trains // 則ContentProvider會經過ContentProvider.geType(url)返回如下MIME類型 vnd.android.cursor.dir/vnd.example.rail
ContentProvider
主要以 表格的形式 組織數據同時也支持文件數據,只是表格形式用得比較多
同數據庫
ContentProvider
的核心方法也主要是上述4個做用<-- 4個核心方法 --> public Uri insert(Uri uri, ContentValues values) // 外部進程向 ContentProvider 中添加數據 public int delete(Uri uri, String selection, String[] selectionArgs) // 外部進程 刪除 ContentProvider 中的數據 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) // 外部進程更新 ContentProvider 中的數據 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) // 外部應用 獲取 ContentProvider 中的數據 // 注: // 1. 上述4個方法由外部進程回調,並運行在ContentProvider進程的Binder線程池中(不是主線程) // 2. 存在多線程併發訪問,須要實現線程同步 // a. 若ContentProvider的數據存儲方式是使用SQLite & 一個,則不須要,由於SQLite內部實現好了線程同步,如果多個SQLite則須要,由於SQL對象之間沒法進行線程同步 // b. 若ContentProvider的數據存儲方式是內存,則須要本身實現線程同步 <-- 2個其餘方法 --> public boolean onCreate() // ContentProvider建立後 或 打開系統後其它進程第一次訪問該ContentProvider時 由系統進行調用 // 注:運行在ContentProvider進程的主線程,故不能作耗時操做 public String getType(Uri uri) // 獲得數據類型,即返回當前 Url 所表明數據的MIME類型
Android
爲常見的數據(如通信錄、日程表等)提供了內置了默認的ContentProvider
ContentProvider
,但上述6個方法必須重寫本文主要講解自定義
ContentProvider
ContentProvider
類並不會直接與外部進程交互,而是經過ContentResolver
類統一管理不一樣 ContentProvider
間的操做
- 即經過
URI
便可操做 不一樣的ContentProvider
中的數據- 外部進程經過
ContentResolver
類 從而與ContentProvider
類進行交互
ContentResolver
類從而與ContentProvider
類進行交互,而不直接訪問ContentProvider
類?答:
ContentProvider
,若須要瞭解每一個ContentProvider
的不一樣實現從而再完成數據交互,操做成本高 & 難度大ContentProvider
類上加多了一個 ContentResolver
類對全部的ContentProvider
進行統一管理。ContentResolver
類提供了與ContentProvider
類相同名字 & 做用的4個方法
// 外部進程向 ContentProvider 中添加數據 public Uri insert(Uri uri, ContentValues values) // 外部進程 刪除 ContentProvider 中的數據 public int delete(Uri uri, String selection, String[] selectionArgs) // 外部進程更新 ContentProvider 中的數據 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) // 外部應用 獲取 ContentProvider 中的數據 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
// 使用ContentResolver前,須要先獲取ContentResolver // 可經過在全部繼承Context的類中 經過調用getContentResolver()來得到ContentResolver ContentResolver resolver = getContentResolver(); // 設置ContentProvider的URI Uri uri = Uri.parse("content://cn.scu.myprovider/user"); // 根據URI 操做 ContentProvider中的數據 // 此處是獲取ContentProvider中 user表的全部記錄 Cursor cursor = resolver.query(uri, null, null, null, "userid desc");
Android
提供了3個用於輔助ContentProvide
的工具類:
ContentUris
UriMatcher
ContentObserver
URI
withAppendedId()
&parseId()
// withAppendedId()做用:向URI追加一個id Uri uri = Uri.parse("content://cn.scu.myprovider/user") Uri resultUri = ContentUris.withAppendedId(uri, 7); // 最終生成後的Uri爲:content://cn.scu.myprovider/user/7 // parseId()做用:從URL中獲取ID Uri uri = Uri.parse("content://cn.scu.myprovider/user/7") long personid = ContentUris.parseId(uri); //獲取的結果爲:7
做用
ContentProvider
中註冊URI
URI
匹配 ContentProvider
中對應的數據表具體使用
Uri
引發 ContentProvider
中的數據變化 & 通知外界(即訪問該數據訪問者)當
ContentProvider
中的數據發生變化(增、刪 & 改)時,就會觸發該ContentObserver
類
// 步驟1:註冊內容觀察者ContentObserver getContentResolver().registerContentObserver(uri); // 經過ContentResolver類進行註冊,並指定須要觀察的URI // 步驟2:當該URI的ContentProvider數據發生變化時,通知外界(即訪問該ContentProvider數據的訪問者) public class UserContentProvider extends ContentProvider { public Uri insert(Uri uri, ContentValues values) { db.insert("user", "userid", values); getContext().getContentResolver().notifyChange(uri, null); // 通知訪問者 } } // 步驟3:解除觀察者 getContentResolver().unregisterContentObserver(uri); // 一樣須要經過ContentResolver類進行解除
至此,關於ContentProvider
的使用已經講解完畢
ContentProvider
不只經常使用於進程間通訊,同時也適用於進程內通訊ContentProvider
講解:
Android
中的SQLite
數據庫步驟說明:
ContentProvider
類ContentProvider
類ContentProvider
的數據具體使用
步驟1:建立數據庫類
關於數據庫操做請看文章:Android:SQLlite數據庫操做最詳細解析
DBHelper.java
public class DBHelper extends SQLiteOpenHelper { // 數據庫名 private static final String DATABASE_NAME = "finch.db"; // 表名 public static final String USER_TABLE_NAME = "user"; public static final String JOB_TABLE_NAME = "job"; private static final int DATABASE_VERSION = 1; //數據庫版本號 public DBHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { // 建立兩個表格:用戶表 和職業表 db.execSQL("CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " name TEXT)"); db.execSQL("CREATE TABLE IF NOT EXISTS " + JOB_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " job TEXT)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
步驟2:自定義 ContentProvider 類
public class MyProvider extends ContentProvider { private Context mContext; DBHelper mDbHelper = null; SQLiteDatabase db = null; public static final String AUTOHORITY = "cn.scu.myprovider"; // 設置ContentProvider的惟一標識 public static final int User_Code = 1; public static final int Job_Code = 2; // UriMatcher類使用:在ContentProvider 中註冊URI private static final UriMatcher mMatcher; static{ mMatcher = new UriMatcher(UriMatcher.NO_MATCH); // 初始化 mMatcher.addURI(AUTOHORITY,"user", User_Code); mMatcher.addURI(AUTOHORITY, "job", Job_Code); // 若URI資源路徑 = content://cn.scu.myprovider/user ,則返回註冊id User_Code // 若URI資源路徑 = content://cn.scu.myprovider/job ,則返回註冊id Job_Code } // 如下是ContentProvider的6個方法 /** * 初始化ContentProvider */ @Override public boolean onCreate() { mContext = getContext(); // 在ContentProvider建立時對數據庫進行初始化 // 運行在主線程,故不能作耗時操做,此處僅做展現 mDbHelper = new DBHelper(getContext()); db = mDbHelper.getWritableDatabase(); // 初始化兩個表的數據(先清空兩個表,再各加入一個記錄) db.execSQL("delete from user"); db.execSQL("insert into user values(1,'Carson');"); db.execSQL("insert into user values(2,'Kobe');"); db.execSQL("delete from job"); db.execSQL("insert into job values(1,'Android');"); db.execSQL("insert into job values(2,'iOS');"); return true; } /** * 添加數據 */ @Override public Uri insert(Uri uri, ContentValues values) { // 根據URI匹配 URI_CODE,從而匹配ContentProvider中相應的表名 // 該方法在最下面 String table = getTableName(uri); // 向該表添加數據 db.insert(table, null, values); // 當該URI的ContentProvider數據發生變化時,通知外界(即訪問該ContentProvider數據的訪問者) mContext.getContentResolver().notifyChange(uri, null); // // 經過ContentUris類從URL中獲取ID // long personid = ContentUris.parseId(uri); // System.out.println(personid); return uri; } /** * 查詢數據 */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 根據URI匹配 URI_CODE,從而匹配ContentProvider中相應的表名 // 該方法在最下面 String table = getTableName(uri); // // 經過ContentUris類從URL中獲取ID // long personid = ContentUris.parseId(uri); // System.out.println(personid); // 查詢數據 return db.query(table,projection,selection,selectionArgs,null,null,sortOrder,null); } /** * 更新數據 */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // 因爲不展現,此處不做展開 return 0; } /** * 刪除數據 */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // 因爲不展現,此處不做展開 return 0; } @Override public String getType(Uri uri) { // 因爲不展現,此處不做展開 return null; } /** * 根據URI匹配 URI_CODE,從而匹配ContentProvider中相應的表名 */ private String getTableName(Uri uri){ String tableName = null; switch (mMatcher.match(uri)) { case User_Code: tableName = DBHelper.USER_TABLE_NAME; break; case Job_Code: tableName = DBHelper.JOB_TABLE_NAME; break; } return tableName; } }
步驟3:註冊 建立的 ContentProvider類
AndroidManifest.xml
<provider android:name="MyProvider" android:authorities="cn.scu.myprovider" />
步驟4:進程內訪問 ContentProvider中的數據
MainActivity.java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /** * 對user表進行操做 */ // 設置URI Uri uri_user = Uri.parse("content://cn.scu.myprovider/user"); // 插入表中數據 ContentValues values = new ContentValues(); values.put("_id", 3); values.put("name", "Iverson"); // 獲取ContentResolver ContentResolver resolver = getContentResolver(); // 經過ContentResolver 根據URI 向ContentProvider中插入數據 resolver.insert(uri_user,values); // 經過ContentResolver 向ContentProvider中查詢數據 Cursor cursor = resolver.query(uri_user, new String[]{"_id","name"}, null, null, null); while (cursor.moveToNext()){ System.out.println("query book:" + cursor.getInt(0) +" "+ cursor.getString(1)); // 將表中數據所有輸出 } cursor.close(); // 關閉遊標 /** * 對job表進行操做 */ // 和上述相似,只是URI須要更改,從而匹配不一樣的URI CODE,從而找到不一樣的數據資源 Uri uri_job = Uri.parse("content://cn.scu.myprovider/job"); // 插入表中數據 ContentValues values2 = new ContentValues(); values2.put("_id", 3); values2.put("job", "NBA Player"); // 獲取ContentResolver ContentResolver resolver2 = getContentResolver(); // 經過ContentResolver 根據URI 向ContentProvider中插入數據 resolver2.insert(uri_job,values2); // 經過ContentResolver 向ContentProvider中查詢數據 Cursor cursor2 = resolver2.query(uri_job, new String[]{"_id","job"}, null, null, null); while (cursor2.moveToNext()){ System.out.println("query job:" + cursor2.getInt(0) +" "+ cursor2.getString(1)); // 將表中數據所有輸出 } cursor2.close(); // 關閉遊標 } }
Carson-Ho Github地址:ContentProvider
至此,進程內對ContentProvider
中的數據進行共享講解完畢。
使用步驟以下:
ContentProvider
類ContentProvider
類前2個步驟同上例相同,此處不做過多描述,此處主要講解步驟3.
步驟3:註冊 建立的 ContentProvider類
AndroidManifest.xml
<provider android:name="MyProvider" android:authorities="scut.carson_ho.myprovider" // 聲明外界進程可訪問該Provider的權限(讀 & 寫) android:permission="scut.carson_ho.PROVIDER" // 權限可細分爲讀 & 寫的權限 // 外界須要聲明一樣的讀 & 寫的權限纔可進行相應操做,不然會報錯 // android:readPermisson = "scut.carson_ho.Read" // android:writePermisson = "scut.carson_ho.Write" // 設置此provider是否能夠被其餘進程使用 android:exported="true" /> // 聲明本應用 可容許通訊的權限 <permission android:name="scut.carson_ho.Read" android:protectionLevel="normal"/> // 細分讀 & 寫權限以下,但本Demo直接採用全權限 // <permission android:name="scut.carson_ho.Write" android:protectionLevel="normal"/> // <permission android:name="scut.carson_ho.PROVIDER" android:protectionLevel="normal"/>
至此,進程1建立完畢,即建立ContentProvider
& 數據 準備好了。
Carson-Ho Github地址:ContentProvider1
步驟1:聲明可訪問的權限
AndroidManifest.xml
// 聲明本應用可容許通訊的權限(全權限) <uses-permission android:name="scut.carson_ho.PROVIDER"/> // 細分讀 & 寫權限以下,但本Demo直接採用全權限 // <uses-permission android:name="scut.carson_ho.Read"/> // <uses-permission android:name="scut.carson_ho.Write"/> // 注:聲明的權限必須與進程1中設置的權限對應
步驟2:訪問 ContentProvider的類
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /** * 對user表進行操做 */ // 設置URI Uri uri_user = Uri.parse("content://scut.carson_ho.myprovider/user"); // 插入表中數據 ContentValues values = new ContentValues(); values.put("_id", 4); values.put("name", "Jordan"); // 獲取ContentResolver ContentResolver resolver = getContentResolver(); // 經過ContentResolver 根據URI 向ContentProvider中插入數據 resolver.insert(uri_user,values); // 經過ContentResolver 向ContentProvider中查詢數據 Cursor cursor = resolver.query(uri_user, new String[]{"_id","name"}, null, null, null); while (cursor.moveToNext()){ System.out.println("query book:" + cursor.getInt(0) +" "+ cursor.getString(1)); // 將表中數據所有輸出 } cursor.close(); // 關閉遊標 /** * 對job表進行操做 */ // 和上述相似,只是URI須要更改,從而匹配不一樣的URI CODE,從而找到不一樣的數據資源 Uri uri_job = Uri.parse("content://scut.carson_ho.myprovider/job"); // 插入表中數據 ContentValues values2 = new ContentValues(); values2.put("_id", 4); values2.put("job", "NBA Player"); // 獲取ContentResolver ContentResolver resolver2 = getContentResolver(); // 經過ContentResolver 根據URI 向ContentProvider中插入數據 resolver2.insert(uri_job,values2); // 經過ContentResolver 向ContentProvider中查詢數據 Cursor cursor2 = resolver2.query(uri_job, new String[]{"_id","job"}, null, null, null); while (cursor2.moveToNext()){ System.out.println("query job:" + cursor2.getInt(0) +" "+ cursor2.getString(1)); // 將表中數據所有輸出 } cursor2.close(); // 關閉遊標 } }
至此,訪問ContentProvider
數據的進程2建立完畢
Carson-Ho Github地址:ContentProvider2
在進程展現時,須要先運行準備數據的進程1,再運行須要訪問數據的進程2
運行準備數據的進程1
在進程1中,咱們準備好了一系列數據
運行須要訪問數據的進程2
在進程2中,咱們先向ContentProvider
中插入數據,再查詢數據
至此,關於ContentProvider
在進程內 & 進程間的使用講解完畢。
ContentProvider
爲應用間的數據交互提供了一個安全的環境:容許把本身的應用數據根據需求開放給 其餘應用 進行 增、刪、改、查,而不用擔憂由於直接開放數據庫權限而帶來的安全問題
對比於其餘對外共享數據的方式,數據訪問方式會因數據存儲的方式而不一樣:
Sharedpreferences
共享數據,須要使用 sharedpreferences API
讀寫數據這使得訪問數據變得複雜 & 難度大。
ContentProvider
方式,其 解耦了 底層數據的存儲方式,使得不管底層數據存儲採用何種方式,外界對數據的訪問方式都是統一的,這使得訪問簡單 & 高效如一開始數據存儲方式 採用
SQLite
數據庫,後來把數據庫換成MongoDB
,也不會對上層數據ContentProvider
使用代碼產生影響
ContentProvider
的底層是採用 Android
中的Binder
機制,若想了解請看文章圖文詳解 Android Binder跨進程通訊的原理
下一篇文章我將對講解Android
的相關知識,有興趣能夠繼續關注Carson_Ho的安卓開發筆記