Android中的IPC進程通訊方式第四篇

本文系轉載文章,閱讀原文可獲取源碼,文章末尾有原文連接html

ps:本文講的是 使用 ContentProvider 進行進程間通訊,demo 是用 kotlin 語言寫的android

一、ContentProvider數據庫

ContentProvider 可用於 Android 中不一樣的應用間進行數據共享,也就是能夠進行進程間的通訊;ContentProvider 的底層是用 Binder 實現的,它的使用過程要比前面學的 AIDL 簡單不少;ContentProvider 分爲系統的和自定義的,系統的也就是例如聯繫人,圖片等數據,要想跨進程訪問這些信息,只要經過 ContentResolver 的 query、update、insert 和 delete 這些方法就能夠了。服務器

google 對 ContentProvider 是這樣描述的,內容提供者將一些特定的應用程序數據供給其它應用程序使用;數據能夠是存儲在文件系統、SQLite 數據庫或其它方式,沒有其餘格式要求;內容提供者繼承於 ContentProvider 類,爲其它應用程序取用和存儲它管理的數據實現了一套標準方法;可是,應用程序並不直接使用這些方法,而是使用一個 ContentResolver 對象,調用它的方法做爲替代;ContentResolver 能夠與任意內容提供者進行會話,與其合做來對全部相關交互通信進行管理。上面這段話簡單的歸納爲:ContentProvider 能夠跨進程通訊,對數據格式沒有要求,實現了一套標準的方法,經過 ContentResolver 訪問數據。多線程

一套標準的方法,也就是 ContentProvider 中的 onCreate、query、insert、update、delete 和 getType 方法,除了onCreate 方法由系統回調並運行在主線程裏,其餘五個方法均由外界回調並運行在 Binder 線程池中;下面對6個方法進行說明:併發

1)onCreate 方法在建立 ContentProvider 時調用,用於初始化。app

2)query(Uri, String[], String, String[], String) 用於查詢指定 Uri 的ContentProvider,返回一個Cursor。ide

3)insert(Uri, ContentValues) 用於添加數據到指定 Uri 的ContentProvider中。佈局

4)update(Uri, ContentValues, String, String[]) 用於更新指定 Uri 的 ContentProvider 中的數據。this

5)delete(Uri, String, String[]) 用於從指定 Uri 的 ContentProvider 中刪除數據。

6)getType(Uri) 用於返回指定的 Uri 中的數據的 MIME 類型。

下面寫一個 demo 演示一下,此次的 demo 是在同一個 APP 裏開2個進程進行 ContentProvider 通訊,它和用2個 APP 進行 ContentProvider 通訊的效果是同樣的;

(1)建立一個 kt 類 DbOpenHelper 並繼承於 SQLiteOpenHelper:

class DbOpenHelper: SQLiteOpenHelper {

companion object {
    var BOOK_TABLE_NAME: String = "book"
    var DB_NAME: String = "book_provider.db"
    var DB_VERSION: Int = 1
}
var CREATE_BOOK_TABLE: String = "CREATE TABLE IF NOT EXISTS " + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)"
constructor(context: Context):super(context, DB_NAME, null, DB_VERSION) {
}
override fun onCreate(db: SQLiteDatabase?) {
    db!!.execSQL(CREATE_BOOK_TABLE)
}

override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
}

}

(2)新建一個 kt 類 MyContentProvider(包名com.xe.ipcprocess) 並繼承於 ContentProvider:

