摘要:前言屬于的四大組件之一本文全面解析了,包括原理使用方法實例講解,希望你們會喜歡。
前言
ContentProvider屬于 Android的四大組件之一
本文全面解析了 ContentProvider ,包括ContentProvider 原理、使用方法 & 實例講解,希望你們會喜歡。
目錄 1. 定義即內容提供者,是 Android 四大組件之一
2. 作用進程間 進行數據交互 & 共享,即跨進程通信
3. 原理ContentProvider的底層是采用 Android中的Binder機制
具體請看文章圖文詳解 Android Binder跨進程通信的原理
4. 具體使用關于ContentProvider的使用主要介紹以下內容:
4.1 統一資源標識符(URI)定義:Uniform Resource Identifier,即統一資源標識符
作用:唯一標識 ContentProvider & 其中的數據
> 外界進程通過 `URI` 找到對應的`ContentProvider` & 其中的數據,再進行數據操作
具體使用
`URI`分為 系統預置 & 自定義,分別對應系統內置的數據(如通訊錄、日程表等等)和自定義數據庫 > 1. 關于 系統預置`URI` 此處不作過多講解,需要的同學可自行查看 > 2. 此處主要講解 自定義`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/#4.2 MIME數據類型
作用:指定某個擴展名的文件用某種應用程序來打開
如指定`.html`文件采用`text`應用程序打開、指定`.pdf`文件采用`flash`應用程序打開
具體使用:
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.rail4.3 ContentProvider類 4.3.1 組織數據方式
ContentProvider主要以 表格的形式 組織數據
> 同時也支持文件數據,只是表格形式用得比較多
每個表格中包含多張表,每張表包含行 & 列,分別對應記錄 & 字段
> 同數據庫4.3.2 主要方法
進程間共享數據的本質是:添加、刪除、獲取 & 修改(更新)數據
所以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 類
4.4 ContentResolver類 4.1 作用統一管理不同 ContentProvider間的操作
4.2 為什么要使用通過ContentResolver類從而與ContentProvider類進行交互,而不直接訪問ContentProvider類?即通過 URI 即可操作 不同的ContentProvider 中的數據
外部進程通過 ContentResolver類 從而與ContentProvider類進行交互
答:
一般來說,一款應用要使用多個ContentProvider,若需要了解每個ContentProvider的不同實現從而再完成數據交互,操作成本高 & 難度大
所以再ContentProvider類上加多了一個 ContentResolver類對所有的ContentProvider進行統一管理。
4.3 具體使用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
4.5 ContentUris類作用:操作 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); //獲取的結果為:74.6 UriMatcher類
作用
在ContentProvider 中注冊URI
根據 URI 匹配 ContentProvider 中對應的數據表
具體使用
// 步驟1:初始化UriMatcher對象 UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); //常量UriMatcher.NO_MATCH = 不匹配任何路徑的返回碼 // 即初始化時不匹配任何東西 // 步驟2:在ContentProvider 中注冊URI(addURI()) 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); // 若URI資源路徑 = content://cn.scu.myprovider/user1 ,則返回注冊碼URI_CODE_a // 若URI資源路徑 = content://cn.scu.myprovider/user2 ,則返回注冊碼URI_CODE_b // 步驟3:根據URI 匹配 URI_CODE,從而匹配ContentProvider中相應的資源(match()) @Override public String getType(Uri uri) { Uri uri = Uri.parse(" content://cn.scu.myprovider/user1"); switch(matcher.match(uri)){ // 根據URI匹配的返回碼是URI_CODE_a // 即matcher.match(uri) == URI_CODE_a case URI_CODE_a: return tableNameUser1; // 如果根據URI匹配的返回碼是URI_CODE_a,則返回ContentProvider中的名為tableNameUser1的表 case URI_CODE_b: return tableNameUser2; // 如果根據URI匹配的返回碼是URI_CODE_b,則返回ContentProvider中的名為tableNameUser2的表 } }4.7 ContentObserver類
定義:內容觀察者
作用:觀察 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的使用已經講解完畢
5. 實例說明由于ContentProvider不僅常用于進程間通信,同時也適用于進程內通信
所以本實例會采用ContentProvider講解:
進程內通信
進程間通信
實例說明:采用的數據源是Android中的SQLite數據庫
*
5.1 進程內通信
步驟說明:
創建數據庫類
自定義 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 ,則返回注冊碼User_Code // 若URI資源路徑 = content://cn.scu.myprovider/job ,則返回注冊碼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
步驟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中的數據進行共享講解完畢。
*
5.2 進程間進行數據共享實例說明:本文需要創建2個進程,即創建兩個工程,作用如下
具體使用
進程1使用步驟如下:
創建數據庫類
自定義 ContentProvider 類
注冊 創建的 ContentProvider 類
前2個步驟同上例相同,此處不作過多描述,此處主要講解步驟3.
步驟3:注冊 創建的 ContentProvider類
AndroidManifest.xml
// 聲明本應用 可允許通信的權限 // 細分讀 & 寫權限如下,但本Demo直接采用全權限 // //
至此,進程1創建完畢,即創建ContentProvider & 數據 準備好了。
進程2步驟1:聲明可訪問的權限
AndroidManifest.xml
// 聲明本應用可允許通信的權限(全權限)// 細分讀 & 寫權限如下,但本Demo直接采用全權限 // // // 注:聲明的權限必須與進程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創建完畢
結果展示在進程展示時,需要先運行準備數據的進程1,再運行需要訪問數據的進程2
運行準備數據的進程1
在進程1中,我們準備好了一系列數據
運行需要訪問數據的進程2
在進程2中,我們先向`ContentProvider`中插入數據,再查詢數據
至此,關于ContentProvider在進程內 & 進程間的使用講解完畢。
6. 優點 6.1 安全ContentProvider為應用間的數據交互提供了一個安全的環境:允許把自己的應用數據根據需求開放給 其他應用 進行 增、刪、改、查,而不用擔心因為直接開放數據庫權限而帶來的安全問題
6.2 訪問簡單 & 高效對比于其他對外共享數據的方式,數據訪問方式會因數據存儲的方式而不同:
采用 文件方式 對外共享數據,需要進行文件操作讀寫數據;
采用 Sharedpreferences 共享數據,需要使用sharedpreferences API讀寫數據
這使得訪問數據變得復雜 & 難度大。
而采用ContentProvider方式,其 解耦了 底層數據的存儲方式,使得無論底層數據存儲采用何種方式,外界對數據的訪問方式都是統一的,這使得訪問簡單 & 高效
> 如一開始數據存儲方式 采用 `SQLite` 數據庫,后來把數據庫換成 `MongoDB`,也不會對上層數據`ContentProvider`使用代碼產生影響7. 總結
我用一張圖總結本文內容
ContentProvider的底層是采用 Android中的Binder機制,若想了解請看文章圖文詳解 Android Binder跨進程通信的原理
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70540.html
摘要:如何自學知識儲備本知識點不做重點講解對于有基礎的同學推薦看編程思想,鞏固基礎,查漏補全,了解并熟悉更多細節知識點。基礎學習基礎學習對于這些基礎的使用谷歌官網給出了很好的實例。是谷歌根據自帶的改進的。是基于谷歌內核的一個可以作為瀏覽器的視圖。 如何自學Android 1. Java知識儲備 本知識點不做重點講解: 對于有基礎的同學推薦看《Java編程思想》,鞏固基礎,查漏補全,了解...
摘要:四大組件都支持這個屬性。到目前為止,中總共有三種啟動方式。返回值方法有一個的返回值,這個返回值標識服務關閉后系統的后續操作。,啟動后的服務被殺死,不能保證系統一定會重新創建。 1. 簡介 這篇文章會從Service的一些小知識點,延伸到Android中幾種常用進程間通信方法。 2. 進程 ? ? ? ?Service是一種不提供用戶交互頁面但是可以在后臺長時間運行的組件,可以通過在An...
閱讀 1537·2021-11-24 10:17
閱讀 1031·2021-09-29 09:43
閱讀 2165·2021-09-23 11:21
閱讀 2161·2019-08-30 14:13
閱讀 1295·2019-08-29 13:58
閱讀 3157·2019-08-28 17:51
閱讀 1803·2019-08-26 13:29
閱讀 2973·2019-08-26 10:13