第一行代碼讀書(shū)筆記 7 -- 內(nèi)容提供器

本篇文章主要介紹以下幾個(gè)知識(shí)點(diǎn):

  • Android 運(yùn)行時(shí)權(quán)限;
  • 內(nèi)容提供器
圖片來(lái)源于網(wǎng)絡(luò)

7.1 運(yùn)行時(shí)權(quán)限

在介紹內(nèi)容提供器之前先來(lái)了解了解 Android 運(yùn)行時(shí)權(quán)限。

7.1.1 Android 權(quán)限機(jī)制

Android 把所有的權(quán)限歸成兩類:普通權(quán)限和危險(xiǎn)權(quán)限。

  • 普通權(quán)限
    ? 不會(huì)直接威脅到用戶的安全和隱私的權(quán)限,對(duì)于這部分的權(quán)限申請(qǐng),系統(tǒng)會(huì)自動(dòng)幫我們進(jìn)行授權(quán)。

  • 危險(xiǎn)權(quán)限
    ? 可能觸及用戶隱私,或?qū)υO(shè)備安全性造成影響的權(quán)限,如獲取聯(lián)系人信息、地理位置等,對(duì)于這部分的權(quán)限申請(qǐng),必須由用戶手動(dòng)點(diǎn)擊授權(quán)才可以,否則程序無(wú)法使用相應(yīng)的功能。

下面列出了 Android 中所有的危險(xiǎn)權(quán)限:

Android中的9組24個(gè)危險(xiǎn)權(quán)限

上表中每個(gè)危險(xiǎn)權(quán)限都屬于一個(gè)權(quán)限組,若用戶同意授權(quán)某個(gè)權(quán)限名,那么該權(quán)限所對(duì)應(yīng)的權(quán)限組中的其他權(quán)限也會(huì)同時(shí)跟著被授權(quán)。

注:訪問(wèn) http://developer.android.com/reference/android/Manifest.permission.html 可以查看完整的權(quán)限列表。

7.1.2 在運(yùn)行程序時(shí)申請(qǐng)權(quán)限

接下來(lái)通過(guò)個(gè)例子來(lái)介紹如何在運(yùn)行程序時(shí)申請(qǐng)權(quán)限。

此次的例子是通過(guò)點(diǎn)擊界面上的一個(gè)按鈕來(lái)直接撥打電話。撥打電話時(shí)需要聲明上節(jié)表中的 CALL_PHONE 這個(gè)危險(xiǎn)權(quán)限。

首先,在 AndroidManifest.xml 文件中聲明權(quán)限:

<uses-permission android:name="android.permission.CALL_PHONE" />

然后在布局 activity_call_phone.xml 中添加個(gè)按鈕:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    <Button
        android:id="@+id/btn_call_phone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="點(diǎn)擊撥打電話"/>

</RelativeLayout>

接著修改 activity 中代碼如下:

public class CallPhoneActivity extends AppCompatActivity {

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

        Button btn_call_phone = (Button) findViewById(R.id.btn_call_phone);
        btn_call_phone.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    // Intent.ACTION_CALL 是系統(tǒng)內(nèi)置的打電話動(dòng)作(而Intent.ACTION_DIAL指打開(kāi)撥號(hào)界面,不需要聲明權(quán)限)
                    Intent intent = new Intent(Intent.ACTION_CALL);
                    intent.setData(Uri.parse("tel:10086"));
                    startActivity(intent);
                }catch (SecurityException e){
                    e.printStackTrace();
                }
            }
        });
    }
}

在 Android 6.0 前,撥打電話功能的實(shí)現(xiàn)很簡(jiǎn)單,上面代碼就把撥打電話功能實(shí)現(xiàn)了,在低于 Android 6.0 系統(tǒng)的手機(jī)上可以正常運(yùn)行,但在 Android 6.0 或更高版本系統(tǒng)的手機(jī)上運(yùn)行則無(wú)效,會(huì)拋出“Permission Denial”的異常。

接下來(lái),修改 activity 中的代碼,修復(fù)上面的問(wèn)題,如下:

public class CallPhoneActivity extends AppCompatActivity {

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