class MyContentProvider: ContentProvider() {

var TAG: String = "MyContentProvider"
var mDb: SQLiteDatabase? = null
var mContext: Context? = null
companion object {
    var URI: String = "content://com.zyb.my_provider"
    var BOOK_URI_CODE: Int = 0
    var sUriMatcher: UriMatcher = UriMatcher(UriMatcher.NO_MATCH);
    init {
        sUriMatcher.addURI(URI,"book",BOOK_URI_CODE)
    }
}

override fun insert(uri: Uri?, values: ContentValues?): Uri {
    Log.d(TAG,"------insert----currentThread------" + Thread.currentThread().getName());
    var table = getTableName(uri!!);
    mDb!!.insert(table,null,values);
    mContext!!.getContentResolver().notifyChange(uri,null);
    return uri;
}

override fun query(uri: Uri?, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor {
    Log.d(TAG,"------query----currentThread------" + Thread.currentThread().getName());
    var table = getTableName(uri!!);
    return mDb!!.query(table,projection,selection,selectionArgs,null,null,sortOrder,null);
}

fun initProviderData() {
    mDb = DbOpenHelper(mContext!!).getWritableDatabase();
    Thread() {
        kotlin.run {
            mDb!!.execSQL("delete from " + DbOpenHelper.BOOK_TABLE_NAME);
            mDb!!.execSQL("insert into book values(3,'Android');");
            mDb!!.execSQL("insert into book values(1,'Ios');");
            mDb!!.execSQL("insert into book values(2,'html');");
        }
    }.start()
}

override fun onCreate(): Boolean {
    Log.d(TAG,"------onCreate----currentThread------" + Thread.currentThread().getName());
    mContext = getContext();
    initProviderData();
    return false;
}

fun getTableName(uri: Uri): String{
    var tableName: String = DbOpenHelper.BOOK_TABLE_NAME
    return tableName
}

override fun update(uri: Uri?, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
    Log.d(TAG,"------update----currentThread------" + Thread.currentThread().getName());
    var table = getTableName(uri!!);
    var row = mDb!!.update(table,values,selection,selectionArgs);
    if (row > 0) {
        getContext().getContentResolver().notifyChange(uri,null);
    }
    return row;
}

override fun delete(uri: Uri?, selection: String?, selectionArgs: Array<out String>?): Int {
    Log.d(TAG,"------delete----currentThread------" + Thread.currentThread().getName());
    var table = getTableName(uri!!);
    var count = mDb!!.delete(table,selection,selectionArgs)
    if (count > 0) {
        getContext().getContentResolver().notifyChange(uri,null);
    }
    return count;
}

override fun getType(uri: Uri?): String {
    Log.d(TAG,"------getType----currentThread------" + Thread.currentThread().getName());
    return null!!
}

}

(3)新建一個 kt 類 Book(包名com.xe.ipcdemo4):

class Book{

var mId: Int? = null
var mName: String? = null

override fun toString(): String {
    return "mId = " + mId + ",mName = " + mName
}

}

(4)新建一個 kt 類型的 AppCompatActivity 子類 MainActivity(包名com.xe.ipcdemo4):

class MainActivity: AppCompatActivity() {

var mContentObserver: ContentObserver? = null
companion object {
    var URI: String = "content://com.zyb.my_provider"//com.zyb.provider
    var TAG: String = "MainActivity"
}
var mTv: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    init()
}

fun onClick(v: View) {
    var bookUri: Uri = Uri.parse(URI + "/book");
    var cv: ContentValues = ContentValues();
    cv.put("_id",7);
    cv.put("name","西遊記");
    Thread() {
        kotlin.run {
            getContentResolver().insert(bookUri,cv);
        }
    }.start()

    Toast.makeText(this,"插入數據成功", Toast.LENGTH_SHORT).show();
}

fun init() {
    mTv = findViewById(R.id.tv);
    mContentObserver = MyContentObserver(Handler());
    var bookUri: Uri = Uri.parse(URI + "/book");
    var cv: ContentValues = ContentValues();
    cv.put("_id",6);
    cv.put("name","程序設計");
    getContentResolver().insert(bookUri,cv);
    var bookCursor: Cursor = getContentResolver().query(bookUri,arrayOf("_id","name"),null,null,null);
    while (bookCursor.moveToNext()) {
        var book: Book  = Book();
        book.mId = bookCursor.getInt(0)
        book.mName = bookCursor.getString(1)
        Log.d(TAG,"query book:" + book.toString());
    }
    bookCursor.close();
    getContentResolver().registerContentObserver(bookUri,true,mContentObserver);
}


inner class MyContentObserver(h: Handler): ContentObserver(h) {
    var TAG: String = "MyContentObserver"
    var sb: StringBuffer = StringBuffer()
    override fun onChange(selfChange: Boolean) {
        super.onChange(selfChange)
        var bookUri: Uri = Uri.parse(URI + "/book");
        var bookCursor: Cursor = getContentResolver().query(bookUri, arrayOf("_id","name"),null,null,null);
        while (bookCursor.moveToNext()) {
            var book: Book = Book();
            book.mId = bookCursor.getInt(0)
            book.mName = bookCursor.getString(1)
            sb.append(book.toString() + "\n")
            Log.d(TAG,"query book-----------onChange---" + book.toString());
        }
        mTv!!.setText(sb)
        bookCursor.close();
    }
}

override fun onDestroy() {
    super.onDestroy()
    getContentResolver().unregisterContentObserver(mContentObserver)
}

}

