描述
為了在應(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 類国撵。同上。