1. 程式人生 > >Android之內容提供器Content Provider詳解(二)

Android之內容提供器Content Provider詳解(二)

上一篇 Android之內容提供器Content Provider詳解(一)講解了Content Provider之訪問其他程式中的資料,本篇繼續講解創如何建自己的內容提供器

本博文是《第一行程式碼 Android》的讀書筆記/摘錄。

三、建立自己的內容提供器

在上一篇中,我們學習瞭如何在自己的程式中訪問其他應用程式的資料。總體來說思路還是非常簡單的,只需要獲取到該應用程式的內容URI,然後藉助ContentResolver 進行CRUD 操作就可以了。可是,那些提供外部訪問介面的應用程式都是如何實現這種功能的呢?它們又是怎樣保證資料的安全性,使得隱私資料不會洩漏出去?

(一)建立內容提供器的步驟

前面已經提到過,如果想要實現跨程式共享資料的功能,官方推薦的方式就是使用內容提供器,可以通過新建一個類去繼承ContentProvider 的方式來建立一個自己的內容提供器。新建MyContentProvider繼承自ContentProvider,程式碼如下所示:

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;

public class MyContentProvider
extends ContentProvider {
public MyContentProvider() { } @Override public boolean onCreate() { return false; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return
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; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } }

ContentProvider 類中有六個抽象方法,我們在使用子類繼承它的時候,需要將這六個方法全部重寫。
(1) onCreate()
初始化內容提供器的時候呼叫。通常會在這裡完成對資料庫的建立和升級等操作,返回true 表示內容提供器初始化成功,返回false 則表示失敗。注意,只有當存在ContentResolver 嘗試訪問我們程式中的資料時,內容提供器才會被初始化。

(2)query()
從內容提供器中查詢資料。使用uri 引數來確定查詢哪張表,projection 引數用於確定查詢哪些列,selection 和selectionArgs 引數用於約束查詢哪些行,sortOrder 引數用於對結果進行排序,查詢的結果存放在Cursor 物件中返回。

(3) insert()
向內容提供器中新增一條資料。使用uri 引數來確定要新增到的表,待新增的資料儲存在values 引數中。新增完成後,返回一個用於表示這條新記錄的URI。

(4)update()
更新內容提供器中已有的資料。使用uri 引數來確定更新哪一張表中的資料,新資料儲存在values 引數中,selection 和selectionArgs 引數用於約束更新哪些行,受影響的行數將作為返回值返回。

(5)delete()
從內容提供器中刪除資料。使用uri 引數來確定刪除哪一張表中的資料,selection和selectionArgs 引數用於約束刪除哪些行,被刪除的行數將作為返回值返回。

(6)getType()
根據傳入的內容URI 來返回相應的MIME 型別。

可以看到,幾乎每一個方法都會帶有Uri 這個引數,這個引數也正是呼叫ContentResolver的增刪改查方法時傳遞過來的。而現在,我們需要對傳入的Uri 引數進行解析,從中分析出呼叫方期望訪問的表和資料。
一個標準的內容URI 寫法是這樣的:

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

這就表示呼叫方期望訪問的是com.example.app 這個應用的table1 表中的資料。除此之外,我們還可以在這個內容URI 的後面加上一個id,如下所示:

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

這就表示呼叫方期望訪問的是com.example.app 這個應用的table1 表中id 為1 的資料。

內容URI 的格式主要就只有以上兩種,以路徑結尾就表示期望訪問該表中所有的資料,以id 結尾就表示期望訪問該表中擁有相應id 的資料。我們可以使用萬用字元的方式來分別匹配這兩種格式的內容URI,規則如下:
(1) *:表示匹配任意長度的任意字元;
(2) #:表示匹配任意長度的數字。

所以,一個能夠匹配任意表的內容URI 格式就可以寫成:

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

而一個能夠匹配table1 表中任意一行資料的內容URI 格式就可以寫成:

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

接著,我們再借助UriMatcher 這個類就可以輕鬆地實現匹配內容URI 的功能。UriMatcher中提供了一個addURI()方法:

public void addURI(String authority, String path, int code);

這個方法接收三個引數,可以分別把許可權、路徑和一個自定義程式碼傳進去。這樣,當呼叫UriMatcher 的match()方法時,就可以將一個Uri 物件傳入,返回值是某個能夠匹配這個Uri 物件所對應的自定義程式碼,利用這個程式碼,我們就可以判斷出呼叫方期望訪問的是哪張表中的資料了。修改MyContentProvider中的程式碼,如下所示:

public class MyContentProvider extends ContentProvider {
    public static final int TABLE1_DIR = 0;
    public static final int TABLE1_ITEM = 1;
    public static final int TABLE2_DIR = 2;
    public static final int TABLE2_ITEM = 3;
    private static UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        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_ITEM);
        uriMatcher.addURI("com.example.app.provider ", "table2/#", TABLE2_ITEM);
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        switch (uriMatcher.match(uri)) {
            case TABLE1_DIR:
                // 查詢table1表中的所有資料
                break;
            case TABLE1_ITEM:
                // 查詢table1表中的單條資料
                break;
            case TABLE2_DIR:
                // 查詢table2表中的所有資料
                break;
            case TABLE2_ITEM:
                // 查詢table2表中的單條資料
                break;
            default:
                break;
        }
        ......
    }
    ......
}

可以看到,MyProvider 中新增了四個整型常量:
(1)TABLE1_DIR 表示訪問table1 表中的所有資料;
(2)TABLE1_ITEM 表示訪問table1 表中的單條資料;
(3)TABLE2_DIR 表示訪問table2 表中的所有資料;
(4)TABLE2_ITEM 表示訪問table2 表中的單條資料。

接著在靜態程式碼塊裡我們建立了UriMatcher 的例項,並呼叫addURI()方法,將期望匹配的內容URI 格式傳遞進去,注意這裡傳入的路徑引數是可以使用萬用字元的。

然後當query()方法被呼叫的時候,就會通過UriMatcher 的match()方法對傳入的Uri 物件進行匹配,如果發現UriMatcher 中某個內容URI 格式成功匹配了該Uri 物件,則會返回相應的自定義程式碼,然後我們就可以判斷出呼叫方期望訪問的到底是什麼資料了。

上述程式碼只是以query()方法為例做了個示範,其實insert()、update()、delete()這幾個方法的實現也是差不多的,它們都會攜帶Uri 這個引數,然後同樣利用UriMatcher 的match()方法判斷出呼叫方期望訪問的是哪張表,再對該表中的資料進行相應的操作就可以了。

除此之外,還有一個方法你會比較陌生,即getType()方法。它是所有的內容提供器都必須提供的一個方法,用於獲取Uri 物件所對應的MIME 型別。一個內容URI 所對應的MIME字串主要由三部分組分,Android 對這三個部分做了如下格式規定。
(1) 必須以vnd 開頭。
(2)如果內容URI 以路徑結尾,則後接android.cursor.dir/,如果內容URI 以id 結尾,則後接android.cursor.item/
(3)最後接上vnd.<authority>.<path>

所以,對於content://com.example.app.provider/table1 這個內容URI,它所對應的MIME型別就可以寫成:

vnd.android.cursor.dir/vnd.com.example.app.provider.table1

對於content://com.example.app.provider/table1/1 這個內容URI,它所對應的MIME 型別就可以寫成:

vnd.android.cursor.item/vnd. com.example.app.provider.table1

現在我們可以繼續完善MyContentProvider中的內容了,這次來實現getType()方法中的邏輯,程式碼如下所示:

    @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;
    }

到這裡,一個完整的內容提供器就建立完成了,現在任何一個應用程式都可以使用ContentResolver 來訪問我們程式中的資料。那麼前面所提到的,如何才能保證隱私資料不會洩漏出去呢?
其實多虧了內容提供器的良好機制,這個問題在不知不覺中已經被解決了。因為所有的CRUD 操作都一定要匹配到相應的內容URI 格式才能進行的,而我們當然不可能向UriMatcher 中新增隱私資料的URI,所以這部分資料根本無法被外部程式訪問到,安全問題也就不存在了。

