1. 程式人生 > >Android程序間通訊 - ContentProvider內容提供者

Android程序間通訊 - ContentProvider內容提供者

簡介

ContentProvider主要用於在不同的應用程式間實現資料共享的功能,允許一個程式訪問另外一個程式中的資料,還能保證資料訪問的安全性。

是Android跨程序實現資料共享的標準方式。

這裡寫圖片描述

  • ContentProvider相當於程序間的搬運工,對資料一系列的操作(CRUD)
  • 資料來源可以是資料庫(SQLite等)、檔案、xml、網路等等。

在瞭解ContentProvider之前,需要對URI和執行時許可權進行回顧下,在ContentProvider中涉及到這兩個知識點

統一資源識別符號(URI)

URI:是給ContentProvider中的資料提供唯一的識別符號。

URI分為自定義和系統內建兩種型別,系統內建比如通訊錄、日程表、相簿等等。

自定義URI

自定義URI的標準命名:
這裡寫圖片描述

  • 主題名(Schema):ContentProvider的URI字首,系統規定的。
  • 授權資訊(Authority):ContentProvider的唯一識別符號
  • 表名(Path):ContentProvider指向資料庫中的某個表名。
  • 記錄(ID):表中的某個記錄,如果無指定,預設返回的是全部記錄。

注意:
自定義URI格式末尾一般有兩種結束方式
一種是以表名(Path)即路徑為結束點,該方式表示訪問表中所有的的資料
如:content://com.hzw.progress/Book
另外一種是記錄(ID)為結束點,則表示只訪問有相應ID屬性的資料
如:content://com.hzw.progress/Book/1

此外某些時候我們希望訪問任意一張表中所有資料或者某張表的任意一行資料,此時可以使用萬用字元的方式。

//  *  匹配任意長度的任何有效字元的字串 
// 表示能訪問com.hzw.progress下所有表資料
content://com.hzw.progress/*

//  #  匹配任意長度的數字字元的字串
//表示能訪問com.hzw.progress下的表名為Book中任意一行資料
content://com.hzw.progress/Book/#

自定義的URI的使用方法:

//通過Uri解析指定的Uri字串得到URi物件
Uri uri = Uri.parse("content://com.hzw.progress/Book/1"
);

系統內建URI

關於系統內建常用的URI,可以參考這篇文章Android許可權Uri用法
在使用內建URI時,需注意在Mainfest檔案中宣告相應的許可權,否則會訪問失敗甚至程式崩潰現象。具體的所需許可權可以搜尋log日誌的“Permission Denial”得知。

執行時許可權

在Android6.0開始系統加入執行時許可權機制,就是在應用安裝使用中需要使用者同意一些系統許可權,若拒絕授權,則無法使用該功能。通常這些許可權是涉及到使用者的安全和隱私,為此稱之為危險許可權,一些常用的危險危險:
這裡寫圖片描述

下面通過調起手機系統撥號為例,說明許可權授權的流程
在未手動授權的情況:

  public void onClick(View view) {
        switch (view.getId()){
            case R.id.but_call:
                call();
                break;
        }
    }

private void call() {
        try {
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        }catch (SecurityException e){
            e.printStackTrace();
        }
    }

//在Mainfest中宣告打電話許可權
<uses-permission android:name="android.permission.CALL_PHONE"/>

在Android5.0之前只要在Mainfest宣告許可權就可以訪問成功,然而在Android6.0及以上環境,點選後會發現程式沒有任何反應,說明調起撥號介面失敗,我們可以通過日誌發現具體錯誤資訊:
這裡寫圖片描述

通過Permission Denial搜尋日誌也可得知具體所需許可權
這裡寫圖片描述