        Button btn_call_phone = (Button) findViewById(R.id.btn_call_phone);
        btn_call_phone.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 1. 判斷用戶是否授權(quán),借助 ContextCompat.checkSelfPermission() 方法
                // ContextCompat.checkSelfPermission() 方法接收兩參數(shù):context 和權(quán)限名
                if (ActivityCompat.checkSelfPermission(CallPhoneActivity.this,
                        Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                    // 3. 若沒(méi)授權(quán),則調(diào)用 ActivityCompat.requestPermissions()方法來(lái)向用戶授權(quán)
                    // 其三個(gè)參數(shù):Activity實(shí)例、String數(shù)組(放權(quán)限名)、請(qǐng)求碼(只要唯一就行了,這里傳1)
                    ActivityCompat.requestPermissions(CallPhoneActivity.this,
                            new String[]{Manifest.permission.CALL_PHONE}, 1);
                    // 4. 調(diào)用完 requestPermissions()方法后,會(huì)彈出一個(gè)權(quán)限申請(qǐng)對(duì)話框,供用戶選擇,
                    // 最后回調(diào) onRequestPermissionsResult()方法
                }else {
                    // 2. 若已授權(quán),則直接執(zhí)行撥打電話的邏輯
                    call();
                }
            }
        });
    }

    /**
     * 撥打電話方法
     */
    private void call() {
        try {
            // Intent.ACTION_CALL 是系統(tǒng)內(nèi)置的打電話動(dòng)作
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        }catch (SecurityException e){
            e.printStackTrace();
        }
    }

    /**
     * 無(wú)論用戶是否同意權(quán)限申請(qǐng),都會(huì)回調(diào)此方法
     * @param requestCode
     * @param permissions
     * @param grantResults 授權(quán)的結(jié)果
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        switch (requestCode){
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                    call();
                }else {
                    ToastUtils.showShort("你拒絕了權(quán)限請(qǐng)求");
                }
                break;

            default:
                break;
        }
    }
}

這樣就完成了權(quán)限申請(qǐng)的代碼編寫(xiě)了,具體過(guò)程看代碼注釋,現(xiàn)在運(yùn)行一下程序,效果如下:

撥打電話權(quán)限申請(qǐng)效果

好了,關(guān)于運(yùn)行時(shí)權(quán)限的內(nèi)容先介紹到這,下面進(jìn)入主題——內(nèi)容提供器。

7.2 內(nèi)容提供器

內(nèi)容提供器(Content Provider)主要用于在不同的應(yīng)用程序之間實(shí)現(xiàn)數(shù)據(jù)共享的功能,它提供了一套完整的機(jī)制,允許一個(gè)程序訪問(wèn)到另一個(gè)程序中的數(shù)據(jù),同時(shí)還能保證被訪問(wèn)數(shù)據(jù)的安全性。目前,使用內(nèi)容提供器是 Android 實(shí)現(xiàn)跨程序共享數(shù)據(jù)的標(biāo)準(zhǔn)方式。

不同于文件存儲(chǔ)和 SharedPreferences 存儲(chǔ)中的兩種全局可讀寫(xiě)操作模式,內(nèi)容提供器可以選擇只對(duì)哪一部分?jǐn)?shù)據(jù)進(jìn)行共享,從而保證程序中的隱私數(shù)據(jù)不會(huì)有泄露的風(fēng)險(xiǎn)。

7.2.1 訪問(wèn)其他程序中的數(shù)據(jù)

內(nèi)容提供器的用法有兩種:

  • 使用現(xiàn)有的內(nèi)容提供器來(lái)讀取和操作相應(yīng)程序中的數(shù)據(jù)
  • 創(chuàng)建自己的內(nèi)容提供器給我們程序的數(shù)據(jù)提供外部訪問(wèn)接口

7.2.1.1 ContentResolver 的基本用法

每一個(gè)應(yīng)用程序,借助 ContenResolver 類才能訪問(wèn)內(nèi)容提供器中共享的數(shù)據(jù),該類可通過(guò) Context 中的 getContentResolver() 方法獲取實(shí)例,該類提供的一系列對(duì)數(shù)據(jù)的增刪查改方法不同于 SQLiteDataBase,是不接收表名參數(shù)的,而是用參數(shù)Uri(稱為內(nèi)容 URI) 代替。

內(nèi)容 URI 給內(nèi)容提供器中的數(shù)據(jù)建立了唯一標(biāo)識(shí)符,它由 authority(區(qū)分不同的應(yīng)用程序)和 path(區(qū)分不同的表)組成。如某個(gè)程序包名為 com.example.app 且存在兩張表 table1 和 table2 ,其標(biāo)準(zhǔn)格式寫(xiě)法如下:

內(nèi)容 URI 的標(biāo)準(zhǔn)寫(xiě)法

在得到了 內(nèi)容 URI 字符串后,需要調(diào)用 Uri.parse() 方法把它解析成 Uri 對(duì)象才可作為參數(shù)傳入,代碼如下:

Uri uri = Uri.parse("content://com.example.app.provider/table1")
  • 查詢操作

現(xiàn)在就可以用這個(gè) Uri 對(duì)象來(lái)查詢 table1 表中的數(shù)據(jù)了,代碼如下:

Curson curson = getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder);

下表對(duì)上面的參數(shù)進(jìn)行了詳細(xì)的解釋:

參數(shù)詳解

查詢完后返回一個(gè) Curson 對(duì)象,讀取代碼如下:

// 通過(guò)移動(dòng)游標(biāo)的位置來(lái)遍歷Cursor的所有行
if (cursor != null){
            while (cursor.moveToNext()){
                String column1 = cursor.getString(cursor.getColumnIndex("column1"));
                int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
            }
            cursor.close();
        }
  • 添加操作

向 table1 表中添加一條數(shù)據(jù):

// 將待添加的數(shù)據(jù)組裝到 ContentValues 中
ContentValues values = new ContentValues();
values.put("column1","text");
values.put("column2",1);
// 調(diào)用 insert() 方法添加數(shù)據(jù)
getContentResolver().insert(uri, values);
  • 更新操作

更新數(shù)據(jù),把 column1 的值清空:

ContentValues values = new ContentValues();
// 清空
values.put("column1","");
// 調(diào)用 update() 方法更新數(shù)據(jù)
getContentResolver().update(uri, values, "column1 = ? and column2 = ?",new String[]{"text","1"});
  • 刪除操作

調(diào)用 ContentResolver 的 delete() 刪除數(shù)據(jù):

getContentResolver().delete(uri,  "column2 = ?", new String[]{"1"});

以上就是 ContentResolver 中的增刪查改方法。

7.2.1.2 讀取系統(tǒng)聯(lián)系人

接下來(lái)舉個(gè)例子來(lái)加深學(xué)習(xí):利用內(nèi)容提供器來(lái)讀取系統(tǒng)聯(lián)系人。

由于模擬器上木有聯(lián)系人,先向模擬器中創(chuàng)建幾個(gè)聯(lián)系人。聯(lián)系人準(zhǔn)備好后,首先在項(xiàng)目的 AndroidManifest.xml 中聲明讀取聯(lián)系人的權(quán)限:

<uses-permission android:name="android.permission.READ_CONTACTS" />

簡(jiǎn)單起見(jiàn),在布局 activity_read_contact.xml 中放一個(gè) ListView 來(lái)顯示讀取的聯(lián)系人信息:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ListView
        android:id="@+id/contacts_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>

</RelativeLayout>

接著修改 activity 中的代碼如下:

public class ReadContactActivity extends AppCompatActivity {
    
    ArrayAdapter<String> adapter;
    List<String> contactsList = new ArrayList<>();

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

        // 獲取 listView 的實(shí)例,設(shè)置適配器
        ListView contacts_view = (ListView) findViewById(R.id.contacts_view);
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,contactsList);
        contacts_view.setAdapter(adapter);
        
        // 判斷是否授權(quán)
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},1);
        }else {
            readContacts();
        }

    }

    /**
     * 讀取聯(lián)系人方法
     */
    private void readContacts() {
        Cursor cursor = null;
        try{
            // 查詢聯(lián)系人數(shù)據(jù)
            cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                    null, null, null, null);
            if (cursor != null){
                while (cursor.moveToNext()){
                    // 獲取聯(lián)系人姓名
                    String name = cursor.getString(cursor.getColumnIndex
                            (ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                    // 獲取聯(lián)系人號(hào)碼
                    String number = cursor.getString(cursor.getColumnIndex
                            (ContactsContract.CommonDataKinds.Phone.NUMBER));
                    contactsList.add(name + "\n" + number);
                }
                adapter.notifyDataSetChanged();
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (cursor != null){
                cursor.close();
            }
        }
        
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        switch (requestCode){
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                    readContacts();
                }else {
                    ToastUtils.showShort("你拒絕了權(quán)限請(qǐng)求");
                }
                break;
            
            default:
                break;
        }
    }
}

