Android——ContentProvider 內(nèi)容提供者

描述

為了在應(yīng)用程序之間交換數(shù)據(jù)音五,Android提供了ContentProvider羔沙,它是不同應(yīng)用程序之間進(jìn)行數(shù)據(jù)交換的標(biāo)準(zhǔn)API躺涝,當(dāng)一個應(yīng)用程序需要把自己的數(shù)據(jù)暴露給其他程序使用時,該應(yīng)用程序就可通過提供ContentProvider來實現(xiàn)坚嗜;其他應(yīng)用程序就可通過ContentResolver來操作ContentResolver暴露的數(shù)據(jù)诗充。

ContentProvider以指定Uri的形式對外提供數(shù)據(jù)苍蔬,允許其他應(yīng)用訪問或修改數(shù)據(jù);其他應(yīng)用程序使用ContentResolver根據(jù)Uri去訪問操作指定數(shù)據(jù)蝴蜓。

一旦某個應(yīng)用程序通過 ContentProvider 暴露了自己的數(shù)據(jù)操作接口碟绑,那么不管該應(yīng)用程序是否啟動格仲,其他應(yīng)用程序都可通過該接口來操作該應(yīng)用程序的內(nèi)部數(shù)據(jù),包括增加數(shù)據(jù)凯肋、刪除數(shù)據(jù)缩歪、修改數(shù)據(jù)穿仪、查詢數(shù)據(jù)等幸海。

從源碼分析ContentProvider的初始化

App進(jìn)程啟動 -> ActivityThread#main() -> ActivityThread#attach() -> ActivityManagerNative#attachApplication() ->
ActivityManagerService#attachApplication() -> ActivityManagerService#generateApplicationProvidersLocked() 舟舒。
generateApplicationProvidersLocked()這個方法通過 PackageManager 去獲取解析后的應(yīng)用的清單文件中 provider 信息癌佩,為每個 provider 新建 ContentProviderRecord 作為 ActivityManagerService 端的 ContentProvider 表現(xiàn)便锨。

即ContentProvider的 onCreate()方法在ActivityThread#main()運行時間接調(diào)用,即ContentProvider是在APP啟動的時候就初始化了姚建。運行在主線程吱殉,不能做耗時的操作友雳。

ContentProvider應(yīng)用內(nèi)數(shù)據(jù)共享

1稿湿、創(chuàng)建數(shù)據(jù)庫類(使用數(shù)據(jù)庫做數(shù)據(jù)共享)

public class DbHelper extends SQLiteOpenHelper {

    // 數(shù)據(jù)庫名
    private static final String DATABASE_NAME = "pkqup.db";
    // 數(shù)據(jù)庫版本號
    private static final int DATABASE_VERSION = 1;

    // 表名
    public static final String USER_TABLE_NAME = "user";
    public static final String ADDRESS_TABLE_NAME = "address";
    public static final String SPECIAL_CHARACTER = "/#";