接下來修復處理這個執行時許可權問題,實現調起撥號介面。
基本步驟:

  1. 獲取所需許可權的授權許可值,通過checkSelfPermission可得。具體返回值有兩種:
    1. PERMISSION_GRANTED:表示已經得到相關許可權
    2. PERMISSION_DENIED:未得到許可權許可
  2. 通過授權許可值,檢查是否有授權(與PackageManager.PERMISSION_GRANTED進行比較)
    1. 未授權:則申請授權,可用requestPermissions申請
    2. 已有授權:直接呼叫業務方法。
  3. 重寫onRequestPermissionsResult方法,在其根據grantResults陣列的元素值和請求code判斷是否授權成功。
    1. 授權成功:可直接呼叫業務方法
    2. 授權失敗或拒絕授權:下次再次申請授權時需前往應用資訊介面手動開啟需求
    private static final int CALL_CODE=100;

    public void onClick(View view) {
        switch (view.getId()){
            case R.id.but_call:
                //獲取CALL_PHONE許可權許可值
                int selfPermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE);
                //檢查是否授權  
                if (selfPermission !=PackageManager.PERMISSION_GRANTED){
                      //沒有授權情況下,則申請CALL_CODE許可權
                    ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CALL_PHONE},CALL_CODE);
                }else {
                     //授權成功的情況下,直接呼叫call方法
                    call();
                }
                break;

    }


   @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //根據grantResults陣列的元素值和請求code判斷是否授權成功
        if (grantResults.length>0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            switch (requestCode){
                case CALL_CODE:
                    call();
                    break;
            }

        }else {

            Log.i(TAG, "onRequestPermissionsResult: "+"許可權授權失敗,需要前往應用資訊頁面設定");
            showPermissionFailureDialog();
        }

    }
    //授權失敗顯示的dialog
    private void showPermissionFailureDialog(){
        new AlertDialog.Builder(this)
                .setMessage("您已拒絕了相關許可權,將無法使用該功能,是否前往應用資訊頁面手動開啟該許可權")
                .setNegativeButton(android.R.string.no,null)
                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Intent intent=getAppDetailSettingIntent(ContentProviderActivity.this);
                        startActivity(intent);
                    }
                }).create().show();
    }

 /**
     * 獲取應用詳情頁面intent
     *
     * @return
     */
    public static Intent getAppDetailSettingIntent(Context context) {
        Intent localIntent = new Intent();
        localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
        localIntent.setData(Uri.fromParts("package", context.getPackageName(), null));
        return localIntent;

    }

以上程式碼執行後初次點選,會彈出一個授權提示對話方塊選擇
這裡寫圖片描述
接著點選允許,可直接撥號
這裡寫圖片描述
如果點選失敗,則會輸出失敗的log日誌
這裡寫圖片描述

以上執行時申請授權的基本流程。

ContentProvider核心方法

程序間共享資料的本質就是CRUD(增刪改查),而ContentProvider正好也提供對應的方法。

public class BookProvider extends ContentProvider{

    private static final String TAG = "BookProvider";

    /**
     * ContentProvider初始化被呼叫
     * 一般用於建立資料庫或者升級等操作,只有在ContentResolver訪問的時候,才會觸發onCreate方法
     * 此方法是主執行緒執行,不能做耗時操作
     * @return true:ContentProvider初始化成功,false則失敗
     */
    @Override
    public boolean onCreate() {
        Log.d(TAG, "onCreate: 當前執行緒"+Thread.currentThread());
        return false;
    }


