Android探索之ContentProvider熟悉而又陌生的元件

總李寫程式碼發表於2016-05-19

前言:

    總結這篇文章之前我們先來回顧一下Android Sqlite資料庫,參考文章:http://www.cnblogs.com/whoislcj/p/5506294.html,Android程式內部資料儲存如果使用Sqlite資料庫,那麼Android 如何實現程式間資料共享?Android 提供了一種機制可以實現程式間的資料共享,它就是Android 四大元件之一ContentProvider,Android為儲存和獲取資料提供統一的介面,用於實現程式間資料共享,不要將其理解為資料庫。

     為什麼說是熟悉又陌生呢?因為我們經常使用到,Android內建的許多資料都是採用ContentProvider,比如圖片,視訊,音訊,手機聯絡人等,至於陌生那是因為我很少自己去實現一個ContentProvider,今天我們重點是來實現一個自定義ContentProvider。

ContentProvider類簡介:

     1.) 我們一般要繼承ContentProvider,那麼要實現那些函式呢?
  • ContentProvider()   建構函式
  • onCreate()    建立資料時呼叫的回撥函式
  • insert()      插入資料
  • delete()     刪除資料
  • update()    更新資料
  • query()      查詢資料
  • getType()  得到資料型別
   2.)URI簡介:

     ContentProvider通過URI來訪問資料執行增刪改查的操作,一個完整的URI有 content://自定義ContentProvider/xxx資料庫名稱 

     我們先宣告一個作用域:

    //訪問URI作用域
    public static final String CONTENT_URI="com.whoislcj.testsqlite.personprovider";

    對應URI舉例說明一下:

  • content://com.whoislcj.testsqlite.personprovider/person   返回person所以資料
  • content://com.whoislcj.testsqlite.personprovider/person/10 返回id為10的person資料
   3.)UriMatcher簡介

       主要用於匹配Uri,為什麼要匹配Uri呢?通過上面的Uri舉例可以看出操作域不一樣,對於執行一個delete、update、query來說我們要獲取引數引數執行不能對應操作。

   使用:

  //定義一個UriMatcher類物件,用來匹配Uri的。
    private static final UriMatcher uriMatcher;
    //集合操作
    public static final int INCOMING_COLLECTION = 1;
    //單個ID操作
    public static final int INCOMING_SIGNAL = 2;
    static {
        //常量UriMatcher.NO_MATCH表示不匹配任何路徑的返回碼
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        //如果match()方法匹配com.whoislcj.testsqlite.personprovider/person路徑,返回匹配碼為1
        uriMatcher.addURI(CONTENT_URI, "person", INCOMING_COLLECTION);//新增需要匹配uri,如果匹配就會返回匹配碼
        //如果match()方法匹配content://com.ljq.provider.personprovider/person/230路徑,返回匹配碼為2
        uriMatcher.addURI(CONTENT_URI, "person/#", INCOMING_SIGNAL);//#號為萬用字元
    }
4.)ContentUris簡介

      ContentUris是對URI的操作類,比如獲取URI路徑裡的引數,或者給URI拼接一個引數

    舉例說明:

  • long id = ContentUris.parseId(uri);//從uri中獲取id
  • Uri rowUri = ContentUris.withAppendedId(uri, rowId);//uri追加id 生成該條資料完整的URI地址
  5.)ContentResolver簡介   

       ContentResolver主要用於為外部程式提供增刪改查的操作函式,也可以註冊觀察者來監聽資料的變化。

 6.)自定義ContentProvider具體實現:
public class PersonProvider extends ContentProvider {
    // DatabaseHelper操作控制程式碼
    private DBHelper dbHelper;
    //訪問URI
    public static final String CONTENT_URI="com.whoislcj.testsqlite.personprovider";
    // 資料集的MIME型別字串則應該以vnd.android.cursor.dir/開頭
    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/person";
    // 單一資料的MIME型別字串應該以vnd.android.cursor.item/開頭
    public static final String CONTENT_TYPE_ITME = "vnd.android.cursor.item/person";
    //定義一個UriMatcher類物件,用來匹配Uri的。
    private static final UriMatcher uriMatcher;
    //集合操作
    public static final int INCOMING_COLLECTION = 1;
    //單個ID操作
    public static final int INCOMING_SIGNAL = 2;
    static {
        //常量UriMatcher.NO_MATCH表示不匹配任何路徑的返回碼
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        //如果match()方法匹配com.whoislcj.testsqlite.personprovider/person路徑,返回匹配碼為1
        uriMatcher.addURI(CONTENT_URI, "person", INCOMING_COLLECTION);//新增需要匹配uri,如果匹配就會返回匹配碼
        //如果match()方法匹配content://com.ljq.provider.personprovider/person/230路徑,返回匹配碼為2
        uriMatcher.addURI(CONTENT_URI, "person/#", INCOMING_SIGNAL);//#號為萬用字元
    }