    //在構(gòu)造方法中指定數(shù)據(jù)庫的 數(shù)據(jù)庫名 和 數(shù)據(jù)庫版本號
    public DbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // 創(chuàng)建兩個表格:用戶表 和地址表
        try {
            db.execSQL("CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + " (" + User.INDEX   + " INTEGER PRIMARY KEY AUTOINCREMENT," + User.NAME + " TEXT, " + User.USER_ID + " TEXT)");
            db.execSQL("CREATE TABLE IF NOT EXISTS " + ADDRESS_TABLE_NAME + " (" + Address.INDEX   + " INTEGER PRIMARY KEY AUTOINCREMENT,"+ Address.NAME + " TEXT, " + Address.PHONE + " TEXT)");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        try {
            //數(shù)據(jù)庫版本發(fā)生變化押赊,刪除舊表
            db.execSQL("DROP TABLE IF EXISTS " + USER_TABLE_NAME);
            db.execSQL("DROP TABLE IF EXISTS " + ADDRESS_TABLE_NAME);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2、自定義 ContentProvider 類

public class MyContentProvider extends ContentProvider {

    private DbHelper dbHelper;
    private SQLiteDatabase db;
    private ContentResolver resolver;

    // UriMatcher類使用:在ContentProvider 中注冊URI
    private static final UriMatcher uriMatcher;

    // 用戶定義列名->數(shù)據(jù)庫列名的映射
    private static final HashMap<String, String> userHashMap;
    private static final HashMap<String, String> addressHashMap;


    // 定義ContentProvider的授權(quán)信息涕俗,即唯一標(biāo)識
    public static final String AUTHORITY = "com.pkqup.android.note";

    // 定義Uri匹配返回碼
    public static final int USER_CODE = 1;
    public static final int USER_CODE_SINGLE = 2;
    public static final int ADDRESS_CODE = 3;
    public static final int ADDRESS_CODE_SINGLE = 4;

    // 設(shè)置URI
    // Uri uri = Uri.parse("content://com.carson.provider/User/1")
    // 上述URI指向的資源是:名為 com.carson.provider 的 ContentProvider  中表名 為`User` 中的 `id`為1的數(shù)據(jù)

    // 特別注意:URI模式存在匹配通配符* 和 #

    // *:匹配任意長度的任何有效字符的字符串
    // 以下的URI 表示 匹配provider的任何內(nèi)容
    // content://com.example.app.provider/*

    // #:匹配任意長度的數(shù)字字符的字符串
    // 以下的URI 表示 匹配provider中的table表的所有行
    // content://com.example.app.provider/table/#



    // 在靜態(tài)代碼塊中初始化 UriMatcher
    static {
        // 初始化 UriMatcher
        // 常量UriMatcher.NO_MATCH 再姑,不匹配任何路徑的返回碼 即初始化時不匹配任何東西
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        // 在ContentProvider 中注冊URI(即addURI())
        uriMatcher.addURI(AUTHORITY, DbHelper.USER_TABLE_NAME, USER_CODE);
        uriMatcher.addURI(AUTHORITY, DbHelper.USER_TABLE_NAME + DbHelper.SPECIAL_CHARACTER,
                USER_CODE_SINGLE);

        uriMatcher.addURI(AUTHORITY, DbHelper.ADDRESS_TABLE_NAME, ADDRESS_CODE);
        uriMatcher.addURI(AUTHORITY, DbHelper.ADDRESS_TABLE_NAME + DbHelper.SPECIAL_CHARACTER,
                ADDRESS_CODE_SINGLE);
        // 若URI資源路徑 = content://com.pkqup.android.note/user 询刹,則返回注冊碼USER_CODE,
        // 即 mMatcher.match(User.USER_CONTENT_URI)的返回值
        // 若URI資源路徑 = content://com.pkqup.android.note/address ,則返回注冊碼ADDRESS_CODE凹联,
        // 即 mMatcher.match(Address.ADDRESS_CONTENT_URI)的返回值

        userHashMap = new HashMap<>();
        userHashMap.put(User.INDEX, User.INDEX);
        userHashMap.put(User.NAME, User.NAME);
        userHashMap.put(User.USER_ID, User.USER_ID);

        addressHashMap = new HashMap<>();
        addressHashMap.put(Address.INDEX, Address.INDEX);
        addressHashMap.put(Address.NAME, Address.NAME);
        addressHashMap.put(Address.PHONE, Address.PHONE);
    }


    // onCreate()方法在ActivityThread#main()運行時間接調(diào)用蔽挠,即ContentProvider是在APP啟動的時候就初始化了瓜浸。運行在主線程插佛,不能做耗時的操作。
    @Override
    public boolean onCreate() {
        resolver = getContext().getContentResolver();
        dbHelper = new DbHelper(getContext());
        db = dbHelper.getWritableDatabase();
        return true;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }


    // 注:以下增刪改成四個方法的說明:
    // 1氢拥、下面4個方法由外部進(jìn)程回調(diào)锨侯,并運行在ContentProvider進(jìn)程的Binder線程池中(不是主線程)
    // 2、存在多線程并發(fā)訪問叁怪,需要實現(xiàn)線程同步
    // 3深滚、若ContentProvider的數(shù)據(jù)存儲方式是使用SQLite &
    // 一個,則不需要血柳,因為SQLite內(nèi)部實現(xiàn)好了線程同步蹬昌,若是多個SQLite則需要皂贩,因為SQL對象之間無法進(jìn)行線程同步
    // 4、若ContentProvider的數(shù)據(jù)存儲方式是內(nèi)存婴栽,則需要自己實現(xiàn)線程同步


    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Uri newUri;
        switch (uriMatcher.match(uri)) {
            case USER_CODE:
                long user_id = db.insert(DbHelper.USER_TABLE_NAME, "", values);
                if (user_id < 0) {
                    throw new SQLiteException("Unable to insert " + values + " for " + uri);
                }
                newUri = ContentUris.withAppendedId(uri, user_id);
                resolver.notifyChange(newUri, null);
                break;
            case ADDRESS_CODE:
                long property_id = db.insert(DbHelper.ADDRESS_TABLE_NAME, "", values);
                if (property_id < 0) {
                    throw new SQLiteException("Unable to insert " + values + " for " + uri);
                }
                newUri = ContentUris.withAppendedId(uri, property_id);
                resolver.notifyChange(newUri, null);
                break;
            default:
                throw new IllegalArgumentException("Error Uri: " + uri);
        }
        return newUri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int count;
        switch (uriMatcher.match(uri)) {
            case USER_CODE:
                count = db.delete(DbHelper.USER_TABLE_NAME, selection, selectionArgs);
                break;
            case USER_CODE_SINGLE:
                String booking_id = uri.getPathSegments().get(1);
                count = db.delete(DbHelper.USER_TABLE_NAME,
                        User.INDEX + "=" + booking_id
                                + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
                        selectionArgs);
                break;

            case ADDRESS_CODE:
                count = db.delete(DbHelper.ADDRESS_TABLE_NAME, selection, selectionArgs);
                break;
            case ADDRESS_CODE_SINGLE:
                String property_id = uri.getPathSegments().get(1);
                count = db.delete(DbHelper.ADDRESS_TABLE_NAME,
                        User.INDEX + "=" + property_id
                                + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
                        selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unnown URI" + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        int count;
        switch (uriMatcher.match(uri)) {
            case USER_CODE:
                count = db.update(DbHelper.USER_TABLE_NAME, values, selection, selectionArgs);
                break;
            case USER_CODE_SINGLE:
                String booking_id = uri.getPathSegments().get(1);
                count = db.update(DbHelper.USER_TABLE_NAME, values,
                        User.INDEX + "=" + booking_id
                                + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
                        selectionArgs);
                break;

            case ADDRESS_CODE:
                count = db.update(DbHelper.ADDRESS_TABLE_NAME, values, selection, selectionArgs);
                break;
            case ADDRESS_CODE_SINGLE:
                String property_id = uri.getPathSegments().get(1);
                count = db.update(DbHelper.ADDRESS_TABLE_NAME, values,
                        Address.INDEX + "=" + property_id
                                + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
                        selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unnown URI" + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        Cursor cursor = null;
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        String orderBy;
        switch (uriMatcher.match(uri)) {
            case USER_CODE:
                qb.setTables(DbHelper.USER_TABLE_NAME);
                // 用戶定義列名->數(shù)據(jù)庫列名的映射
                qb.setProjectionMap(userHashMap);
                if (TextUtils.isEmpty(sortOrder)) {
                    orderBy = User.DEFAULT_SORT_ORDER;
                } else {
                    orderBy = sortOrder;
                }
                cursor = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
                // 用來為Cursor對象注冊一個觀察數(shù)據(jù)變化的URI
                cursor.setNotificationUri(getContext().getContentResolver(), uri);
                break;
            case ADDRESS_CODE:
                qb.setTables(DbHelper.ADDRESS_TABLE_NAME);
                // 用戶定義列名->數(shù)據(jù)庫列名的映射
                qb.setProjectionMap(addressHashMap);
                if (TextUtils.isEmpty(sortOrder)) {
                    orderBy = Address.DEFAULT_SORT_ORDER;
                } else {
                    orderBy = sortOrder;
                }
                cursor = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
                // 用來為Cursor對象注冊一個觀察數(shù)據(jù)變化的URI
                cursor.setNotificationUri(getContext().getContentResolver(), uri);
                break;
        }
        return cursor;
    }
}

3、在AndroidManifest文件中注冊創(chuàng)建的 ContentProvider類


   <!-- 聲明訪問ContentProvider的權(quán)限-->
   <permission
       android:name="com.android.pkqup.androidnote.PROVIDER"
       android:protectionLevel="normal" />


   <provider
           android:name=".content_provider_test.MyContentProvider"
           //定義授權(quán)信息
           android:authorities="com.pkqup.android.note"
           //設(shè)置外部應(yīng)用可訪問
           android:exported="true"
          //設(shè)置自定義權(quán)限
android:permission="com.android.pkqup.androidnote.PROVIDER" />

4捅彻、進(jìn)程內(nèi)訪問 ContentProvider的數(shù)據(jù)
4.1定義實體類

public class User {

    public static final String INDEX = "_index";// 主鍵
    public static final String NAME = "name";
    public static final String USER_ID = "userId";


    // 定義 user 表的 uri
    public static final Uri USER_CONTENT_URI =
            Uri.parse("content://" + MyContentProvider.AUTHORITY + "/" + DbHelper.USER_TABLE_NAME);

    // 定義 user 表的 uri
    public static final Uri USER_CONTENT_URI_SINGLE =
            Uri.parse("content://" + MyContentProvider.AUTHORITY + "/" + DbHelper.USER_TABLE_NAME
                    + DbHelper.SPECIAL_CHARACTER);

    // 排序方式步淹,定義為主鍵排序
    public static final String DEFAULT_SORT_ORDER = INDEX;

    private String name;
    private String userId;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }
}

4.2诚撵、定義ContentProvider數(shù)據(jù)庫操作類

public class UserUtils {

    private Context context;

    public UserUtils(Context context) {
        this.context = context;
    }

    public void insertUser(String name, String age) {
        ContentValues values = new ContentValues();
        values.put(User.NAME, name);
        values.put(User.USER_ID, age);
        context.getContentResolver().insert(User.USER_CONTENT_URI, values);
    }

    public void deleteUser() {
        context.getContentResolver().delete(User.USER_CONTENT_URI, null, null);
    }

    public void updateUser(String name, String age) {
        ContentValues values = new ContentValues();
        values.put(User.NAME, name);
        values.put(User.USER_ID, age);
        context.getContentResolver().update(User.USER_CONTENT_URI, values, null, null);
    }

    public User queryUser() {
        User user = new User();
        Cursor mCursor = context.getContentResolver().query(User.USER_CONTENT_URI, null, null, null,
                User.DEFAULT_SORT_ORDER);
        if (null != mCursor) {
            while (mCursor.moveToNext()) {
                String name = mCursor.getString(mCursor.getColumnIndexOrThrow(User.NAME));
                String age = mCursor.getString(mCursor.getColumnIndexOrThrow(User.USER_ID));
                user.setName(name);
                user.setUserId(age);
            }
            mCursor.close();
        }
        return user;
    }


    // 數(shù)據(jù)庫查詢方法的參數(shù)說明
    public void query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        // projection表示要查詢哪些列寿烟,比如查詢媒體庫,可能要關(guān)注音樂的藝術(shù)家缝其,長度畅铭,文件位置等。
        // selection表示查詢條件假残,就是查詢里面的where語句炉擅,
        // selectionArgs是查詢條件的值。
        // sortOrder是排序方式眶俩。
    }

    // 根據(jù)id查詢名稱
    public String getUserNameFrom(String userId) {
        String userName = "";
        Cursor mCursor = context.getContentResolver().query(User.USER_CONTENT_URI,
                new String[] {User.NAME, User.USER_ID}, "userId=?", new String[] {userId},
                User.DEFAULT_SORT_ORDER);
        if (null != mCursor) {
            while (mCursor.moveToNext()) {
                userName = mCursor.getString(mCursor.getColumnIndexOrThrow(User.NAME));
            }
            mCursor.close();
        }
        return userName;
    }
}

進(jìn)程間數(shù)據(jù)共享

1快鱼、創(chuàng)建數(shù)據(jù)庫類
見 DbHelper 類抹竹。同上。

2窃判、自定義 ContentProvider 類
見 MyContentProvider 類袄琳。同上燃乍。

3宛琅、在AndroidManifest文件中注冊創(chuàng)建的 ContentProvider類,注意配置權(quán)限和外部可使用屬性座咆。
同上仓洼。

4堤舒、在外部應(yīng)用中也需要配置相同的權(quán)限

<!--自定義上個應(yīng)用ContentProvider需要的權(quán)限-->
<permission
     android:name="com.android.pkqup.androidnote.PROVIDER"
     android:protectionLevel="normal" />

<!--使用該權(quán)限-->
<uses-permission android:name="com.android.pkqup.androidnote.PROVIDER"/>

5、外部應(yīng)用通過提供的Uri 對ContentProvider進(jìn)行增刪改查箕戳。
見 UserUtils 類国撵。同上。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末壮虫,一起剝皮案震驚了整個濱河市囚似,隨后出現(xiàn)的幾起案子线得,更是在濱河造成了極大的恐慌,老刑警劉巖募狂,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件角雷,死亡現(xiàn)場離奇詭異,居然都是意外死亡粱哼,警方通過查閱死者的電腦和手機(jī)檩咱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绊含,“玉大人躬充,你說我怎么就攤上這事〕渖酰” “怎么了伴找?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抖誉。 經(jīng)常有香客問我衰倦,道長,這世上最難降的妖魔是什么我磁? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任淹接,我火速辦了婚禮,結(jié)果婚禮上劲适,老公的妹妹穿的比我還像新娘厢蒜。我一直安慰自己,他們只是感情好愕贡,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布固以。 她就那樣靜靜地躺著,像睡著了一般憨琳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上菌湃,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天惧所,我揣著相機(jī)與錄音绪杏,去河邊找鬼。 笑死蕾久,一個胖子當(dāng)著我的面吹牛腔彰,可吹牛的內(nèi)容都是我干的辖佣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼杯拐,長吁一口氣:“原來是場噩夢啊……” “哼世蔗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起顶滩,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤礁鲁,失蹤者是張志新(化名)和其女友劉穎赁豆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體魔种,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡节预,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了准谚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柱衔。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哲戚,靈堂內(nèi)的尸體忽然破棺而出艾岂,到底是詐尸還是另有隱情,我是刑警寧澤脆炎,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布氓辣,位于F島的核電站,受9級特大地震影響几蜻,放射性物質(zhì)發(fā)生泄漏体斩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一弧烤、第九天 我趴在偏房一處隱蔽的房頂上張望源武。 院中可真熱鬧,春花似錦话浇、人聲如沸闹究。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至自娩,卻和暖如春渠退,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背姊扔。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工梅誓, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嵌言。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓呀页,卻偏偏與公主長得像拥坛,于是被迫代替她去往敵國和親尘分。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容