    /**
     * 插入一條新資料(新增資料)--增
     * @param uri 根據uri插入到具體哪張資料表
     * @param values  ContentValues底層是key-value鍵值對結構,使用HashMap實現的
     *                key:表示列名,value:表示行名,如果value為空,在表中則是空行,無內容
     * @return 新增成功後,返回這條新資料的uri。
     */
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.d(TAG, "insert: 當前執行緒"+Thread.currentThread());
        return null;
    }


    /**
     * 刪除資料 -- 刪
     * @param uri  根據uri刪除哪張表的資料
     * @param selection 根據條件刪除具體哪行資料
     * @param selectionArgs 與selection類似
     * @return  返回被刪除的行數。
     */
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d(TAG, "delete: 當前執行緒"+Thread.currentThread());
        return 0;
    }

    /**
     * 更新資料 -- 改
     * @param uri 根據Uri修改具體哪張表的資料
     * @param values 與insert的ContentValues一樣,key是列名,若傳入的value為空,則會刪除原來的資料置空。
     * @param selection 選擇符合該條件的行資料進行修改
     * @param selectionArgs 與selection類似
     * @return 更新的行數
     */
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d(TAG, "update: 當前執行緒"+Thread.currentThread());
        return 0;
    }


    /**
     * 查詢資料 -- 查
     * @param uri 根據uri查詢具體哪張資料表
     * @param projection 確定查詢表中哪些列 ,傳null則返回所有的列
     * @param selection 確定查詢哪行,傳null這返回所有的行
     * @param selectionArgs 與selection類似
     * @param sortOrder 用於對查詢結果進行排序,傳null則使用預設的排序方式,也可以是無序的。
     * @return 查詢的返回值,是個Cursor物件,在取完資料需進行關閉。否則會記憶體洩漏
     */
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.d(TAG, "query: 當前執行緒"+Thread.currentThread());
        return null;
    }


    /**
     * 返回指定內容URL的MIME型別
     * @param uri 具體Url
     * @return MIME型別 比如圖片、視訊等等,可直接返回null
     */
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }
}

MIME型別命名規範

  • 必須以vnd為開頭
  • 如果URI以路徑為結尾,則後接android.cursor.dir/,如果URi以記錄id為結尾,則後接android.cursor.item/
  • 最後接上vnd.<授權資訊Authority>.<路徑Path>

示例:

//URi以路徑Path為結尾:content://com.hzw.progress/Book
vnd.android.cursor.dir/vnd.com.hzw.progress.Book

//URi以id為結尾:content://com.hzw.progress/Book/1
vnd.android.cursor.dir/vnd.com.hzw.progress.1

注意:
1、onCreate方法是在主執行緒中執行,不能做耗時操作
2、query、insert、delete、update四個核心方法是執行在Binder執行緒中,並不是主執行緒
3、當執行緒併發時,需在核心方法中處理同步問題。

在Mainfest檔案中宣告註冊ContentProvider

       <provider
            android:authorities="com.hzw.progress" 
            android:name=".provider.BookProvider"  
            android:permission="com.hzw.PROVIDER"  //包括全部許可權
            android:process=":provider"/>
  • authorities:ContetProvider唯一標識
  • permission:宣告訪問ContentProvider需要的許可權,一般是另一外應用需要訪問該ContentProvider需要宣告該許可權,否則外界應用會異常終止。
  • process:開啟一個新程序

關於許可權,可分別宣告讀寫的所需不同的許可權
android:writePermission=”” 寫許可權
android:readPermission=”” 讀許可權
android:permission=”” 全部許可權

外部(Activity)呼叫

 Uri uri = Uri.parse("content://com.hzw.progress");
 getContentResolver().query(uri, null, null, null, null);
 getContentResolver().query(uri, null, null, null, null);
getContentResolver().query(uri, null, null, null, null);

這裡寫圖片描述

ContentProvider基本使用

ContentProvider的使用主要從系統內建、自定義這兩方面深入瞭解

  • 系統內建的ContentProvider,比如Android的通訊錄、日程表、相簿等等,很多。
  • 自定義ContentProvider,分為兩種程序間和程序內,必須重寫上面的6個方法。

在使用ContentProvider之前,必須對ContentResolver這個類進行了解下,因為ContentProvider 類並不會直接與外部程序互動,而是通過 ContentResolver 類

ContentResolver內容解析器

ContentResolver是個抽象類,具體例項是Content中就已經初始化了,也就是說應用一啟動就初始化了。

public abstract class ContentResolver {
}

這裡寫圖片描述

ContentResolver的作用:統一管理不同的ContentProvider間的操作,ContentResolver提供了與ContentProvider一樣的增刪改查方法。

// 外部程序向 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)

系統內建ContentProvider的使用

下面以獲取手機通訊錄資訊為例,先得到ContentResolver物件,緊接著呼叫其query方法,注意需要READ_CONTACTS授權