(二)實現跨程式資料共享

我們建立一個簡單的MyBook專案,有插入資料和查詢的功能,然後通過內容提供器來給它加入外部訪問介面。

首先來看佈局檔案activity_main.xml,就簡單的兩個按鈕:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.wz.mybook.MainActivity">

    <Button
        android:text="插入資料"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="74dp"
        android:id="@+id/insert_button" />

    <Button
        android:text="查詢資料"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="31dp"
        android:id="@+id/query_button"
        android:layout_below="@+id/insert_button"
        android:layout_alignStart="@+id/insert_button" />

</RelativeLayout>

然後是MainActivity:

package com.wz.mybook;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private MyDatabaseHelper databaseHelper;

    private Button insertBtn,queryBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        insertBtn = (Button) findViewById(R.id.insert_button);
        insertBtn.setOnClickListener(this);
        queryBtn = (Button) findViewById(R.id.query_button);
        queryBtn.setOnClickListener(this);

        databaseHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.insert_button:
                insert();
                break;
            case R.id.query_button:
                query();
                break;
            default:
                break;
        }
    }

    /**
     * 插入資料
     */
    private void insert() {
        SQLiteDatabase db = databaseHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        //插入第一條資料
        values.put("name", "Java開發");
        values.put("author", "小明");
        values.put("pages", 454);
        values.put("price", 49.9);
        db.insert("Book", null, values);

        values.clear();
        //插入第二條資料
        values.put("name", "Android開發");
        values.put("author", "小李");
        values.put("pages", 696);
        values.put("price", 69.9);
        db.insert("Book", null, values);
    }

    /**
     * 查詢資料
     */
    private void query() {
        SQLiteDatabase database = databaseHelper.getWritableDatabase();
        Cursor cursor = null;
        try {
            cursor = database.query("Book", null, null, null, null, null, null);
            while (cursor != null && cursor.moveToNext()) {
                String name = cursor.getString(cursor.getColumnIndex("name"));
                String author = cursor.getString(cursor.getColumnIndex("author"));
                int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                double price = cursor.getDouble(cursor.getColumnIndex("price"));
                Log.d("MainActivity", "book name is " + name);
                Log.d("MainActivity", "book author is " + author);
                Log.d("MainActivity", "book pages is " + pages);
                Log.d("MainActivity", "book price is " + price);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(cursor != null){
                cursor.close();
            }
        }
    }
}

其中資料庫操作類MyDatabaseHelper讓其繼承SQLiteOpenHelper:

package com.wz.mybook;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

public class MyDatabaseHelper extends SQLiteOpenHelper {

    public static final String CREATE_BOOK = "create table Book ("
            + "id integer primary key autoincrement, "
            + "author text, "
            + "price real, "
            + "pages integer, "
            + "name text)";

    public static final String CREATE_CATEGORY = "create table Category ("
            + "id integer primary key autoincrement, "
            + "category_name text, "
            + "category_code integer)";

    public MyDatabaseHelper(Context context, String name,
                            CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("drop table if exists Book");
        db.execSQL("drop table if exists Category");
        onCreate(db);
    }

}

最主要的是通過內容提供器來給它加入外部訪問介面:

package com.wz.mybook;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

public class MyBookProvider extends ContentProvider {
    public static final int BOOK_DIR = 0;

    public static final int BOOK_ITEM = 1;

    public static final int CATEGORY_DIR = 2;

    public static final int CATEGORY_ITEM = 3;

    public static final String AUTHORITY = "com.wz.mybook.provider";

    private static UriMatcher uriMatcher;

    private MyDatabaseHelper dbHelper;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
        uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
    }


    @Override
    public boolean onCreate() {
        dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
        return true;

    }

    @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:
                cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case BOOK_ITEM:
                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;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        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;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        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;
    }

    @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;
    }


    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.com.wz.mybook.provider.book";
            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.com.wz.mybook.provider.book";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.wz.mybook.provider.category";
            case CATEGORY_ITEM:
                return "vnd.android.cursor.item/vnd.com.wz.mybook.provider.category";
        }
        return null;
    }

}