(5)新建 MainActivity 對應的佈局文件 activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent"
>

<Button

android:layout_width="match_parent"
   android:text="添加數據"
   android:textAllCaps="false"
   android:onClick="onClick"
   android:layout_height="wrap_content" />
<TextView
    android:id="@+id/tv"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</LinearLayout>

(6)給 AndroidManifest.xml 文件作一下配置:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.xe.ipcdemo4">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>

            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>
    <provider
        android:authorities="com.zyb.my_provider"
        android:process=":remote"
        android:exported="true"
        android:name="com.xe.ipcprocess.MyContentProvider">

    </provider>
</application>

</manifest>

程序一開始運行的主界面以下所示:

圖片

點擊「添加數據」按鈕後,界面變化以下所示:

圖片

而後伴隨有日誌打印,注意將圈出來的地方切換到 「com.xe.ipcprocess:remote」進程中。

圖片

總結以前,咱們先這裏說明一下,客戶端 MainActivity 訪問另一個進程服務器端 MyContentProvider 時所用的 URI 要和 AndroidManifest.xml 裏配置的 authorities 屬性相同,不然訪問失敗;若是是2個APP進行進程間通訊,必須讓 provider 配一個 android:exported="true" 屬性給外部應用訪問;雖然咱們只創建了一個表,因此 sUriMatcher.addURI(URI,"book",BOOK_URI_CODE) 這條語句沒有任何意義,但若是 ContentProvider 創建多個表的時候,這條語句就起做用了,
咱們將 book表指定了Uri,爲"content://com.zyb.my_provider/book 」
這個 Uri 所關聯的 Uri_Code 是0,將 Uri 和 Uri_Code 關聯之後,就能夠經過以下方式來獲取外界所要訪問的數據源,根據 Uri 先取出 Uri_Code,根據 Uri_Code 就能夠獲得數據表的名稱,接下來就能夠響應外界的增刪改查請求了。

從日誌能夠看出,ContentProvider 中的 onCreate 方法是運行在主線程的,在本案例中,咱們只對數據進行插入和查詢,因此驗證了 query 和 insert 方法是運行在線程池裏的,其餘3個方法也是運行在線程池裏的,因此不能夠在 onCreate 方法裏作耗時的操做,因爲時間問題,這個就有讀者本身去驗證了。

在該本例中 MainActivity 中的 ContentObserver 對象,若是客戶端對 ContentObserver 對象進行了監聽,當服務器端的數據發生改變時,即 getContext().getContentResolver().notifyChange(uri,null) 語句執行時,客戶端中的 ContentObserver 對象中的 onChange 方法就會回調,也實現了跨進程回調。

這裏須要注意一點 ,query、update、insert、delete 四大方法是存在多線程併發訪問的,若是經過多個 SQLiteDatabase 對象來操做數據庫就沒法保證線程同步,由於 SQLiteDatabase 對象之間不能進行線程同步;若是 ContentProvider 的底層數據集是一塊內存的話,例如 ArrayList,這時候 ArrayList 的遍歷等操做就須要進行線程同步,否則會引發併發錯誤。

相關文章
相關標籤/搜索