ContentProvider是android中提供對應(yīng)不用應(yīng)用間數(shù)據(jù)共享的方式,也就是可以進(jìn)行進(jìn)程間通信。作為進(jìn)程間通信的工具,ContentProvider的底層實現(xiàn)是 Binder。可見Binder在安卓中的地位。
1、用法
ContentProvider的用法很簡單,只需要繼承ContentProvider然后實現(xiàn)它的六個抽象方法就行了。六個方法包括:onCreate、query、update、delete、insert和getType。其中onCreate是指ContentProvider的創(chuàng)建,一般初始化的工作在這里做。getType用來返回一個Uri請求所對應(yīng)的MIME類型(媒體類型),比如圖片、視頻。如果用不到可以返回null或者"/"。根據(jù)Binder的工作原理,我們知道這六個方法運(yùn)行在ContentProvider的進(jìn)程中,除了onCreate由系統(tǒng)調(diào)用并運(yùn)行在主線程里,其他五個方法均由外界回調(diào)并運(yùn)行在Binder線程池中。
2、數(shù)據(jù)存儲
ContentProvider主要以表格的形式來組織數(shù)據(jù),并且可以包含多個表。除了表格的形式,ContentProvider還支持文件數(shù)據(jù),比如圖片、視頻等。文件數(shù)據(jù)和表格數(shù)據(jù)的結(jié)構(gòu)不同,因此處理這類數(shù)據(jù)時可以在ContentProvider中返回文件的句柄給外界從而讓文件來訪問ContentProvider中的文件信息。android系統(tǒng)中所提供的MediaStore功能就是文件類型的ContentProvider,雖然ContentProvider的底層數(shù)據(jù)看起來很像一個SQLite數(shù)據(jù)庫,但是ContentProvider對底層的數(shù)據(jù)存儲方式?jīng)]有任何要求,我們既可以使用SQLite數(shù)據(jù)庫,也可以使用普通的文件,甚至可以采用內(nèi)存中的一個對象來進(jìn)行數(shù)據(jù)的存儲。
3、簡單的示例,演示ContentProvider的工作流程
首先創(chuàng)建一個BookProvider類,它繼承自ContentProvider并實現(xiàn)了抽象方法。
package com.example.administrator.myapplication.contentprovider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import static android.content.ContentValues.TAG;
public class BookContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
Log.d(TAG, "onCreate: " + Thread.currentThread().getName());
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
Log.d(TAG, "query: " + Thread.currentThread().getName());
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.d(TAG, "getType: ");
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
Log.d(TAG, "insert: ");
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
Log.d(TAG, "delete: ");
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
Log.d(TAG, "update: ");
return 0;
}
}
接下來需要在AndroidManifest注冊這個BookProvider,如下
<pre><code><provider
android:authorities="com.example.administrator.myapplication.book.provider"
android:name=".contentprovider.BookContentProvider"
android:permission="com.myapplication.PROVIDER"
android:process=":provider"/></pre></code>
其中authorities是ContentProvider的唯一標(biāo)識,通過這個屬性外部應(yīng)用就能訪問要BookProvider,因此authorities必須是唯一的,建議名字加上包名前綴。這里的BookProvider運(yùn)行在獨(dú)立的進(jìn)程中( android:process=":provider")并給它添加了權(quán)限,外界想訪問BookProvider的話必須聲明com.myapplication.PROVIDER權(quán)限,其中權(quán)限還分為readPermission和writePermission分別是讀權(quán)限和寫權(quán)限。沒有聲明相應(yīng)的權(quán)限外界應(yīng)用調(diào)用BookProvider對應(yīng)方法時會異常終止。
這里BookProvider運(yùn)行在獨(dú)立的進(jìn)程中,所有在同一應(yīng)用訪問BookProvider的效果和外界訪問效果是一樣的。注意要聲明對應(yīng)的權(quán)限。
package com.example.administrator.myapplication;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Uri uri = Uri.parse("content://com.example.administrator.myapplication.book.provider");
getContentResolver().query(uri, null, null ,null,null);
getContentResolver().query(uri, null, null ,null,null);
getContentResolver().query(uri, null, null ,null,null);
}
}
打印結(jié)果如下:
07-03 21:26:46.281 4970-4970/? D/ContentValues: onCreate: 4970
07-03 21:26:46.287 4970-4990/? D/ContentValues: query: 4990
07-03 21:26:46.289 4970-4991/? D/ContentValues: query: 4991
07-03 21:26:46.291 4970-4990/? D/ContentValues: query: 4990
BookProvider的query方法被調(diào)用3次分別在不同線程中,這里queryinsert、update、delete方法都運(yùn)行在Binder線程中,onCreate方法運(yùn)行在主線程中,所以在onCreate方法里不能做耗時操作。
下面我們建一個數(shù)據(jù)庫,讓BookProvider能對外提供數(shù)據(jù)
DBOpenHelper.java
package com.example.administrator.myapplication.contentprovider;
import android.content.Context;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* Created by Administrator on 2017/7/3/003.
*/
public class DBOpenHelper extends SQLiteOpenHelper {
public static final String DB_NAME = "book_provider.db";
public static final String BOOK_TABLE_NAME ="book";
public static final String USER_TABLE_NAME = "user";
private static final int DB_VERSION = 1;
public static final String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS" + BOOK_TABLE_NAME + "(_id INTEGER PRIMAYR KEY," + "name TEXT)";
public static final String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS" + USER_TABLE_NAME + "(_id INTEGER PRIMAYR KEY," + "name TEXT," + "SEX INT)";
public DBOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
super(context, name, factory, version, errorHandler);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(CREATE_BOOK_TABLE);
sqLiteDatabase.execSQL(CREATE_USER_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
BookContentProvider.java
package com.example.administrator.myapplication.contentprovider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import static android.content.ContentValues.TAG;
/**
* Created by Administrator on 2017/7/3/003.
*/
public class BookContentProvider extends ContentProvider {
private static final String AUTHORITY = "com.example.administrator.myapplication.book.provider";
public static final String BOOK_CONTENT_URI = "content://" + AUTHORITY + "/book";
public static final String USER_CONTENT_URI = "content://" + AUTHORITY + "/user";
public static final int BOOK_URI_CODE = 0;
public static final int USER_URI_CODE = 1;
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(AUTHORITY, "book",0);
uriMatcher.addURI(AUTHORITY, "user",1);
}
private Context context;
private SQLiteDatabase db;
@Override
public boolean onCreate() {
Log.d(TAG, "onCreate: " + Thread.currentThread().getName());
context = getContext();
//建立數(shù)據(jù)庫的耗時操作最好不要放在主線程中
initProviderData();
return false;
}
private void initProviderData(){
db = new DBOpenHelper(context).getWritableDatabase();
db.execSQL("delete from " + DBOpenHelper.BOOK_TABLE_NAME);
db.execSQL("delete from " + DBOpenHelper.USER_TABLE_NAME);
db.execSQL("insert into " + DBOpenHelper.BOOK_TABLE_NAME + " values(3, 'android');");
db.execSQL("insert into " + DBOpenHelper.BOOK_TABLE_NAME + " values(4, 'ios');");
db.execSQL("insert into " + DBOpenHelper.BOOK_TABLE_NAME + " values(5, 'windows');");
db.execSQL("insert into " + DBOpenHelper.USER_TABLE_NAME + " values(1, 'laura', 0);");
db.execSQL("insert into " + DBOpenHelper.USER_TABLE_NAME + " values(2, 'map', 1);");
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
Log.d(TAG, "query: " + Thread.currentThread().getName());
String tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("UnSupported URI: " + uri);
}
return db.query(tableName, strings, s, strings1,null, s1,null);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.d(TAG, "getType: ");
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
Log.d(TAG, "insert: ");
String tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("UnSupported URI: " + uri);
}
db.insert(tableName, null, contentValues);
context.getContentResolver().notifyChange(uri, null);
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
Log.d(TAG, "delete: ");
String tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("UnSupported URI: " + uri);
}
int count = db.delete(tableName, s, strings);
if (count > 0) {
context.getContentResolver().notifyChange(uri, null);
}
return count;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
Log.d(TAG, "update: ");
String tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("UnSupported URI: " + uri);
}
int row = db.update(tableName, contentValues,s, strings);
if (row > 0) {
context.getContentResolver().notifyChange(uri, null);
}
return row;
}
private String getTableName(Uri uri){
switch (uriMatcher.match(uri)){
case BOOK_URI_CODE:
return DBOpenHelper.BOOK_TABLE_NAME;
case USER_URI_CODE:
return DBOpenHelper.USER_TABLE_NAME;
}
return null;
}
}
```
MainActivity.java
package com.example.administrator.myapplication;
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;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Uri uri = Uri.parse("content://com.example.administrator.myapplication.book.provider/book");
// getContentResolver().query(uri, null, null ,null,null);
// getContentResolver().query(uri, null, null ,null,null);
// getContentResolver().query(uri, null, null ,null,null);
Uri bookUri = Uri.parse("content://com.example.administrator.myapplication.book.provider/book");
ContentValues values = new ContentValues();
values.put("_id", 6);
values.put("name", "android開發(fā)藝術(shù)探索");
getContentResolver().insert(bookUri, values);
Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);
while(bookCursor.moveToNext()){
Log.d(TAG, "_id: " + bookCursor.getString(0));
Log.d(TAG, "name: " + bookCursor.getString(1));
}
bookCursor.close();
}
}
MainActivity向book表中插入一條數(shù)據(jù),并且查尋并打印出該條數(shù)據(jù)。需要注意的是query、update、insert、delete四個方法運(yùn)行在不同的線程中,可能出現(xiàn)多線程并發(fā)訪問,所以方法內(nèi)部要做好多線程同步。本例中采用了Sqlite并且只有一個SQLiteDatabase連接,所以可以正確應(yīng)對多線程的情況。因為sqlite內(nèi)部對數(shù)據(jù)庫的操作是有做同步處理的,但是如果是多個SQLiteDatabase對象操作數(shù)據(jù)庫的話就無法保證線程同步,因為SQLiteDatabase對象之間無法進(jìn)行線程同步。如果ContentProvider的底層數(shù)據(jù)集是一塊內(nèi)存的話,比如是List,那么就要對list的操作進(jìn)行線程同步了,否則可能會引起并發(fā)錯誤。
ContentProvider除了支持增刪改查的操作外還_支持自定義調(diào)用_,需要通過ContentResolver的Call方法和ContentProvider的Call方法來完成。此處就不作演示了。
參考:android開發(fā)藝術(shù)探索