運(yùn)行程序,效果如下:

訪問(wèn)聯(lián)系人并展示

7.2.2 創(chuàng)建自己的內(nèi)容提供器

上一小節(jié),介紹了如何訪問(wèn)其他應(yīng)用程序得數(shù)據(jù),其思路是獲取應(yīng)用程序的內(nèi)容 URI 后借助 ContentResolver 進(jìn)行 CRUD 操作就行了。接下來(lái)介紹創(chuàng)建自己的內(nèi)容提供器。

7.2.2.1 創(chuàng)建步驟

首先,新建一個(gè)類去繼承 ContentProvider,重寫(xiě)它的6個(gè)抽象方法,如下:

/**
 * 自己的內(nèi)容提供器
 * Created by KXwon on 2016/12/18.
 */

public class MyProvider extends ContentProvider{
    
    // 初始化內(nèi)容提供器的時(shí)候調(diào)用,返回true表示成功,false失敗
    @Override
    public boolean onCreate() {
        return false;
    }

    // 從內(nèi)容提供器中查詢數(shù)據(jù)
    @Override
    public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
        return null;
    }
    
   // 向內(nèi)容提供器中添加一條數(shù)據(jù)
    @Override
    public Uri insert(Uri uri, ContentValues contentValues) {
        return null;
    }

    // 從內(nèi)容提供器中刪除數(shù)據(jù)
    @Override
    public int delete(Uri uri, String s, String[] strings) {
        return 0;
    }

    // 更新內(nèi)容提供器中已有的數(shù)據(jù)
    @Override
    public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
        return 0;
    }
    
    // 根據(jù)傳入的內(nèi)容 URI 來(lái)返回 MIME 類型
    @Override
    public String getType(Uri uri) {
        return null;
    }
}

