Simple difference between process and thread
Thread is the smallest unit of CPU scheduling and a limited system resource, while process is an execution unit.
In computers and mobile phones, a process can contain multiple threads.
For example, a process is a closed box, and processes run in closed boxes, each box has its own space and does not interfere with each other.
1.ContentProvider
Many contentProviders are preset in the system, such as the address book. To obtain and manipulate these messages across processes, you only need to use the query, update, insert, and delete methods of the ContentResolver.
Contentproviders organize data primarily in tables and can contain multiple tables
For example, let’s take terminal A identifying the logged-in user from terminal B.
ContentProvider, which provides the data
End A provides the ContentProvider for end B
public class UserInfoContentProvider extends ContentProvider {
private static final UriMatcher sMATCHER = new UriMatcher(UriMatcher.NO_MATCH); // Uri matcher
public static final int USER_INFO_CODE = 1;// Basic user information
public static final int USER_ACCOUNT_CODE = 2;// The user account password needs rsa encryption
private Context mContext;
private DataBaseOpenHelper mDBhelper = null;
User_info and user_Account tables are uri specified and associated with Uri_code, 1 and 2 respectively
//db.UserInfoContentProvider/user_info
//db.UserInfoContentProvider/user_account
static {
// Add two matching rules
sMATCHER.addURI("UserInfoContentProvider"."user_info", USER_INFO_CODE);
sMATCHER.addURI("UserInfoContentProvider"."user_account", USER_ACCOUNT_CODE);
}
@Override
public boolean onCreate(a) { // This method is run in the main thread by the system callback
mContext = getContext();
mDBhelper =DataBaseOpenHelper.getInstance(mContext);
return true;
}
// Run by external callback in Binder thread pools
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
mDBhelper =DataBaseOpenHelper.getInstance(mContext);
synchronized (mDBhelper) {
SQLiteDatabase db = mDBhelper.getWritableDatabase();
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
mContext.getContentResolver().notifyChange(uri, null);
return db.query(table, projection, selection, selectionArgs, null.null, sortOrder, null); }}// Run by external callback in Binder thread pools
//getType returns the MIME type (media type) of a Uri request.
@Override
public String getType(Uri uri) {
switch (sMATCHER.match(uri)) {
case USER_INFO_CODE:
return "vnd.android.cursor.dir/db.UserInfoContentProvider/user_info";
case USER_ACCOUNT_CODE:
return "vnd.android.cursor.dir/db.UserInfoContentProvider/user_account";
}
return null;
}
// Run by external callback in Binder thread pools
@Override
public Uri insert(Uri uri, ContentValues values) {
mDBhelper =DataBaseOpenHelper.getInstance(mContext);
synchronized (mDBhelper) {
SQLiteDatabase db = mDBhelper.getWritableDatabase();
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
db.insert(table, null, values);
mContext.getContentResolver().notifyChange(uri, null);
}
return uri;
}
// Run by external callback in Binder thread pools
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
mDBhelper =DataBaseOpenHelper.getInstance(mContext);
synchronized (mDBhelper) {
int count = -1;
SQLiteDatabase db = mDBhelper.getWritableDatabase();
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
// if(! Constant.TABLE_ACCOUNT_INFO.equals(table)) {
count = db.delete(table, selection, selectionArgs);
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
/ /}
returncount; }}// Run by external callback in Binder thread pools
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
mDBhelper =DataBaseOpenHelper.getInstance(mContext);
int row = -1;
synchronized (mDBhelper) {
SQLiteDatabase db = mDBhelper.getWritableDatabase();
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
// if(! Constant.TABLE_ACCOUNT_INFO.equals(table)) {
row = db.update(table, values, selection, selectionArgs);
if (row > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
/ /}
}
return row;
}
// We can get the table name according to the URI_code of the URI, and know which table the outside world needs to access
private String getTableName(Uri uri) {
String tableName = null;
switch (sMATCHER.match(uri)) {
case USER_INFO_CODE:
tableName = Constant.TABLE_USERINFO_CACHE;
break;
case USER_ACCOUNT_CODE:
tableName = Constant.TABLE_ACCOUNT_INFO;
break;
default:break;
}
returntableName; }}Copy the code
Register the ContentProvider in the androidManifes.xml file
<provider
android:name=".db.UserInfoContentProvider"
android:authorities="db.UserInfoContentProvider"
android:exported="true" />
Copy the code
Get the data for the ContentProvider
End B obtains the information code of end A:
public class DbDataUtils {
public final static String TAG = "DbDataUtils";
public final static int IS_STU = 0;
public final static int IS_STU_HD = 1;
public static UserInfoEntity getContentProviderInfo(Context mCtx) {
// Specify the URI and which table to operate on
// This string corresponds to the authorities of the registered ContentProvider
Uri uri = Uri.parse("content://db.UserInfoContentProvider/user_info");
UserInfoEntity infoEntity = getStuCursorInfo(mCtx, uri, IS_STU);// Get information from A. uri = Uri.parse("content://global.provider.EkwContentProvider/userprovider"); infoEntity = getStuCursorInfo(mCtx, uri, IS_STU_HD); Access to informationreturn infoEntity;
}
private static UserInfoEntity getStuCursorInfo(Context mCtx, Uri uri, int isStu){
ContentResolver cr = mCtx.getContentResolver();
// Execute the query method to return a result set
Cursor cursor = null;
try {
cursor = cr.query(uri, null.null.null.null);
}catch (Exception e) {
e.printStackTrace();
}
if(cursor == null) return null;
// Select * from result set;
UserInfoEntity infoEntity = new UserInfoEntity();
if (isStu == IS_STU) {
while (cursor.moveToNext()) {
Log.e(TAG, "1 = =" + cursor.getColumnName(1) + "2 = =" + cursor.getColumnName(2) + "3 = ="
+ cursor.getColumnName(3) + "4 = =" + cursor.getColumnName(4) + "5 = =" + cursor.getColumnName(5) + "6 = ="
+ cursor.getColumnName(6) + "7 = =" + cursor.getColumnName(7));
infoEntity.setUsername(cursor.getString(1));
infoEntity.setStu_id(cursor.getString(2));
infoEntity.setSchool(cursor.getString(3));
infoEntity.setNicename(cursor.getString(4));
infoEntity.setClasses(cursor.getString(5));
infoEntity.setAvatar(cursor.getString(6));
infoEntity.setParentPhone(cursor.getString(7)); }}else {
while(cursor.moveToNext()){
infoEntity.setUsername(cursor.getString(cursor.getColumnIndex("username")));
infoEntity.setStu_id(cursor.getString(cursor.getColumnIndex("stu_id")));
infoEntity.setSchool(cursor.getString(cursor.getColumnIndex("school")));
infoEntity.setNicename(cursor.getString(cursor.getColumnIndex("nicename")));
infoEntity.setClasses(cursor.getString(cursor.getColumnIndex("classes")));
infoEntity.setAvatar(cursor.getString(cursor.getColumnIndex("avatar")));
infoEntity.setParentPhone(cursor.getString(cursor.getColumnIndex("parentphone"))); }}return infoEntity;
}
Copy the code
Note: ⚠️
Query, update, INSERT, and DELETE methods have concurrent access from multiple threads, so they need to be synchronized internally.
In the above, only one SQLiteHelper object is used at the A end, and the synchronization lock operation is carried out to ensure the safety of multiple threads.
** Applicability: ** This method is suitable for one-to-many interprocess sharing scenarios with data sources.
2. File sharing
serialization
The process of converting the state of a data structure or object into a usable format (e.g. as a file, cached, or sent over a network) so that the original state can be restored later in the same or another computer environment. When the result of retrieving a byte according to the serialization format, it can be used to produce a semantically identical copy of the original object.
Two processes exchange data by reading/writing the same file. Android is based on Linux, and files can be read and written concurrently without limitation. If concurrent write problems exist, the read data may not be the latest data, resulting in dirty data.
The transferred object implements either Serializable or Parcelable interfaces.
data class User( var userId: Int, var userName: String ): Serializable
// Store serialized object data in a file
private fun persistToFile(a) {
Thread(Runnable {
val user = User(1."hello")
val dir = File("path")
if(! dir.exists()) { dir.mkdirs() } val cacheFile = File("cacheFile")
var objectOutputStream: ObjectOutputStream? = null
try {
objectOutputStream = ObjectOutputStream(FileOutputStream(cacheFile))
objectOutputStream.writeObject(user)
} catch (e: Exception) {
e.printStackTrace()
} finally{ objectOutputStream? .close() } }).start() }// Deserialize data objects from files
private fun recoverFromFile(a) {
Thread(Runnable {
var user: User? = null
val cacheFile = File("cacheFilePath")
if (cacheFile.exists()) {
var objectInputStream: ObjectInputStream? = null
try {
objectInputStream = ObjectInputStream(FileInputStream(cacheFile))
user = objectInputStream.readObject() as User
} catch (e: Exception) {
e.printStackTrace()
} finally{ objectInputStream? .close() } } }).start() }Copy the code
As mentioned above, there are concurrency problems, file sharing is not ideal if you want to use in high concurrency, because of the time consumption of thread synchronization, in addition to the object serialization after the writing of the file, IO operation time leading to a long lock held by the thread is another limitation.
** Applicability: ** Therefore, file sharing is suitable for communication between processes that do not require high data synchronization and need to properly handle concurrent reads and writes. It is suitable for exchanging simple data with little or no concurrency.
3. The radio
Broadcasting is a subscription model. When an application sends a broadcast, others subscribe to the application’s broadcast and receive the message sent by the broadcast. Broadcasts can also carry data when they’re sent, and they’re delivered through bundles.
The typical application scenario is read captcha in Android. The application sends a verification code message to the mobile phone number, and the monitoring system receives the broadcast message. Then the system sends a broadcast notification after receiving the message. The application reads and judges the content of the message and extracts the verification code.
Here is the sample code:
public class SMSBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "SMSBroadcastReceiver";
private static MessageListener mMessageListener;
public SMSBroadcastReceiver(a) {
super(a); }@Override
public void onReceive(Context context, Intent intent) {
Object[] pdus = (Object[]) intent.getExtras().get("pdus");
for (Object pdu : pdus) {
SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) pdu);
String sender = smsMessage.getDisplayOriginatingAddress();
String content = smsMessage.getMessageBody();
long date = smsMessage.getTimestampMillis();
Date timeDate = new Date(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = simpleDateFormat.format(timeDate);
Log.i(TAG, "OnReceive: Text message from :" + sender);
Log.i(TAG, "OnReceive: Text message content :" + content);
Log.i(TAG, "OnReceive: text message time :" + time);
// If the SMS number comes from your own SMS gateway number
if ("your sender number".equals(sender) && mMessageListener ! =null) {
Log.i(TAG, "OnReceive: callback"); mMessageListener.OnReceived(content); }}}// Callback interface
public interface MessageListener {
/** * Callback when you receive your own captcha *@paramMessage Message content */
void OnReceived(String message);
}
/** * Set the verification code to receive listening *@paramMessageListener receives its own verification code and calls back */ when it receives its own verification code
public void setOnReceivedMessageListener(MessageListener messageListener) {
this.mMessageListener = messageListener; }}Copy the code
Register the corresponding broadcast in XML:
<receiver android:name=".SMSBroadcastReceiver">
<intent-filter android:priority="1000">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
Copy the code
The Bundle carries the information in the broadcast. The disadvantage is that only the data types (String, Int, Double, serialized object data, etc.) supported by the Bundle can be transmitted, but these are generally sufficient for everyday scenarios.
4. Shared memory
Shared memory, also known as Anonymous shared memory (Ashmem) in Android, creates a block of memory that two processes use together to communicate data.
The process is as follows: the client retrieves the ParcelFileDescriptor via MemoryFile and passes the ParcelFileDescriptor(int) to the server through Binder. So what are these two?
A MemoryFile is an encapsulation of ShareMemory, which is abstracted into a file
Let’s start with FileDescriptor, which represents the original Linux FileDescriptor, which can be written to a Parcel and return a ParcelFileDescriptor object to operate on the original FileDescriptor when read.
A ParcelFileDescriptor is a copy of the original descriptor: the object and the FileDescriptor are different, but operate on the same file stream and use the same file location pointer.
The client creates the name and size of a MemoryFile, retrieves its descriptor, passes the descriptor through Binder to the server, which directly manipulates the MemoryFile.
// Client implementation
private fun createMemFile(a) {
try {
val mContent = ByteArray(640*480)
// The parameters are file name and file length
//MemoryFile is an object that Android encapsulates for anonymous shared memory
val mServiceShareMemory =
MemoryFile("com.yinlib.service" + System.currentTimeMillis(), mContent.size)
// Use reflection to get the file descriptor
val method = MemoryFile::class.java.getDeclaredMethod("getFileDescriptor")
val fd = method.invoke(mServiceShareMemory) as FileDescriptor
// Serialize file descriptors because data transmitted through Binder is meant to be serializable. This way the file descriptor is transferred to the server through Binder
val mParceServiceShareFile = ParcelFileDescriptor.dup(fd)
if(mServiceShareMemory ! =null) {
mServiceShareMemory.allowPurging(false)}}catch (e: Exception) {
e.printStackTrace()
}
}
Binder retrieves MemoryFile descriptors to read data on the server. fis = new FileInputStream(FileDescriptor.getFileDescriptor()); fis.read(new byte[100]);
Copy the code
When will shared memory be used, because operations are all in the same block of memory, reducing middlemen, and the amount of data exchanged can be large.
A MemoryFile is the temporary creation of a node in a TMPFS file system.
When we open the application, the statusbar at the top or the navigation bar at the side and the entire interface of the application can only be displayed after execution in Draw, so the data after drawing must be synthesized and refreshed to the hardware for display by SurfaceFlinger. Our application and SurfaceFlinger are two different processes, and the amount of data passed between them is also very large, so anonymous shared memory is used for communication. With 0 Copy of shared memory, data can be transferred quickly due to the large amount of data.
5.Binder
Passing information between processes is like passing information between two closed boxes that can’t be done without an intermediary.
The principle of contentProviders mentioned above is the Binder mechanism, the principle of file sharing is read and write to the same file (intermediary), broadcast message communication is also through the Binder mechanism, shared memory is operated on shared memory (intermediary). Let’s take a peek at Binder’s cross-process communication and how data is transferred.
As you can see from this picture, Binder is the bridge between the various Managers and the corresponding ManagerService that ServiceManager connects.
There are two methods in the figure above, transact and onTransact.
-
transact
The main API of the IBinder interface allows you to make calls to remote IBinder objects, that is, to send data through them.
-
OnTransact Binder’s methods enable your own remote objects to respond to incoming calls, as opposed to transact methods.
Running on the server of the Binder in the thread pool, when the client has launched across processes the request, the remote request will be through the system to the method for processing after the underlying encapsulation, this method will be from the data (where the data is in fact a deputy object) to retrieve the target method and perform the required parameters, execute after to reply in writing the return value.
This is just a very simple process diagram. In fact, the whole chain of calls is very complicated, so I won’t list them here.
conclusion
In IPC process, ContentProvider, broadcast and anonymous shared memory are involved with binders, which serialize data (also used in shared files) into byte descriptions for permanent storage, thus facilitating transmission and cross-process communication by binders. Data transferred through a Binder is copied as an object instead of shared memory.
other
Of course, there is more to interprocess communication than that. Here I just briefly describe the ways in which processes communicate on Android, as well as Linux’s pipes, message queues, signals, sockets, and so on. Although it is not usually possible to use it, understanding the principle behind it will also deepen our thinking about some system design to deal with problems.