天天看點

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

文章目錄

  • 第一章 四大元件
    • 第三元件 Content Provider
      • (一)定義
      • (二)作用
      • (三)原理
      • (四)具體使用
        • (1)統一資源辨別符
        • (2)MIME資料類型
        • (3)ContentProvider類
        • (4)輔助工具類
          • 1、ContentPesolver類
          • 2、ContentUris類
          • 3、UriMatcher類
          • 4、ContentObserver類
      • (五)執行個體說明(資料源采用SQLite)
        • 1、程序内通信
        • 2、程序間通信(資料共享)

第一章 四大元件

第三元件 Content Provider

(一)定義

内容提供者

(二)作用

實作各個app應用/程序間進行資料互動&共享(跨程序通信)

ContentProvider=中間者角色(搬運工),真正存儲&操作資料的資料源為原來存儲資料的方式(資料庫(sqlite)、檔案、XML、網絡等等)

ContentProvider一般為存儲和擷取資料提供統一的接口,可以在不同的應用程式之間共享資料。

之是以使用ContentProvider,主要有以下幾個理由:

1、對底層資料庫抽象:ContentProvider提供了對底層資料存儲方式的抽象。比如下圖中,底層使用了SQLite資料庫,在用了ContentProvider封裝後,即使你把資料庫換成MongoDB,也不會對上層資料使用層代碼産生影響。

2、封裝統一資料類型:Android架構中的一些類需要ContentProvider類型資料。如果你想讓你的資料可以使用在如SyncAdapter, Loader, CursorAdapter等類上,那麼你就需要為你的資料做一層ContentProvider封裝。

3、用安全的方式封裝:是ContentProvider為應用間的資料互動提供了一個安全的環境。它準許你把自己的應用資料根據需求開放給其他應用進行增、删、改、查,而不用擔心直接開放資料庫權限而帶來的安全問題。

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

(三)原理

Android中的Binder機制

Binder是一種Android中實作跨程序通信(IPC)的方式

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

(四)具體使用

(1)統一資源辨別符

(1)定義:Uniform Resource Identifier,即統一資源辨別符

(2)作用:唯一辨別 ContentProvider & 其中的資料:外界程序通過 URI 找到對應的ContentProvider & 其中的資料,再進行資料操作

(3)具體使用:URI分為 系統預置 & 自定義,分别對應系統内置的資料(如通訊錄、日程表等等)和自定義資料庫

第一章 四大元件 之 ContentProvider(三)第一章 四大元件
// 設定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中設定的權限對應