public class ContentProviderActivity extends AppCompatActivity{

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_content_provider);
    }

    private static final int CALL_CODE=100;
    private static final int CONTACTS_CODE=200;

    public void onClick(View view) {
        switch (view.getId()){
            case R.id.but_contacts:
                //檢查是否授權成功
                int checkSelfPermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS);
                if (checkSelfPermission!=PackageManager.PERMISSION_GRANTED){
                    ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},CONTACTS_CODE);
                }else {
                    //讀取手機聯絡人
                    readContacts();
                }
                break;
        }
    }

    //讀取手機聯絡人
    private void readContacts(){
        //通過ContentResolver物件,查詢所有的聯絡人
        Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
       try {
           if (cursor!=null){
               //遍歷Cursor ,讀取所有的資訊
               while (cursor.moveToNext()){
                   //聯絡人姓名
                   String contactName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                   //聯絡人號碼
                   String contactPhone = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));

                   Log.i(TAG, "聯絡人姓名:"+contactName+"  號碼:"+contactPhone);
               }
           }
       }catch (SecurityException e){
           e.printStackTrace();
       }finally {
           //關閉游標cursor,避免記憶體洩漏
           if (cursor!=null){
               cursor.close();
           }
       }


    }

 //關於許可權的程式碼和上面是一樣的,這裡就不貼下
 ……………………

}

輸出結果:
這裡寫圖片描述

自定義ContentProvider的使用

所謂自定義ContentProvider就是繼承於ContentProvider類,重寫核心方法,一般用於應用間的資料共享,就是我們常說的使用ContentProvider進行程序間通訊,其實訪問系統內建的ContentProvider也屬於程序間訪問,一般應用內很少使用ContentProvider進行資料共享,在應用內進行資料共享有很多其他更便捷的方式,比如file檔案、sp、db等等,如果在應用內使用ContentProvider資料共享感覺有點大題小做。

基本使用步驟,可大體分以下三個步驟:

  1. 設計資料儲存方式
    1. 可選擇檔案和資料庫進行儲存,一般情況下會用資料庫。
  2. 建立ContentProvider子類,實現核心方法
    1. 使用UriMatcher物件統一管理不同的Uri
    2. 在onCreate方法初始化一些資料,注意不要耗時操作,可開啟一個子執行緒執行。另外,只有外界呼叫了getContentResolver方法,才會觸發onCreate的初始化。
    3. 當資料發生了變化(比如增刪改操作)時,需通過ContentResolver的notifyChange方法通知外界訪問者ContentProvider中的資料已經改變了。
  3. 外界通過ContentResolver和Uri對ContentProvider操作

設計資料儲存

這裡我們使用SQLite進行資料儲存,實現SQLiteOpenHelper幫助類的子類

public class BookDbHelper extends SQLiteOpenHelper{

    //資料庫名稱
    private static final String DATABASE_NAME="Book.db";
    //表名稱
    public static final String USER_TABLE_NAME="user";
    public static final String BOOK_TABLE_NAME="book";
    //資料庫版本號
    private static final int DATABASE_VERSION=1;



    public BookDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // 建立兩個表格:user表 和book表
        //user表是個3列的結構,有id、name、sex三個屬性值
        db.execSQL("create table if not exists " + USER_TABLE_NAME + "(_id integer primary key autoincrement," + "name TEXT,"+"sex TEXT)");
       //book表是個2列結構,有id,book兩個屬性值
        db.execSQL("create table if not exists " + BOOK_TABLE_NAME + "(_id integer primary key autoincrement," + "book TEXT)");

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

建立ContentProvider子類

public class BookProvider extends ContentProvider{

    private static final String TAG = "BookProvider";

    //1、ContentProvider唯一標識
    public static final String AUTHORITY="com.hzw.progress";
    //2、分別為book和user表指定Uri
    public static final Uri BOOK_CONTENT_URI=Uri.parse("content://"+AUTHORITY+"/book");
    public static final Uri USER_CONTENT_URI=Uri.parse("content://"+AUTHORITY+"/user");

