This article reprints the article, read the original source code can be obtained, at the end of the article has the original link

PS: This article is about interprocess communication using ContentProvider. The demo is written in Kotlin

1, the ContentProvider

The ContentProvider can be used to share data between different applications in Android, that is, to communicate between processes. The ContentProvider is implemented in Binder, which is much easier to use than AIDL. ContentProvider can be divided into system data and custom data. System data, such as contacts, images and so on, can be accessed across processes by using the query, update, insert and delete methods of ContentResolver.

Google describes the ContentProvider as “a ContentProvider that provides certain application data to other applications for use. The data can be stored in a file system, a SQLite database, or by other means. There are no other format requirements. The ContentProvider inherits from the ContentProvider class and implements a standard way for other applications to access and store the data it manages. However, the application does not use these methods directly. Instead, it uses a ContentResolver object and calls its methods instead; A ContentResolver can talk to any content provider and cooperate with it to manage all related interactions. A ContentProvider can communicate across processes. It has no data format requirements. It implements a standard set of methods to access data from a ContentResolver.

A set of standard methods, namely onCreate, Query, Insert, Update, Delete, and GetType in the ContentProvider, except that the onCreate method is called back by the system and runs on the main thread. The other five methods are called back by the outside world and run in the Binder thread pool; Here are six ways to do this:

1) The onCreate method is called when the ContentProvider is created and is used for initialization.

Query (URI, String[], String, String[], String); query(URI, String[], String);

INSERT (URI, ContentValues) is used to add data to the ContentProvider with the specified URI.

4) Update (URI, contentValues, String, String[]) is used to update the data in the ContentProvider with the specified URI.

DELETE (URI, String, String[]) Deletes data from the ContentProvider URI.

GetType (URI) returns the MIME type of the data in the specified URI.

Let’s write a demo to demonstrate it. This demo is to open two processes in the same APP for ContentProvider communication. It has the same effect as using two apps for ContentProvider communication.

(1) Create a class dbopenHelper that extends from 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) Create a new class MyContentProvider (package name com.xe. ipcProcess) and inherit it from 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() { { 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!! }


Create a new Book of class KT (package name com.xe. ipcDemo4) :

class Book{

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

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


Create a new subclass of AppCompatActivity (MainActivity) of type KT (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"," Journey to the West "); Thread() { { getContentResolver().insert(bookUri,cv); }.start() toast.maketext (this," insert data successfully ", toast.length_short).show(); } fun init() { mTv = findViewById(; mContentObserver = MyContentObserver(Handler()); var bookUri: Uri = Uri.parse(URI + "/book"); var cv: ContentValues = ContentValues(); cv.put("_id",6); CV. Put ("name"," program design "); 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) Create the layout file activity_main.xml corresponding to MainActivity:



Android :layout_width="match_parent" android:text=" add data "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" />


(6) Configure the AndroidManifest.xml file:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
    <activity android:name=".MainActivity">
            <action android:name="android.intent.action.MAIN"/>

            <category android:name="android.intent.category.LAUNCHER"/>



The main interface to start the program looks like this:

The picture

After clicking the “Add Data” button, the interface changes as follows:

The picture

Then along with the log printing, note to switch the circled area to the “com.xe.ipcprocess:remote” process.

The picture

To conclude, the client mainActivity uses the same URI as the Authorities property configured in the AndroidManifest.xml when it accesses another process, the server side MyContentProvider. Otherwise the access fails; If two applications communicate between processes, you must assign a provider with an android:exported=”true” attribute to access by external applications. Surimatcher.addURI (URI,”book”,BOOK_URI_CODE) is meaningless because we created only one table, but if the ContentProvider creates more than one table, it will work. URI_CODE = “CONTENT ://com.zyb.my_provider/book”; URI_CODE = “CONTENT ://com.zyb.my_provider/book”; After associating URI and URI_CODE, you can obtain the data source to be visited by the outside world in the following ways. Take out the URI_CODE according to the URI first, and then get the name of the data table according to the URI_CODE, and then you can respond to the request of adding, deleting, modifying and searching.

The ContentProvider’s onCreate method runs on the main thread. In this case, we are only inserting and querying data, so we verify that the Query and INSERT methods are running in the thread pool. The other three methods are also running in the thread pool. So you can’t do any time-consuming things in the onCreate method, so it’s up to the reader to verify that for time reasons.

In this example, the ContentTobServer object in MainActivity, if the client listens on the ContentTobServer object, when the data on the server changes, When getContext().getContentResolver().NotifyChange (URI, NULL) is executed, the onChange method in the ContenttobServer object in the client is called back, which also implements a cross-process callback.

Query, UPDATE, INSERT and DELETE can be accessed by multiple threads concurrently. If you use multiple SQLiteDatabase objects to manipulate the database, you cannot guarantee thread synchronization. Because SQLiteDatabase objects do not have thread synchronization; If the ContentProvider’s underlying data set is a memory block, such as an ArrayList, then operations such as traversal of an ArrayList will require thread synchronization, otherwise they will cause concurrency errors.