Content Provider 之 最終彈 實戰(zhàn)體驗跨程序數據共享(結合SQLiteDemo)


本模塊共有四篇文章,參考郭神的《第一行代碼》,對Content Provider的學習做一個詳細的筆記,大家可以一起交流一下:


簡單起見,我們還是在之前的DatabaseTest項目(點擊前往碼云地址)的基礎上繼續(xù)開發(fā)。
需求是:通過內容提供器來給它加人外部訪問接口。

程序設計的步驟:

1.在A程序中注冊內容提供器,寫好接口處理方法;
      具體的,全局變量:定義自定義代碼常量,定義authority常量,聲明uriMatcher和DatabaseHelper對象;
      1.1 內容提供器中增刪改查的程序設計步驟為:
            1.1.1  調用實例化DatabaseHelper的get方法獲得SQLiteDatabase實例化對象;
                      (get方法即getWritableDatabase或者getReadableDatabase)
            1.1.2   接著,query需定義一個Cursor對象(cursor)用于接收返回結果;
                                  insert需定義一個Uri對象(urireturn)用于接收insert新增的數據行的uri
                                  update需定義一個int對象(updatedRows)用于接收受影響的行數;
                                  delete需定義一個int對象(deletedRows)用于接收受影響的行數(被刪除的行數);
            1.1.3  使用switch語句進行對uri的判斷及判斷結果的處理;
2.在需要訪問A程序的內容提供器的程序中,構建對應的Uri,通過getContentResolver調用增刪改查即可;


下面開始詳細解析:
打開DatabaseTest項目(點擊前往碼云地址),首先將MyDatabaseHelper中使用Toast彈出創(chuàng)建數據庫成功的提示去除掉,因為跨程序訪問時我們不能直接使用Toast(?。。。。?。然后創(chuàng)建一個內容提供器,右擊com.example.databasetest包—New—Other—ContentProvider,



會彈出如圖所示:

這里我們將內容提供器命名為DatabaseProvider,
authority指定為com.example.databasetest.provider,
Exported屬性表示是否允許外部程序訪問我們的內容提供器,
Enabled屬性表示是否啟用這個內容提供器。
將兩個屬性都勾中,點擊Finish完成創(chuàng)建。
接著我們修改DatabaseProvider:

package com.example.databasetest;

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 DatabaseProvider 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.example.databasetest.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 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);          //插入后返回一個id!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);//id構造用于返回的URI!!!!!!!!!!!!!!!!!!!!!!
                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 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 String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest. provider.book";
            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.databasetest. provider.book";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest. provider.category";
            case CATEGORY_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.databasetest. provider.category";
        }
        return null;
    }

}
  • 代碼雖然很長,不過都不難理解,都是上一節(jié)學習過的內容。
    首先在類的一開始,同樣是定義了4個常量,分別用于表示
    訪問Book表中的所有數據;
    訪問Book表中的單條數據;
    訪問Category表中的所有數據;
    訪問Category表中的單條數據。
    然后在靜態(tài)代碼塊里對UriMatcher進行了初始化操作,將期望匹配的幾種URI格式添加了進去。

  • 接下來是onCreate()方法:
    創(chuàng)建了一個MyDatabaseHelper的實例,然后返回true表示內容提供器初始化成功,這時數據庫就已經完成了創(chuàng)建或升級操作。

  • 接著是query()方法:
    首先獲取到SQLiteDatabase實例,然后根據傳入的Uri參數判斷出用戶想要訪問哪張表,
    再調用SQLiteDatabase的query()進行查詢,并將Cursor對象返回即可。
    注意當訪問單條數據的時候有一個細節(jié):
    這里調用了Uri對象的getpathSegments()方法,它會將內容URI權限之后的部分以“/“符號進行分割,并把分割后
    的結果放入到一個字符串列表中,那這個列表的第0個位置存放的就是路徑,第1個位置存放的就是id了。
    得到了id之后,再通過selection和selectionArgs參數進行約束,就實現了查詢單條數據的功能。

getPathSegments().get(1)的解釋參考:

  • 然后是insert()方法:
    同樣先獲取到SQLiteDatabase實例,
    然后根據傳入的Uri參數判斷出用戶想要往哪張表里添加數據,
    再調用SQLiteDatabase的insert()方法進行添加即可。
    注意insert()方法要求返回一個能夠表示這條新增數據的URI,則這里還需調用Uri.parse()方法來將一個內容URI解析成Uri對象,當然這個內容URI是以新增數據的id結尾的。

  • 接著是update()方法:
    先獲取SQLiteDatabase實例,
    然后根據傳入的Uri參數判斷出用戶想要更新哪張表里的數據,
    再調用SQLiteDatabase的update()方法進行更新即可,受影響的行數作為返回值返回。

  • 然后是delete()方法:
    先獲取到SQLiteDatabase的實例,
    然后根據傳入的Uri參數判斷出用戶想要刪除哪張表里的數據,
    再調用SQLiteDatabase的delete()方法進行刪除即可,被刪除的行數作為返回值返回。

  • 最后是getType()方法,這里按照上一節(jié)中介紹的格式規(guī)則編寫即可。

至此內容提供器中的代碼便全部編寫完了。

另外!!內容提供器一定要在AndroidMamfest.xml文件中注冊才可以使用,
不過使用Androidstudio的快捷方式創(chuàng)建內容提供器的話,注冊會被自動完成。
打開AndroidManifest.xmI文件看一下:

可以看到<application>標簽內出現了一個新的標簽<provider>,我們使用它來注冊內容提供器DatabaseProvider。
其中android:name指定DatabaseProvider的類名,
android:authorities指定了DatabaseProvider的authority,
enabled和exported屬性則是根據我們剛才勾選的狀態(tài)自動生成的。

現在DatabaseTest這個項目便具備跨程序共享數據的功能了。
可以調試一下:
首先將DatabaseTest程序從模擬器中刪除掉,防止遺留數據的造成干擾。
然后運行一下項目,將DatabaseTest程序重新安裝在模擬器上。
接著關閉掉DatabaseTest這個項目,并創(chuàng)建一個新項目ProviderTest,
接著通過這個程序去訪問DatabaseTest中的數據,

先編寫布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button
        android:id="@+id/add_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add To Book" />

    <Button
        android:id="@+id/query_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Query From Book" />

    <Button
        android:id="@+id/update_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Update Book" />

    <Button
        android:id="@+id/delete_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Delete From Book" />

</LinearLayout>

MainActivity.java:

package com.example.providertest;

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

public class MainActivity extends AppCompatActivity {

    private String newId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button addData = (Button) findViewById(R.id.add_data);
        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 添加數據
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
                ContentValues values = new ContentValues();
                values.put("name", "A Clash of Kings");
                values.put("author", "George Martin");
                values.put("pages", 1040);
                values.put("price", 55.55);
                Uri newUri = getContentResolver().insert(uri, values);
                newId = newUri.getPathSegments().get(1);
            }
        });
        Button queryData = (Button) findViewById(R.id.query_data);
        queryData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 查詢數據
                Uri uri = Uri.parse("content://com.example.databasetest.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();
                }
            }
        });
        Button updateData = (Button) findViewById(R.id.update_data);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 更新數據
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
                ContentValues values = new ContentValues();
                values.put("name", "A Storm of Swords");
                values.put("pages", 1216);
                values.put("price", 24.05);
                getContentResolver().update(uri, values, null, null);
            }
        });
        Button deleteData = (Button) findViewById(R.id.delete_data);
        deleteData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 刪除數據
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
                getContentResolver().delete(uri, null, null);
            }
        });
    }
}

其中值得注意的是:

從以上代碼中,我們可以看到DIR類型常量匹配的,selection, selectionArgs參數位是由調用本內容提供器的時候由調用方程序提供的,
ITEM則不同,uri中已經包含了ID,我們可以使用getPathSegments將之get出來再使用,即ITEM類型常量匹配的,調用方程序無需提供selection, selectionArgs參數位(如下方的代碼截圖),uri中已經包含了信息,處理方法也在內容提供器中寫好了。
(這一點在調用getContentResolver().update()以及getContentResolver().delete()的時候都一樣)

下面進行代碼的簡析:
分別在這4個按鈕的點擊事件里面處理了增刪改查的邏輯:

  • 添加數據的時候:
    首先調用Uri.parse()將內容URI解析成Uri對象,
    把要添加的數據存放到ContentValues對象中,
    調用ContentResolver的insert()方法執(zhí)行添加操作即可。
    注意insert()會返回一個Uri對象,這個對象中包含了新增數據的id,這里用getPathSegments()將這個id取出,稍后會用到它;

  • 查詢數據的時候:
    調用Uri.parse()將內容URI解析成Uri對象,
    調用ContentResolver的query()方法去查詢數據,
    查詢的結果存放在Cursor對象中,
    對Cursor進行遍歷,從中取出查詢結果,并一一打印出來;

  • 更新數據的時候:
    調用Uri.parse()將內容URI解析成Uri對象,
    把想要更新的數據存放到ContentValues對象中,
    調用ContentResolver的update()方法執(zhí)行更新操作即可;
    注意這里為了不讓Book表中的其他行受到影響,
    在調用Uri.parse()方法時,給內容URI的尾部增加了一個id,而這個id正是添加數據時所返回的。
    也就是說這里只更新剛剛添加的那條數據,不受影響B(tài)ook表中的其他行。

  • 刪除數據的時候,
    解析一個以id結尾的內容URI,
    調用ContentResolver的delete()方法執(zhí)行刪除操作就可以了,
    由于我們在內容URI里指定了一個id,因此只會刪掉擁有相應id的那行數據,不會影響B(tài)ook表中的其他數據。

現在運行一下ProviderTest項目,效果圖如下:


點擊一下AddToBook按鈕,此時數據便已經添加到DatabaseTest程序的數據庫中了,
可以點擊QueryFromBook按鈕來檢查一下,打印日志如圖:

這里可以看到DatabaseTest程序中只有我們剛剛添加的一條數據,
databaseTest的SQLite數據庫是我們在點擊Add To Book的時候,試圖訪問DatabaseTest的內容提供器,由此DatabaseTest的內容提供器(DatabaseProvider)會觸發(fā)DatabaseProvider.java中的onCreate()方法,如下,
由此創(chuàng)建了數據庫,并返回True,這一點在 跨程序共享數據——Content Provider 之 創(chuàng)建自己的內容提供器中曾經有提及;
創(chuàng)建了數據庫之后,便添加了添加了一條數據,由此DatabaseTest程序中只有我們剛剛添加的那一條數據而已。

點擊一下Update Book按鈕來更新數據,再點擊一下Query From Book按鈕進行檢查,結果如圖:

最后點擊Delete From Book按鈕刪除數據,此時再點擊Query From Book按鈕就查詢不到數據了。

至此跨程序共享數據功能便成功實現了。

現在不僅是ProviderTest程序,任何一個程序都可以輕松訪問DatabaseTest中的數據,同時絲毫不用擔心隱私數據泄漏的問題。

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

友情鏈接更多精彩內容