本文系转载文章,阅读原文可获取源码,文章末尾有原文连接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 的遍历等操做就须要进行线程同步,否则会引发并发错误。