我們來分析這個類:
首先在類的一開始,同樣是定義了四個常量,分別用於表示訪問Book 表中的所有資料、訪問Book 表中的單條資料、訪問Category 表中的所有資料和訪問Category 表中的單條資料。然後在靜態程式碼塊裡對UriMatcher 進行了初始化操作,將期望匹配的幾種URI 格式添加了進去。

接下來就是每個抽象方法的具體實現了,先來看下onCreate()方法,這個方法的程式碼很短,就是建立了一個MyDatabaseHelper 的例項,然後返回true 表示內容提供器初始化成功,這時資料庫就已經完成了建立或升級操作。

接著看一下query()方法,在這個方法中先獲取到了SQLiteDatabase 的例項,然後根據傳入的Uri 引數判斷出使用者想要訪問哪張表,再呼叫SQLiteDatabase 的query()進行查詢,並將Cursor 物件返回就好了。注意當訪問單條資料的時候有一個細節,這裡呼叫了Uri 物件的getPathSegments()方法,它會將內容URI 許可權之後的部分以“/”符號進行分割,並把分割後的結果放入到一個字串列表中,那這個列表的第0 個位置存放的就是路徑,第1 個位置存放的就是id 了。得到了id 之後,再通過selection 和selectionArgs 引數進行約束,就實現了查詢單條資料的功能。

再往後就是insert()方法,同樣它也是先獲取到了SQLiteDatabase 的例項,然後根據傳入的Uri 引數判斷出使用者想要往哪張表裡新增資料,再呼叫SQLiteDatabase 的insert()方法進行新增就可以了。注意insert()方法要求返回一個能夠表示這條新增資料的URI,所以我們還需要呼叫Uri.parse()方法來將一個內容URI 解析成Uri 物件,當然這個內容URI 是以新增資料的id 結尾的。

接下來就是update()方法了,也是先獲取SQLiteDatabase 的例項,然後根據傳入的Uri 引數判斷出使用者想要更新哪張表裡的資料,再呼叫SQLiteDatabase 的update()方法進行更新就好了,受影響的行數將作為返回值返回。

下面是delete()方法,這裡仍然是先獲取到SQLiteDatabase 的例項,然後根據傳入的Uri 引數判斷出使用者想要刪除哪張表裡的資料,再呼叫SQLiteDatabase 的delete()方法進行刪除就好了,被刪除的行數將作為返回值返回。

最後是getType()方法。

最後的最後,還需要將內容提供器在AndroidManifest.xml 檔案中註冊:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wz.mybook">

    <permission android:name="com.wz.permission.READ_CONTENTPROVIDER"/>
    <permission android:name="com.wz.permission.WRITE_CONTENTPROVIDER"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <provider
            android:name=".MyBookProvider"
            android:authorities="com.wz.mybook.provider"
            android:enabled="true"
            android:exported="true"></provider>
    </application>

</manifest>

可以看到,這裡我們使用了<provider>標籤來對MyBookProvider這個內容提供器進行註冊,在android:name 屬性中指定了該類的全名,又在android:authorities 屬性中指定了該內容提供器的許可權。

注意:
(1)android:exported屬性非常重要。

android:exported="true"

這個屬性用於指示該服務是否能夠被其他應用程式元件呼叫或跟它互動。如果設定為true,則能夠被呼叫或互動,否則不能。設定為false時,只有同一個應用程式的元件或帶有相同使用者ID的應用程式才能啟動或繫結該服務。如果content provider允許其他應用呼叫,即允許其他程序呼叫,需要將該屬性設定為true。

(2)在Android 6.0上需要自定義讀寫許可權,將provider暴露出去:

    <permission android:name="com.wz.permission.READ_CONTENTPROVIDER"/>
    <permission android:name="com.wz.permission.WRITE_CONTENTPROVIDER"/>

現在MyBook這個專案就已經擁有了跨程式共享資料的功能了。

看一下效果:
1

