第一章 四大組件 之 ContentProvider(三)

第一章 四大組件

第三組件 Content Provider

(一)定義

內容提供者

(二)作用

實現各個app應用/進程間進行數據交互&共享(跨進程通信)
ContentProvider=中間者角色(搬運工),真正存儲&操作數據的數據源爲原來存儲數據的方式(數據庫(sqlite)、文件、XML、網絡等等)
ContentProvider一般爲存儲和獲取數據提供統一的接口,可以在不同的應用程序之間共享數據。
之所以使用ContentProvider,主要有以下幾個理由:
1、對底層數據庫抽象:ContentProvider提供了對底層數據存儲方式的抽象。比如下圖中,底層使用了SQLite數據庫,在用了ContentProvider封裝後,即使你把數據庫換成MongoDB,也不會對上層數據使用層代碼產生影響。
2、封裝統一數據類型:Android框架中的一些類需要ContentProvider類型數據。如果你想讓你的數據可以使用在如SyncAdapter, Loader, CursorAdapter等類上,那麼你就需要爲你的數據做一層ContentProvider封裝。
3、用安全的方式封裝:是ContentProvider爲應用間的數據交互提供了一個安全的環境。它准許你把自己的應用數據根據需求開放給其他應用進行增、刪、改、查,而不用擔心直接開放數據庫權限而帶來的安全問題。
在這裏插入圖片描述

(三)原理

Android中的Binder機制
Binder是一種Android中實現跨進程通信(IPC)的方式
在這裏插入圖片描述

(四)具體使用

(1)統一資源標識符

(1)定義:Uniform Resource Identifier,即統一資源標識符
(2)作用:唯一標識 ContentProvider & 其中的數據:外界進程通過 URI 找到對應的ContentProvider & 其中的數據,再進行數據操作
(3)具體使用: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/#

(2)MIME數據類型

(1)作用:ContentProvider根據 URI 返回MIME類型
(2)組成=父類型+子類型(父類型爲固定類型,用於區分單條/多條記錄,子類型可自定義)
形式1:單條記錄:vnd.android.cursor.item/自定義
形式2:多條記錄:vnd.android.cursor.dir/自定義
(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

(3)ContentProvider類

(1)數據組織方法
ContentProvider主要以表格形式組織數據(表、記錄、字段)
(2)主要使用方法
1、添加數據

public Uri insert(Uri uri, ContentValues values)// 外部進程向 ContentProvider 中添加數據

2、刪除數據

public int delete(Uri uri, String selection, String[] selectionArgs)// 外部進程 刪除 ContentProvider 中的數據

3、查詢數據

public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)// 外部進程更新 ContentProvider 中的數據

4、修改數據

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的數據存儲方式是內存,則需要自己實現線程同步

5、其他方法

public boolean onCreate()
// ContentProvider創建後 或 打開系統後其它進程第一次訪問該ContentProvider時 由系統進行調用
// 注:運行在ContentProvider進程的主線程,故不能做耗時操作
public String getType(Uri uri)
// 得到數據類型,即返回當前 Url 所代表數據的MIME類型

(3)Android爲常見的數據(如通訊錄、日程表等)提供了內置了默認的ContentProvider,也可根據需求自定義ContentProvider,上述6個方法必須重寫。ContentProvider類不會直接與外部進程交互,而是通過ContentResolver 類。

(4)輔助工具類

1、ContentPesolver類

1、作用:統一管理不同 ContentProvider間的操作

  1. 即通過 URI 即可操作 不同的ContentProvider 中的數據
  2. 外部進程通過 ContentResolver類 從而與ContentProvider類進行交互

2、一款應用要使用多個ContentProvider(手機中可能安裝很多含有Provider應用,比如聯繫人應用、日曆應用、字典應用等等),故用一個ContentResolver對所有ContentProvider統一管理能降低操作成本,實現難度小。
ContentResolver通過URL來區別不同的ContentProvider
3、使用方法
3.1)具體使用: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)

3.2)實例說明

// 使用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"); 
2、ContentUris類

2.1)作用:操作URI
2.2)具體使用: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
3、UriMatcher類

3.1)作用:
1、在ContentProvider中註冊URI(2)根據URI匹配ContentProvider對應的數據表
3.2)具體使用:

// 步驟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、ContentObserver類

4.1)定義:內容觀察者
4.2)作用:觀察 Uri引起 ContentProvider 中的數據變化 & 通知外界(即訪問該數據訪問者):當ContentProvider 中的數據發生變化(增、刪 & 改)時,就會觸發該 ContentObserver類通知數據變化
適用場景:需要頻繁檢測的數據庫或者某個數據是否發生改變,如果使用線程去操作,很不經濟而且很耗時 。
4.3)具體使用:

// 步驟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類進行解除

4.4)實例:
觀察系統的短信息數據發生了變化。當監聽到短信數據發生變化時,查詢所有已發送的短信並且顯示出來。
(1)創建觀察系統裏短消息的數據庫變化的ContentObserver派生類SMSContentObserver.java

//用來觀察系統裏短消息的數據庫變化 ”表“內容觀察者,只要信息數據庫發生變化,都會觸發該ContentObserver 派生類
public class SMSContentObserver extends ContentObserver {
private static String TAG = "SMSContentObserver";
private int MSG_OUTBOXCONTENT = 2 ;
private Context mContext ;
private Handler mHandler ; //用Handler更新UI線程
public SMSContentObserver(Context context,Handler handler) {
super(handler);
mContext = context ;
mHandler = handler ;
}

//當所監聽的Uri中數據發生變化回收,就會回調該方法。
@Override
public void onChange(boolean selfChange){
Log.i(TAG, "the sms table has changed");
//查詢發件箱裏的內容
Uri outSMSUri = Uri.parse("content://sms/sent") ;
Cursor c = mContext.getContentResolver().query(outSMSUri, null, null, null,"date desc");
if(c != null){
Log.i(TAG, "the number of send is"+c.getCount()) ;
StringBuilder sb = new StringBuilder() ;
//循環遍歷
while(c.moveToNext()){
// sb.append("發件人手機號碼: "+c.getInt(c.getColumnIndex("address")))
// .append("信息內容: "+c.getInt(c.getColumnIndex("body")))
// .append("是否查看: "+c.getInt(c.getColumnIndex("read")))
// .append("發送時間: "+c.getInt(c.getColumnIndex("date")))
// .append("\n");
sb.append("發件人手機號碼: "+c.getInt(c.getColumnIndex("address")))
.append("信息內容: "+c.getString(c.getColumnIndex("body")))
.append("\n");
}
c.close();
mHandler.obtainMessage(MSG_OUTBOXCONTENT, sb.toString()).sendToTarget();
}
}
}

(2)主流程MainActivity註冊內容觀察者,並監聽數據變化

public class MainActivity extends Activity {

private TextView tvAirplane;
private EditText etSmsoutbox;
// Message 類型值
private static final int MSG_OUTBOXCONTENT = 1;
private SMSContentObserver smsContentObserver;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tvAirplane = (TextView) findViewById(R.id.tvAirplane);
etSmsoutbox = (EditText) findViewById(R.id.smsoutboxContent);
// 創建ContentObserver對象
smsContentObserver = new SMSContentObserver(this, mHandler);
//步驟1:註冊內容觀察者
registerContentObservers() ;
}
private void registerContentObservers() {
//指定監聽Uri並註冊監聽該Uri,該Uri的contentProvider中數據變化會執行notifychange()通知Observer數據變化
Uri smsUri = Uri.parse("content://sms");
getContentResolver().registerContentObserver(smsUri, true,smsContentObserver);
}

private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
System.out.println("---mHanlder----");
switch (msg.what) {
case MSG_OUTBOXCONTENT:
String outbox = (String) msg.obj;
//顯示短信信息
etSmsoutbox.setText(outbox);
break;
default:
break;
}
}
};
}

(五)實例說明(數據源採用SQLite)

1、進程內通信

步驟說明:

  • 創建數據庫類
  • 自定義 ContentProvider 類
  • 註冊 創建的 ContentProvider類
  • 進程內訪問 ContentProvider的數據

(1)創建數據庫類(SQLite數據庫封裝類)

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類

<provider android:name="MyProvider"
    android:authorities="cn.scu.myprovider"
/>

(4)進程內訪問 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://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();
        // 關閉遊標
    }
}

2、進程間通信(數據共享)

(1)進程1:
1、創建數據庫類
2、自定義 ContentProvider 類
3、註冊 創建的 ContentProvider 類

<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"/>

(2)進程2:
(1)聲明可訪問的權限
(2)訪問ContentProvider類
1、聲明可訪問的權限

// 聲明本應用可允許通信的權限(全權限)
<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在進程間的訪問需要聲明權限
(1)進程1聲明外界進程可訪問該Provider的權限

<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"/>

(2)進程2聲明可訪問的權限

    // 聲明本應用可允許通信的權限(全權限)
    <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中設置的權限對應
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章