之前提到一個(gè)標(biāo)準(zhǔn)的內(nèi)容 URI 寫(xiě)法是這樣的:

// 表示調(diào)用方期望訪問(wèn)的是 com.example.app 這個(gè)應(yīng)用的 table1 表中的數(shù)據(jù)
content://com.example.app.provider/table1

除此之外,還可以在內(nèi)容 URI 后面加一個(gè)id,如下:

// 表示調(diào)用方期望訪問(wèn)的是 com.example.app 這個(gè)應(yīng)用的 table1 表中 id 為 1 的數(shù)據(jù)
content://com.example.app.provider/table1/1

內(nèi)容 URI 的格式主要就只有以上兩種,可以通過(guò)用通配符的方式來(lái)匹配這兩種內(nèi)容 URI,規(guī)則如下:
?(1) *:表示匹配任意長(zhǎng)度的任意字符。
?(2)# :表示匹配任意長(zhǎng)度的數(shù)字

所以,一個(gè)能夠匹配任意表的內(nèi)容 URI 格式可寫(xiě)成:

content://com.example.app.provider/*

一個(gè)能夠匹配 table1 表中任意一行數(shù)據(jù)的內(nèi)容 URI 格式可寫(xiě)成:

content://com.example.app.provider/table1/#

然后,再借助 UriMatcher 這個(gè)類實(shí)現(xiàn)內(nèi)容 URI 功能,修改 MyProvider 類如下:

public class MyProvider extends ContentProvider{
    
    public static final int TABLE1_DIR = 0; //訪問(wèn) table1 表中的所有數(shù)據(jù)
    public static final int TABLE1_ITEM = 1;//訪問(wèn) table1 表中的單條數(shù)據(jù)
    public static final int TABLE2_DIR = 3; //訪問(wèn) table2 表中的所有數(shù)據(jù)
    public static final int TABLE2_ITEM = 4;//訪問(wèn) table2 表中的單條數(shù)據(jù)
    