    public PersonProvider() {
    }

    /**
     * 回撥函式,在ContentProvider建立的時候,就會執行
     * 作用獲取操作使用者的控制程式碼
     */
    @Override
    public boolean onCreate() {
        //這裡會呼叫 DBHelper的建構函式建立一個資料庫;
        dbHelper = new DBHelper(getContext());
        return true;
    }

    /**
     * 執行插入資料函式
     *
     * @param uri
     * @param values
     * @return
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        //獲取一個可寫的資料庫
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        //呼叫資料庫的插入操作 也可以自己構造sql語句 執行  db.execSQL();相對比較麻煩
        long rowId = db.insert(DBHelper.TABLE_NAME, "", values);
        //判斷是否插入成功
        if (rowId > 0) {
            Uri rowUri = ContentUris.withAppendedId(uri, rowId);//uri追加id 生成該條資料完整的URI地址
            getContext().getContentResolver().notifyChange(uri, null);
            return rowUri;
        }
        throw new SQLException("Failed to insert row" + uri);
    }

    /**
     * 刪除資料操作
     *
     * @param uri
     * @param selection
     * @param selectionArgs
     * @return
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        //獲取一個可寫的資料庫
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count = 0;
        switch (uriMatcher.match(uri)) {
            case INCOMING_COLLECTION:
                //執行刪除操作
                count = db.delete(DBHelper.TABLE_NAME, selection, selectionArgs);
                getContext().getContentResolver().notifyChange(uri, null);
                break;
            case INCOMING_SIGNAL:
                long id = ContentUris.parseId(uri);//從uri中獲取id
                String where = "id=" + id; // 刪除指定id的記錄
                where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : ""; // 把其它條件附加上
                count = db.delete(DBHelper.TABLE_NAME, where, selectionArgs);
                getContext().getContentResolver().notifyChange(uri, null);
                break;
            default:
                throw new SQLException("Failed to delete row " + uri);
        }
        //關閉資料庫
        db.close();
        return count;
    }

    /**
     * 更新資料操作
     *
     * @param uri
     * @param values
     * @param selection
     * @param selectionArgs
     * @return
     */
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        //獲取一個可寫的資料庫
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int count = 0;
        switch (uriMatcher.match(uri)) {
            case INCOMING_COLLECTION:
                //執行更新資料
                count = db.update(DBHelper.TABLE_NAME, values, selection, selectionArgs);
                getContext().getContentResolver().notifyChange(uri, null);
                break;
            case INCOMING_SIGNAL:
                long id = ContentUris.parseId(uri);//從uri中獲取id
                String where = "id=" + id;    // 刪除指定id的記錄
                where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : "";// 把其它條件附加上
                //執行更新資料
                count = db.update(DBHelper.TABLE_NAME, values, where, selectionArgs);
                getContext().getContentResolver().notifyChange(uri, null);
                break;
            default:
                throw new SQLException("Failed to update row " + uri);
        }
        //關閉資料庫
        db.close();
        return count;
    }

    /**
     * 查詢操作
     *
     * @param uri
     * @param projection
     * @param selection
     * @param selectionArgs
     * @param sortOrder
     * @return
     */
    @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 INCOMING_COLLECTION:
                //執行查詢
                cursor = db.query(DBHelper.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case INCOMING_SIGNAL:
                long id = ContentUris.parseId(uri);//從uri中獲取id
                String where = "id=" + id;    // 刪除指定id的記錄
                where += !TextUtils.isEmpty(selection) ? " and (" + selection + ")" : "";// 把其它條件附加上
                cursor = db.query(DBHelper.TABLE_NAME, projection, where, selectionArgs, null, null, sortOrder);
                break;
            default:
                throw new SQLException("Failed to query " + uri);
        }
        return cursor;
    }

    /**
     * 該方法用於返回當前Url所代表資料的MIME型別。
     *
     * @param uri
     * @return
     */
    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case INCOMING_COLLECTION:
                return CONTENT_TYPE;
            case INCOMING_SIGNAL:
                return CONTENT_TYPE_ITME;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
    }
}
View Code
7.)外部如何訪問
        ContentResolver resolver = getContentResolver();
        Uri uri = Uri.parse("content://com.whoislcj.testsqlite.personprovider/person");
        //新增一條記錄
        ContentValues values = new ContentValues();
        values.put("name", "whoislcj");
        resolver.insert(uri, values);

        //更新一條資料
        ContentValues updateValues = new ContentValues();
        updateValues.put("name", "lcj");
        //組合
        resolver.update(uri, updateValues, "id=?", new String[]{"2"});
        //單個
        Uri updateIdUri = ContentUris.withAppendedId(uri, 5);
        resolver.update(updateIdUri, updateValues, null, null);
        //刪除person表指定資料
        Uri deleteIdUri = ContentUris.withAppendedId(uri, 5);
        resolver.delete(deleteIdUri, null, null);

        //獲取person表指定資料
        Uri tempUri = ContentUris.withAppendedId(uri, 5);
        Cursor cursor = resolver.query(tempUri, null, null, null, "id asc");
        while (cursor.moveToNext()) {
            Log.e("testContentProvider", "signal id=" + cursor.getInt(0) + ",name=" + cursor.getString(1));
        }
        cursor.close();

        //獲取person表中所有記錄
        cursor = resolver.query(uri, null, null, null, "id asc");
        while (cursor.moveToNext()) {
            Log.e("testContentProvider", "id=" + cursor.getInt(0) + ",name=" + cursor.getString(1));
        }
        cursor.close();
