本文系转载文章,浏览原文可获取源码,文章开端有原文链接

ps:本文讲的是 应用 ContentProvider 进行过程间通信,demo 是用 kotlin 语言写的

1、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 时调用,用于初始化。

2)query(Uri, String[], String, String[], String) 用于查问指定 Uri 的ContentProvider,返回一个Cursor。

3)insert(Uri, ContentValues) 用于增加数据到指定 Uri 的ContentProvider中。

4)update(Uri, ContentValues, String, String[]) 用于更新指定 Uri 的 ContentProvider 中的数据。

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? = nullvar mContext: Context? = nullcompanion 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? = nullvar mName: String? = nulloverride fun toString(): String {    return "mId = " + mId + ",mName = " + mName}

}

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

class MainActivity: AppCompatActivity() {

var mContentObserver: ContentObserver? = nullcompanion object {    var URI: String = "content://com.zyb.my_provider"//com.zyb.provider    var TAG: String = "MainActivity"}var mTv: TextView? = nulloverride 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 的遍历等操作就须要进行线程同步,不然会引起并发谬误。