    private static UriMatcher uriMatcher;
    
    static {
        // 創(chuàng)建 UriMatcher 實(shí)例
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        // 調(diào)用 addURI() 方法,此方法接收3個(gè)參數(shù):authority、path、自定義代碼
        uriMatcher.addURI("com.example.app.provider","table1",TABLE1_DIR);
        uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_ITEM);
        uriMatcher.addURI("com.example.app.provider","table2",TABLE2_DIR);
        uriMatcher.addURI("com.example.app.provider","table2/#",TABLE2_ITEM);
    }

    // 從內(nèi)容提供器中查詢數(shù)據(jù)
    @Override
    public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
        switch (uriMatcher.match(uri)){
            case TABLE1_DIR:
                // 查詢 table1 表中的所有數(shù)據(jù)
                break;
            case TABLE1_ITEM:
                // 查詢 table1 表中的單條數(shù)據(jù)
                break;
            case TABLE2_DIR:
                // 查詢 table2 表中的所有數(shù)據(jù)
                break;
            case TABLE2_ITEM:
                // 查詢 table2 表中的單條數(shù)據(jù)
                break;
            default:
                break;
        }
        . . .
    }

   . . .
}

上述代碼只是以 query() 方法做了個(gè)示范,其他3個(gè)增刪改的方法也差不多。

而 getType() 方法,是所有的內(nèi)容提供器都必須提供的一個(gè)方法,用于獲取 Uri 對(duì)象所對(duì)應(yīng)的 MIME 類型。MIME 字符串主要由 3 部分組成,并有如下格式規(guī)定:
?(1) 必須由 vnd 開(kāi)頭
?(2)若內(nèi)容 URI 以路徑結(jié)尾,則后接 android.cursor.dir/,若內(nèi)容 URI 以 id 結(jié)尾,則后接 android.cursor.item/
?(3)最后接上 vnd.<authority>.<path>

最后,實(shí)現(xiàn) getType() 方法中的邏輯,完善 MyProvide r類如下:

public class MyProvider extends ContentProvider{

    . . . 

    // 根據(jù)傳入的內(nèi)容 URI 來(lái)返回 MIME 類型
    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)){
            case TABLE1_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
            case TABLE1_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
            case TABLE2_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
            case TABLE2_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
            default:
                break;
        }
        return null;
    }
}

到這里,一個(gè)完整的內(nèi)容提供器就創(chuàng)建完成了。

7.2.2.1 實(shí)現(xiàn)跨程序數(shù)據(jù)共享

好了,例子來(lái)了。上一章項(xiàng)目中建立了 BookStore.db 數(shù)據(jù)庫(kù),里面有 book 表和 category 表這兩張表,為簡(jiǎn)單起見(jiàn),在上一章的基礎(chǔ)上繼續(xù)開(kāi)發(fā)。

首先,為項(xiàng)目創(chuàng)建個(gè)內(nèi)容提供器,在 Android Studio 中右擊 com.wonderful.myfirstcode.chapter7.provider包(你項(xiàng)目所在的包名)→New→Other→Content Provider,會(huì)彈出如下窗口:

創(chuàng)建內(nèi)容提供器的窗口

可以看到,這里把內(nèi)容提供器命名為 DatabaseProvider,authority 指定為項(xiàng)目包名,Exported 表示是否允許外部程序訪問(wèn)我們的內(nèi)容提供器,Enabled 表示是否啟用這個(gè)內(nèi)容提供器。兩個(gè)勾選點(diǎn)擊 Finish 完成創(chuàng)建。

接著修改 DatebaseProvider 中的代碼如下:

public class DataBaseProvider extends ContentProvider {

    public static final int BOOK_DIR = 0; //訪問(wèn) book 表中的所有數(shù)據(jù)
    public static final int BOOK_ITEM = 1;//訪問(wèn) book 表中的單條數(shù)據(jù)
    public static final int CATEGORY_DIR = 3;
    public static final int CATEGORY_ITEM = 4;

    public static final String AUTHORITY = "com.wonderful.myfirstcode.chapter7.provider";

    private static UriMatcher uriMatcher;
    
    private MyDatabaseHelper dbHelper;