    //3、建立UriMatcher物件,用於管理Uri
    private static final UriMatcher uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
    private static final int BOOK_URI_CODE=0;
    private static final int USER_URI_CODE=1;
    //4、初始化UriMatcher元素,將book表和User表的Uri新增到UriMatcher中,可根據code值取對應的表名
    static {
        uriMatcher.addURI(AUTHORITY,"book",BOOK_URI_CODE);
        uriMatcher.addURI(AUTHORITY,"user",USER_URI_CODE);
    }


    // 5、根據Uri在UriMatcher中匹配得到對應的表名

    private String getTableName(Uri uri){
        String tableName="";
        switch (uriMatcher.match(uri)){
            case BOOK_URI_CODE:
                tableName=BookDbHelper.BOOK_TABLE_NAME;
                break;
            case USER_URI_CODE:
                tableName=BookDbHelper.USER_TABLE_NAME;
                break;
        }

        return tableName;
    }

    private BookDbHelper mDbHelper;
    private SQLiteDatabase mWritableDatabase;
    private Context mContext;

    /**
     * ContentProvider初始化被呼叫
     * 一般用於建立資料庫或者升級等操作,只有在ContentResolver訪問的時候,才會觸發onCreate方法
     * 此方法是主執行緒執行,不能做耗時操作
     * @return true:ContentProvider初始化成功,false則失敗
     */
    @Override
    public boolean onCreate() {
        Log.d(TAG, "onCreate: 當前執行緒:"+Thread.currentThread().getName());
        mContext = getContext();

        //6、初始化資料庫資料,不能在主執行緒做耗時操作
        //建立資料庫幫助類,通過BookDbHelper得到SQLite資料庫寫入例項
        mWritableDatabase = new BookDbHelper(mContext).getWritableDatabase();
        new Thread(new Runnable() {
            @Override
            public void run() {
                initProviderData();
            }
        }).start();


        return true;
    }

    private void initProviderData() {

        //先刪除清空Book、User表的資料
        mWritableDatabase.execSQL("delete from "+BookDbHelper.BOOK_TABLE_NAME);
        mWritableDatabase.execSQL("delete from "+BookDbHelper.USER_TABLE_NAME);
        //接著在資料庫中新增資料,分別給book、user表插入資料
        mWritableDatabase.execSQL("insert into book values(2,'Android開發藝術探索');");
        mWritableDatabase.execSQL("insert into book values(3,'Android進階之光');");
        mWritableDatabase.execSQL("insert into user values(3,'張衛健','男');");
        mWritableDatabase.execSQL("insert into user values(4,'微微','女');");
    }


    /**
     * 查詢資料 -- 查
     * @param uri 根據uri查詢具體哪張資料表
     * @param projection 確定查詢表中哪些列 ,傳null則返回所有的列
     * @param selection 確定查詢哪行,傳null這返回所有的行
     * @param selectionArgs 與selection類似
     * @param sortOrder 用於對查詢結果進行排序,傳null則使用預設的排序方式,也可以是無序的。
     * @return 查詢的返回值,是個Cursor物件,在取完資料需進行關閉。否則會記憶體洩漏
     */
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.d(TAG, "query: 當前執行緒:"+Thread.currentThread().getName());
        String tableName = getTableName(uri);
        return mWritableDatabase.query(tableName, projection, selection, selectionArgs,null,null, sortOrder);
    }


