轉載請以鏈接形式標明出處:
本文出自:103style的博客
《Android開發藝術探索》 學習記錄
base on AndroidStudio 3.5.1
目錄
- 簡介
- 自定義ContentProvider
- 小結
簡介
前面我們介紹了:
進程間通信基礎介紹
通過AIDL介紹Binder的工作機制
通過 Bundle、文件共享、Messenger實現進程間通信
進程間通信的方式之AIDL
本文主要介紹進程間通信的方式之 ContentProvider。
ContentProvider 是 Android 中提供的專門用於不同應用間進行數據共享的方式,從這一點來看,他天生就適合進程間通信。
ContentProvider 的底層實現同樣也是 Binder,不過使用 比AIDL簡單。由於系統已經封裝好了,我們可以很輕鬆實現IPC。
不過還是有很多需要注意的細節,比如 CRUD操作、放SQL注入 、權限控制 等。
系統預置了很多 ContentProvider,像通訊錄信息、日程表信息等,訪問這些信息只需要用 ContentResolver 的 query、update、insert、delete 方法即可。
接下來我們實現一個自定義的 ContentProvider。
自定義ContentProvider
自定義 ContentProvider 很簡單,我們只要繼承 ContentProvider,然後實現六個抽象方法就好了。
onCreate()
代表ContentProvider的創建,getType(...)
用來返回一個Uri請求所對應的 MIME類型,如果我們不關心這個可以直接返回 null
或者 */*
,query(...)
、insert(...)
、delete(...)
、update(...)
即爲增刪改查的實現。
根據Binder的原理,我們知道這些方法都運行在 ContentProvider 進程中,onCreate
方法由系統回調並運行在 主線程 裏,其他五個方法則運行在 Binder線程池 中。
我們先看如下示例,雖然啥也沒幹,但是它也是可以工作的:
//TestProvider.java
public class TestProvider extends ContentProvider {
private static final String TAG = "TestProvider";
@Override
public boolean onCreate() {
Log.e(TAG, "onCreate, thread = " + Thread.currentThread().getName());
return false;
}
@Override
public String getType(@NonNull Uri uri) {
Log.e(TAG, "getType");
return null;
}
@Override
public Cursor query(...) {
Log.e(TAG, "query, thread = " + Thread.currentThread().getName());
return null;
}
@Override
public Uri insert(...) {
Log.e(TAG, "insert");
return null;
}
@Override
public int delete(...) {
Log.e(TAG, "delete");
return 0;
}
@Override
public int update(...) {
Log.d(TAG, "update");
return 0;
}
}
在 AndroidManifest.xml
中註冊 並 聲明權限:
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<permission
android:name="com.test.cp.PROVIDER"
android:protectionLevel="normal" />
<uses-permission android:name="com.test.cp.PROVIDER" />
<application ...>
....
<provider
android:name="cp.TestProvider"
android:authorities="com.test.cp.provider"
android:permission="com.test.cp.PROVIDER"
android:process=":provider" />
</application>
</manifest>
然後在應用默認進程是訪問,進行三次查詢操作:
//MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//參數爲 “content://authorities” authorities爲manifest中註冊的
Uri uri = Uri.parse("content://com.test.cp.provider");
getContentResolver().query(uri, null, null, null, null);
getContentResolver().query(uri, null, null, null, null);
getContentResolver().query(uri, null, null, null, null);
}
}
運行程序,切換到 :provider
進程,日誌信息如下:
TestProvider: onCreate, thread = main
TestProvider: query, thread = Binder:2588_3
TestProvider: query, thread = Binder:2588_2
TestProvider: query, thread = Binder:2588_2
我們可以看到 onCreate 是運行在 UI線程,所以我們不能進行耗時操作。
三次查詢操作則運行在不同的非UI線程中。
接下來我們來完善 TestProvider 來實現訪問 日程安排 的功能。
首先我們來創建保存數據用的數據庫。
創建數據庫
//DbHelper.java
public class DbHelper extends SQLiteOpenHelper {
public static final String TABLE_NAME = "todo";
private static final String DB_NAME = "todo.db";
private static final int VERSION = 1;
private String CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME
+ "(_id INTEGER PRIMARY KEY, title TEXT, priority INT)";
public DbHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_SQL);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
完善 TestProvider 如下:
//TestProvider.java
public class TestProvider extends ContentProvider {
public static final String AUTH = "com.test.cp.provider";
public static final String TODO_URI = "content://" + AUTH + "/todo";
public static final int TODO_CODE = 1;
private static final String TAG = "TestProvider";
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
static {
URI_MATCHER.addURI(AUTH, "todo", TODO_CODE);
}
private SQLiteDatabase sqLiteDatabase;
private Context mContext
@Override
public boolean onCreate() {
Log.e(TAG, "onCreate, thread = " + Thread.currentThread().getName());
mContext = getContext();
DbHelper dbHelper = new DbHelper(mContext);
sqLiteDatabase = dbHelper.getWritableDatabase();
initData();
return false;
}
private void initData() {
sqLiteDatabase.execSQL("insert into todo values(1,'buy computer',10);");
sqLiteDatabase.execSQL("insert into todo values(2,'install androidstudio',5);");
}
public String getTableName(Uri uri) {
if (URI_MATCHER.match(uri) == TODO_CODE) {
return DbHelper.TABLE_NAME;
} else {
throw new IllegalArgumentException("illegal uri = " + uri);
}
}
@Override
public String getType(@NonNull Uri uri) {
Log.e(TAG, "getType");
return null;
}
@Override
public Cursor query(...) {
Log.e(TAG, "query, thread = " + Thread.currentThread().getName());
String tableName = getTableName(uri);
return sqLiteDatabase.query(tableName, projection, selection, selectionArgs,
null, null, sortOrder, null);
}
@Override
public Uri insert(...) {
Log.e(TAG, "insert");
String tableName = getTableName(uri);
sqLiteDatabase.insert(tableName, null, values);
notify(uri);
return null;
}
@Override
public int delete(...) {
Log.e(TAG, "delete");
String tableName = getTableName(uri);
int count = sqLiteDatabase.delete(tableName, selection, selectionArgs);
if (count > 0) {
notify(uri);
}
return count;
}
@Override
public int update(...) {
Log.e(TAG, "update");
String tableName = getTableName(uri);
int row = sqLiteDatabase.update(tableName, values, selection, selectionArgs);
if (row > 0) {
notify(uri);
}
return row;
}
private void notify(Uri uri) {
mContext.getContentResolver().notifyChange(uri, null);
}
}
需要注意的是 增刪改查四個方法是併發訪問的,所以我們正確處理多線程的問題。
示例只有一個數據庫連接,所以是沒有問題的。
不過如果 ContentProvider 的底層數據是一塊內存的話,例如 List,對其進行數據操作就得進行線程同步了。
在 MainActivity 中進行數據操作:
//MainActivity.java
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(TestProvider.TODO_URI);
ContentValues values = new ContentValues();
values.put("title", "read Android藝術開發探索");
values.put("priority", 5);
getContentResolver().insert(uri, values);
query(uri);
getContentResolver().delete(uri, "priority=10", null);
query(uri);
}
private void query(Uri uri) {
Cursor cursor = getContentResolver().query(uri, new String[]{"_id", "title", "priority"}, null, null, null);
if (cursor == null) {
return;
}
while (cursor.moveToNext()) {
Todo todo = new Todo();
todo._id = cursor.getInt(0);
todo.title = cursor.getString(1);
todo.priority = cursor.getInt(2);
Log.e(TAG, "query todo: " + todo.toString());
}
cursor.close();
}
}
//Todo.java
public class Todo {
public int _id;
public String title;
public int priority;
@Override
public String toString() {
return "Todo{" +
"_id=" + _id +
", title='" + title + '\'' +
", priority=" + priority +
'}';
}
}
運行程序,得到日誌信息如下:
TestProvider: onCreate, thread = main
TestProvider: insert
TestProvider: query, thread = Binder:6004_3
MainActivity: query todo: Todo{_id=1, title='buy computer', priority=10}
MainActivity: query todo: Todo{_id=2, title='install androidstudio', priority=5}
MainActivity: query todo: Todo{_id=3, title='read Android藝術開發探索', priority=5}
TestProvider: delete
TestProvider: query, thread = Binder:6004_3
MainActivity: query todo: Todo{_id=2, title='install androidstudio', priority=5}
MainActivity: query todo: Todo{_id=3, title='read Android藝術開發探索', priority=5}
從以上日誌我們可以看到進行了插入、查詢、刪除、查詢操作,說明 TestProvider 已經能正確的處理外部請求了。
小結
這裏我們通過自定義一個 ContentProvider 來介紹使用 ContentProvider 進行 IPC,
可以看到使用ContentProvider進行IPC非常簡單,只需要繼承 ContentProvider ,然後實現對應的方法 進行對應的數據操作就好。
如果覺得不錯的話,請幫忙點個讚唄。
以上
掃描下面的二維碼,關注我的公衆號 Android1024, 點關注,不迷路。