    static {
        // 創(chuàng)建 UriMatcher 實(shí)例
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        // 調(diào)用 addURI() 方法,此方法接收3個(gè)參數(shù):authority、path、自定義代碼
        uriMatcher.addURI(AUTHORITY,"book",BOOK_DIR);
        uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY,"category",CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY,"category/#",CATEGORY_ITEM);
    }

    /**
     * 初始化內(nèi)容提供器
     */
    @Override
    public boolean onCreate() {
        // 創(chuàng)建 MyDatabaseHelper 實(shí)例
        dbHelper = new MyDatabaseHelper(getContext(),"BookStore.db",null,2);
        // 返回true表示完成了創(chuàng)建或升級(jí)數(shù)據(jù)庫(kù)
        return true;
    }

    /**
     * 查詢數(shù)據(jù)
     */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
                // 查詢 book 表中的所有數(shù)據(jù)
                cursor = db.query("book",projection,selection,selectionArgs,
                        null,null,sortOrder);
                break;
            
            case BOOK_ITEM:
                // 查詢 book 表中的單條數(shù)據(jù)
                String bookId = uri.getPathSegments().get(1);
                cursor = db.query("book",projection,"id = ?",new String[]{bookId},
                        null,null,sortOrder);
                break;
            
            case CATEGORY_DIR:
                cursor = db.query("category",projection,selection,selectionArgs,
                        null,null,sortOrder);
                break;
            
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                cursor = db.query("category",projection,"id = ?",new String[]
                        {categoryId}, null,null,sortOrder);
                break;
            
            default:
                break;
        }
        return cursor;
    }

    /**
     * 添加數(shù)據(jù)
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
            case BOOK_ITEM:
                long newBookId = db.insert("book",null,values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book" + newBookId);
                break;

            case CATEGORY_DIR:
            case CATEGORY_ITEM:
                long newCategoryId = db.insert("category",null,values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/category" +
                        newCategoryId);
                break;

            default:
                break;
        }
        return uriReturn;
    }

    /**
     * 更新數(shù)據(jù)
     */
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        int updatedRows = 0;
        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
                updatedRows = db.update("book",values,selection,selectionArgs);
                break;

            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                updatedRows = db.update("book",values,"id = ?",new String[]{bookId});
                break;

            case CATEGORY_DIR:
                updatedRows = db.update("category",values,selection,selectionArgs);
                break;

            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                updatedRows = db.update("category",values,"id = ?",new String[]
                        {categoryId});
                break;

            default:
                break;
        }
        return updatedRows;
    }

    /**
     * 刪除數(shù)據(jù)
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        int deletedRows = 0;
        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
                deletedRows = db.delete("book",selection,selectionArgs);
                break;

            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                deletedRows = db.delete("book","id = ?",new String[]{bookId});
                break;

            case CATEGORY_DIR:
                deletedRows = db.delete("category",selection,selectionArgs);
                break;

            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                deletedRows = db.delete("category","id = ?",new String[]
                        {categoryId});
                break;

            default:
                break;
        }
        return deletedRows;
    }

    /**
     * 獲取 Uri 對(duì)象所對(duì)應(yīng)的 MIME 類型
     */
    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.com.wonderful.myfirstcode." +
                        "chapter7.provider.book";

            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.com.wonderful.myfirstcode." +
                        "chapter7.provider.book";

            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.wonderful.myfirstcode." +
                        "chapter7.provider.category";

            case CATEGORY_ITEM:
                return "vnd.android.cursor.item/vnd.com.wonderful.myfirstcode." +
                        "chapter7.provider.category";
        }
        return null;
    }
    
}

另外,內(nèi)容提供器一定要在 AndroidManifest.xml 中注冊(cè)才可使用。不過(guò)剛用 AS 創(chuàng)建的內(nèi)容提供器已經(jīng)幫我們自動(dòng)注冊(cè)完成了,如下:

 <provider
     android:name=".chapter7.provider.DataBaseProvider"
     android:authorities="com.wonderful.myfirstcode.chapter7.provider"
     android:enabled="true"
     android:exported="true">
</provider>