    /**
     * 插入一條新資料(新增資料)--增
     * @param uri 根據uri插入到具體哪張資料表
     * @param values  ContentValues底層是key-value鍵值對結構,使用HashMap實現的
     *                key:表示列名,value:表示行名,如果value為空,在表中則是空行,無內容
     * @return 新增成功後,返回這條新資料的uri。
     */
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.d(TAG, "insert: 當前執行緒:"+Thread.currentThread().getName());
        // a、通過Uri插入具體表中
        String tableName = getTableName(uri);
        mWritableDatabase.insert(tableName, null, values);
        //b、資料發生改變,通知外部呼叫者
        mContext.getContentResolver().notifyChange(uri,null);
        return uri;
    }


    /**
     * 刪除資料 -- 刪
     * @param uri  根據uri刪除哪張表的資料
     * @param selection 根據條件刪除具體哪行資料
     * @param selectionArgs 與selection類似
     * @return  返回被刪除的行數。
     */
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d(TAG, "delete: 當前執行緒"+Thread.currentThread().getName());
        String tableName = getTableName(uri);
        int count = mWritableDatabase.delete(tableName, selection, selectionArgs);
        //如果大於0,則刪除成功
        if (count>0){
            mContext.getContentResolver().notifyChange(uri,null);
        }
        return count;
    }

    /**
     * 更新資料 -- 改
     * @param uri 根據Uri修改具體哪張表的資料
     * @param values 與insert的ContentValues一樣,key是列名,若傳入的value為空,則會刪除原來的資料置空。
     * @param selection 選擇符合該條件的行資料進行修改
     * @param selectionArgs 與selection類似
     * @return 更新的行數
     */
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d(TAG, "update: 當前執行緒:"+Thread.currentThread().getName());
        String tableName = getTableName(uri);
        int rows = mWritableDatabase.update(tableName, values, selection, selectionArgs);
        if (rows>0){
            mContext.getContentResolver().notifyChange(uri,null);
        }
        return 0;
    }



    /**
     * 返回指定內容URL的MIME型別
     * @param uri 具體Url
     * @return MIME型別 比如圖片、視訊等等 ,可直接返回null
     */
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }
}

緊接著需要另外一個程序的Mainfest檔案宣告註冊ContentProvider,並設定permission和authorities屬性值。

 <!--自定義訪問ContentProvider的許可權-->
    <permission android:name="com.hzw.PROVIDER"/>
    <!--在程序中註冊該許可權-->
    <uses-permission android:name="com.hzw.PROVIDER"/>

****

<provider
            android:name=".provider.BookProvider"
            android:authorities="com.hzw.progress"
            android:permission="com.hzw.PROVIDER"
            android:exported="true"
            android:process=":provider"/>

通過 ContentResolver 和 URI 進行增刪改查


                //檢查是否授權
                int checkSelfPermission1 = ActivityCompat.checkSelfPermission(this, com.hzw.progress.Manifest.permission.PROVIDER);
                if (checkSelfPermission1!= PackageManager.PERMISSION_GRANTED){
                    Log.d(TAG, "當前程序沒有獲取BookProvider的許可權,不可進行訪問");
                    return;
                }

                Uri bookUri = Uri.parse("content://com.hzw.progress/book");
                //在book表新增一條資料
                ContentValues values = new ContentValues();
                values.put("_id","6");
                values.put("book","Android高階進階");
                getContentResolver().insert(bookUri,values);
                //查詢book表所有資料
                Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id","book"}, null, null, null);
                if (bookCursor!=null){
                    while (bookCursor.moveToNext()){
                        int bookId = bookCursor.getInt(0);
                        String bookName = bookCursor.getString(1);
                        Log.d(TAG, "bookId: "+bookId+",bookName:"+bookName);
                    }
                }
              if (bookCursor!=null){
                  bookCursor.close();
              }

                //查詢user表所有資料
                Uri userUri = Uri.parse("content://com.hzw.progress/user");
                Cursor userCursor = getContentResolver().query(userUri, new String[]{"_id","name","sex"}, null, null, null);
                if (userCursor!=null){
                    while (userCursor.moveToNext()){
                        int userId = userCursor.getInt(0);
                        String userName = userCursor.getString(1);
                        String sex = userCursor.getString(2);
                        Log.d(TAG, "userId: "+userId+",userName:"+userName+",sex:"+sex);
                    }
                }
                if (userCursor!=null){
                    userCursor.close();
                }

以上執行結果:
這裡寫圖片描述

到這裡ContentProvider內容提供者一些常用知識點基本差不多了,底層的實現同樣是Binder機制,ContentProvider對Binder進行了封裝。在使用ContentProvider跨程序通訊也會存在一些安全性問題,比如SQL語句注入、執行緒同步等問題。後續還需對一些幾點學習

  • SQL語句注入
  • 執行緒同步
  • CUDR原始碼

參考