View Code
8.)如何監聽資料變化

需要註冊一個自定義的觀察者,當時如下

     // 為uri的資料改變註冊監聽器
        getContentResolver().registerContentObserver(
                Uri.parse("content://com.whoislcj.testsqlite.personprovider/person"), true,
                new Observer(new Handler()));

    // 提供方自定義的ContentOberver監聽器
    private final class Observer extends ContentObserver {

        public Observer(Handler handler) {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            // 查詢傳送郵箱中的短息(處於正在傳送狀態的簡訊放在傳送箱)
            Log.e("MainActivity", "onChange--->uri :" + uri.toString());
        }
    }

同樣資料操作位置也需要執行如下程式碼

getContext().getContentResolver().notifyChange(uri, null);
9.)訪問許可權控制

宣告讀寫自定義許可權

    <permission android:name="com.whoislcj.testsqlite.personprovider.read" />
    <permission android:name="com.whoislcj.testsqlite.personprovider.write" />

    <uses-permission android:name="com.whoislcj.testsqlite.personprovider.read" />
    <uses-permission android:name="com.whoislcj.testsqlite.personprovider.write" />

ContentProvider註冊宣告:

       <provider
            android:name=".PersonProvider"
            android:authorities="com.whoislcj.testsqlite.personprovider"
            android:enabled="true"
            android:exported="true"
            android:readPermission="com.whoislcj.testsqlite.personprovider.read"
            android:writePermission="com.whoislcj.testsqlite.personprovider.write">
        </provider>

 10.)關於getTpye

        ContentProvider裡面一個getType ()函式很多人不知道 這個幹嘛的,接下來介紹一下,

    // 資料集的MIME型別字串則應該以vnd.android.cursor.dir/開頭
    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/person";
    // 單一資料的MIME型別字串應該以vnd.android.cursor.item/開頭
    public static final String CONTENT_TYPE_ITME = "vnd.android.cursor.item/person";

    /**
     * 該方法用於返回當前Url所代表資料的MIME型別。
     *
     * @param uri
     * @return
     */
    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case INCOMING_COLLECTION:
                return CONTENT_TYPE;
            case INCOMING_SIGNAL:
                return CONTENT_TYPE_ITME;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
    }

假設我們在專案搞了一個聯絡人列表Activity,我們需要外面來訪問這個Activity,首先看下這個Activity的註冊宣告:

<activity android:name=".TestActivity" android:icon="@mipmap/ic_launcher">
            <intent-filter>
                <action android:name="com.whoislcj.testsqlite.personprovider" />
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:mimeType="vnd.android.cursor.dir/person" />
            </intent-filter>

        </activity>

看到上面的mimeType:vnd.android.cursor.dir/person

外部如何啟動呢:

    Uri uri = Uri.parse("content://com.whoislcj.testsqlite.personprovider/person");
                Intent intent = new Intent();
                intent.setAction("com.whoislcj.testsqlite.personprovider");
                intent.setData(uri);
                startActivity(intent);

這樣以來系統會去呼叫你定義的ContentProvider中的getType,去匹配出相應的Activity來實現跳轉。

相關文章