現(xiàn)在,內(nèi)容提供器已經(jīng)創(chuàng)建好了,接下來(lái)新建一個(gè)項(xiàng)目 ProviderTest 來(lái)訪問(wèn)上面程序中的數(shù)據(jù),記得清空上一章項(xiàng)目里的數(shù)據(jù),以防造成干涉。

先來(lái)編寫(xiě)下布局文件 activity_main.xml,添加4個(gè)按鈕來(lái)增刪查改,如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_add_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="添加數(shù)據(jù)"/>

    <Button
        android:id="@+id/btn_query_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="查詢數(shù)據(jù)"/>

    <Button
        android:id="@+id/btn_update_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="更新數(shù)據(jù)"/>

    <Button
        android:id="@+id/btn_delete_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="刪除數(shù)據(jù)"/>

</LinearLayout>

然后修改 MainActivity 中的代碼如下:

public class MainActivity extends AppCompatActivity {

    private String newId;

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

        // 添加數(shù)據(jù)
        Button btn_add_data = (Button) findViewById(R.id.btn_add_data);
        btn_add_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.wonderful.myfirstcode.chapter7.provider/book");
                ContentValues values = new ContentValues();
                values.put("name", "第一行代碼");
                values.put("author", "郭霖");
                values.put("pages", 1000);
                values.put("price", 66.66);
                Uri newUri = getContentResolver().insert(uri,values);
                newId = newUri.getPathSegments().get(1);
            }
        });

        // 查詢數(shù)據(jù)
        Button btn_query_data = (Button) findViewById(R.id.btn_query_data);
        btn_query_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.wonderful.myfirstcode.chapter7.provider/book");
                Cursor cursor = getContentResolver().query(uri,null,null,null,null);
                if (cursor != null){
                    while (cursor.moveToNext()){
                        String name = cursor.getString(cursor.getColumnIndex("name"));
                        String auhtor = cursor.getString(cursor.getColumnIndex("auhtor"));
                        int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                        double price = cursor.getDouble(cursor.getColumnIndex("price"));

                        Log.d("MainActivity", "書(shū)名: " + name);
                        Log.d("MainActivity", "作者: " + auhtor);
                        Log.d("MainActivity", "頁(yè)數(shù): " + pages);
                        Log.d("MainActivity", "價(jià)格: " + price);
                    }
                }
                cursor.close();
            }
        });

        // 更新數(shù)據(jù)
        Button btn_update_data = (Button) findViewById(R.id.btn_update_data);
        btn_update_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.wonderful.myfirstcode.chapter7.provider/book/"
                        + newId);
                ContentValues values = new ContentValues();
                values.put("name", "第二行代碼");
                values.put("pages", 2000);
                values.put("price", 88.88);
                getContentResolver().update(uri,values,null,null);
            }
        });

        // 刪除數(shù)據(jù)
        Button btn_delete_data = (Button) findViewById(R.id.btn_delete_data);
        btn_delete_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.wonderful.myfirstcode.chapter7.provider/book/" 
                        + newId);
                getContentResolver().delete(uri,null,null);
            }
        });
    }
}

代碼很簡(jiǎn)單,不做解釋了,現(xiàn)在運(yùn)行一下項(xiàng)目,如下界面:

ProviderTest 主界面

點(diǎn)擊 添加數(shù)據(jù) 按鈕,此時(shí)數(shù)據(jù)就添加到上一章項(xiàng)目的數(shù)據(jù)庫(kù)中了,點(diǎn)擊 查詢數(shù)據(jù) 按鈕,打印日志如下:

查詢添加的數(shù)據(jù)

然后點(diǎn)擊 更新數(shù)據(jù) 按鈕,再次點(diǎn)擊 查詢數(shù)據(jù) 按鈕查看打印日志如下:

查詢更新后的數(shù)據(jù)

最后點(diǎn)擊 刪除數(shù)據(jù) 按鈕,此時(shí)數(shù)據(jù)就沒(méi)了,點(diǎn)擊 查詢數(shù)據(jù) 按鈕也就查詢不到數(shù)據(jù)了。

以上,跨程序共享數(shù)據(jù)功能成功實(shí)現(xiàn)了。

與內(nèi)容提供器相關(guān)內(nèi)容就介紹到這。下篇文章將進(jìn)入手機(jī)多媒體的學(xué)習(xí)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容