點選【插入資料】按鈕,此時,應該已經插入了兩條資料,點選【查詢資料】,可以看到:

10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book name is Java開發
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book author is 小明
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book pages is 454
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book price is 49.9
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book name is Android開發
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book author is 小李
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book pages is 696
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book price is 69.9

說明OK了。

下面,我們將建立一個新專案MyProviderTest,通過這個程式去訪問MyBook中的資料。

首先,編寫佈局檔案activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="wz.com.myprovidertest.MainActivity">

    <Button
        android:text="向MyBook新增資料"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true"
        android:layout_marginStart="134dp"
        android:layout_marginTop="56dp"
        android:id="@+id/add_btn"
        android:elevation="0dp" />

    <Button
        android:text="從MyBook中查詢資料"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/add_btn"
        android:layout_alignStart="@+id/add_btn"
        android:layout_marginTop="31dp"
        android:id="@+id/query_btn" />

    <Button
        android:text="更新MyBook中的資料"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/update_btn"
        android:layout_below="@+id/query_btn"
        android:layout_alignStart="@+id/query_btn"
        android:layout_marginTop="36dp" />

    <Button
        android:text="刪除MyBook中的資料"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="39dp"
        android:id="@+id/delete_btn"
        android:layout_below="@+id/update_btn"
        android:layout_alignStart="@+id/update_btn" />
</RelativeLayout>

佈局檔案很簡單,裡面放置了四個按鈕,分別用於新增、查詢、修改和刪除資料的。然後修改MainActivity 中的程式碼,如下所示:

package wz.com.myprovidertest;

import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button addBtn, queryBtn, updateBtn, deleteBtn;

    private String newId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        addBtn = (Button) findViewById(R.id.add_btn);
        queryBtn = (Button) findViewById(R.id.query_btn);
        updateBtn = (Button) findViewById(R.id.update_btn);
        deleteBtn = (Button) findViewById(R.id.delete_btn);

        addBtn.setOnClickListener(this);
        queryBtn.setOnClickListener(this);
        updateBtn.setOnClickListener(this);
        deleteBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.add_btn:
                add();
                break;
            case R.id.query_btn:
                query();
                break;
            case R.id.update_btn:
                update();
                break;
            case R.id.delete_btn:
                delete();
                break;
            default:
                break;
        }
    }

    private void add() {
        Uri uri = Uri.parse("content://com.wz.mybook.provider/book");
        ContentValues values = new ContentValues();
        values.put("name", "Python開發");
        values.put("author", "小王");
        values.put("pages", 798);
        values.put("price", 89.9);
        Uri newUri = getContentResolver().insert(uri, values);
        newId = newUri.getPathSegments().get(1);
    }

    private void query() {
        Uri uri = Uri
                .parse("content://com.wz.mybook.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 author = cursor.getString(cursor
                        .getColumnIndex("author"));
                int pages = cursor.getInt(cursor
                        .getColumnIndex("pages"));
                double price = cursor.getDouble(cursor
                        .getColumnIndex("price"));
                Log.d("MainActivity", "book name is " + name);
                Log.d("MainActivity", "book author is " + author);
                Log.d("MainActivity", "book pages is " + pages);
                Log.d("MainActivity", "book price is " + price);
            }
            cursor.close();
        }
    }

    private void update() {
        Uri uri = Uri
                .parse("content://com.wz.mybook.provider/book/" + newId);
        ContentValues values = new ContentValues();
        values.put("name", "JavaScript開發");
        values.put("pages", 1198);
        values.put("price", 129.9);
        getContentResolver().update(uri, values, null, null);
    }

    private void delete() {
        Uri uri = Uri
                .parse("content://com.wz.mybook.provider/book/" + newId);
        getContentResolver().delete(uri, null, null);
    }
}

可以看到,我們分別在這四個按鈕的點選事件裡面處理了增刪改查的邏輯。
(1)新增資料的時候,首先呼叫了Uri.parse()方法將一個內容URI 解析成Uri 物件,然後把要新增的資料都存放到ContentValues 物件中,接著呼叫ContentResolver 的insert()方法執行新增操作