Android IPC机制(三)进程间通信方式
在Android中有以下几种进程间通信方式:
目录
1.Bundle
2.文件共享
3.Messenger
4.ContentProvider
5.AIDL
1.Bundle
Bundle
是Android中用于存储一组键值对的类,它实现了Parcelable
接口。这使得Bundle
能够在不同的进程之间传递数据。当我们通过Intent
启动其他应用的组件(如Activity
、Service
或BroadcastReceiver
)时,可以将数据存储在Bundle
中,并将其附加到Intent
中,从而实现跨进程通信。
下面是一个简单的示例,展示如何在MainActivity
中使用Bundle
将数据传递到SecondActivity
。
在MainActivity中:
Intent intent = new Intent(this, SecondActivity.class);
Bundle bundle = new Bundle();
bundle.putString("test", "这是Bundle跨进程通信");
intent.putExtra("test", bundle);
startActivity(intent);
在上述代码中,我们创建了一个Intent
对象,用于启动SecondActivity
。然后,我们创建了一个Bundle
对象,并使用putString
方法将一条字符串信息存储在其中。接着,我们将这个Bundle
附加到Intent
中,并调用startActivity
方法启动目标活动。
在SecondActivity中接收数据:
在SecondActivity
中,我们可以通过以下代码来接收并使用传递过来的Bundle
数据:
Bundle result = getIntent().getBundleExtra("test");
Log.e("data", "onCreate:" + result.getString("test"));
在这里,我们使用getIntent().getBundleExtra("test")
方法获取传递过来的Bundle
,然后通过getString
方法提取出我们之前存储的字符串。最后,我们使用Log.e
打印出接收到的数据。
运行结果:
当我们运行这个示例时,SecondActivity
将会在日志中输出以下内容:
2.文件共享
两个进程通过读/写同一个文件来交互数据,从而实现通信。但是Android上面没有对文件的并发读/写做限制,甚至两个进程同时对一个文件进行写操作都是运行的,因此有可能发送异常,所以,文件共享发送适合在对数据同步要求不高的进程间通信,并且要妥善处理并发的读/写问题。
文件共享的实现,在我们的示例中,我们在MainActivity中序列化一个 User对象到应用的内部存储文件中。然后在SecondActivity 中去反序列化,我们期望在 SecondActivity中能够正确地恢复 User 对象的值。关键代码如下:
在MainActivity中序列化到文件:
private void persistToFile(){try{User user=new User(1,"hello word",false);File file=new File(getExternalCacheDir(),"share_data.txt");if(!file.exists()){file.createNewFile();}FileOutputStream fos = new FileOutputStream(file);ObjectOutputStream objectOutputStream=new ObjectOutputStream(fos);objectOutputStream.writeObject(user);Log.e("xxx","persist user:"+user);fos.close();objectOutputStream.close();} catch (IOException e) {e.printStackTrace();}}
在 SecondActivity中从文件恢复:
private void recoverFromFile(){try {User user = null;File file = new File(getExternalCacheDir(), "share_data.txt");if (!file.exists()) {file.createNewFile(); }ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));user = (User) objectInputStream.readObject();Log.e("xxx", "recover user:" + user);objectInputStream.close();} catch (Exception e) {e.printStackTrace();}}
下面看一下log,很显然,在SecondActivity 中成功地从文件从恢复了之前存储的 User对象的内容,这里之所以说内容,是因为反序列化得到的对象只是在内容上和序列化之前的对象是一样的,但它们本质上还是两个对象。
我们知道SharedPerference是存储xml文件里面,从本质上说也是文件的一种,但是由于系统对它的读写有一定的缓存策略,即在内存中会有一份SharedPerference文件的缓存,因此多进程模式下,系统对它的读/写就变得不可靠,当面对高并发的读/写访问时,SharedPerference有很大概率会丢失数据,因此不建议进程间的通信使用SharedPerference。
3.Messenger
Messenger 可以翻译为信使,通过它可以在不同进程中传递 Message 对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。
为什么说Messenger的底层是基于AIDL实现?
我们大致看一下Messenger 这个类的构造方法就明白了。下面是Messenger 的两个构造方法,从构造方法的实现上我们可以明显看出AIDL的痕迹,不管是IMessenger还是 Stub.asInterface,这种使用方法都表明它的底层是 AIDL。
public Messenger(Handler target) {mTarget = target.getIMessenger();}public Messenger(IBinder target) {mTarget = IMessenger.Stub.asInterface(target);}
既然Messenger是基于AIDL实现的,那么它与AIDL有什么区别呢?
- Messenger是通过Bundle传递数据,多以只能传递基本数据类型,而AIDL可以传递实体类,其实Bundler也可以传递序列化之后的实体类。
- Messenger适合传递数据少,数据量少的情况,AIDL适合数据多,数据量大的情况。
- Messenger返回值是异步的,AIDL是同步的。
下面是一个简单的示例,展示如何在Android应用中使用Messenger进行进程间通信。
客户端代码(MainActivity)
public class MainActivity extends AppCompatActivity {private Handler mHandler=new Handler(Looper.getMainLooper()){@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);if(msg.what==11) {Bundle data = msg.getData();String test = data.getString("test");Log.d("xxx", "handleMessage:" + test);}}};private final Messenger mMessager=new Messenger(mHandler); // 用于与服务通信的Messenger@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Intent intent=new Intent(this,MessengerService.class);// 创建服务的IntentbindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);// 绑定服务}private final ServiceConnection mServiceConnection=new ServiceConnection(){@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Messenger messenger=new Messenger(service);// 获取服务的MessengerMessage message= Message.obtain();// 创建消息message.what=1;Bundle bundle=new Bundle();bundle.putString("test","Messenger传递数据");message.setData(bundle);message.replyTo=mMessager;// 设置回复的Messengertry {messenger.send(message);// 发送消息到服务} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {}};@Overrideprotected void onDestroy() {super.onDestroy();if(mServiceConnection!=null){unbindService(mServiceConnection);// 解绑服务}}
}
服务端代码(MessengerService)
public class MessengerService extends Service {private Handler mHandler=new Handler(Looper.getMainLooper()){@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);if(msg.what==1) {Bundle data = msg.getData();String test = data.getString("test");Log.d("xxx", "handleMessage:" + test);Messenger replyTo=msg.replyTo;Message message=new Message();message.what=11;Bundle bundle=new Bundle();bundle.putString("test","这是服务器回传数据到客户端");message.setData(bundle);message.replyTo=mMessenger;try {replyTo.send(message);} catch (RemoteException e) {e.printStackTrace();}}}};private Messenger mMessenger=new Messenger(mHandler);@Nullable@Overridepublic IBinder onBind(Intent intent) {return mMessenger.getBinder();}
}
AndroidManifest.xml配置
在AndroidManifest.xml中,需要声明服务并指定其运行在单独的进程中:
<service android:name=".MessengerService" android:process=":test"/>
运行结果: 服务端收到了客户端发送的信息,而客户端也收到了服务端的回复。
4.ContentProvider
ContentProvider
是 Android 中提供的专门用于不同应用间进行数据共享的方式,虽然ContentProvider
的底层实现是 Binder,但是它的使用过程要比 AIDL简单许多,这是因为系统已经为我们做了封装,使得我们无须关注底层细节即可轻松实现IPC。
实现一个 ContentProvider
涉及几个步骤,包括创建类、定义数据模型、实现必要的方法以及在 AndroidManifest.xml
中注册。以下是一个简单的示例,展示如何实现一个 ContentProvider
。
1. 创建数据模型
首先,我们需要定义一个数据模型。假设我们要管理一个简单的联系人信息,包括姓名和电话号码。
public class Contact {public static final String TABLE_NAME = "contacts";public static final String COLUMN_ID = "_id";public static final String COLUMN_NAME = "name";public static final String COLUMN_PHONE = "phone";
}
2. 创建 ContentProvider 类
接下来,我们创建一个继承自 ContentProvider
的类,并实现必要的方法。
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;public class ContactProvider extends ContentProvider {private static final String AUTHORITY = "com.example.app.provider";private static final String BASE_PATH = "contacts";public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);private static final int CONTACTS = 1;private static final int CONTACT_ID = 2;private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);static {uriMatcher.addURI(AUTHORITY, BASE_PATH, CONTACTS);uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CONTACT_ID);}private SQLiteDatabase database;@Overridepublic boolean onCreate() {// 初始化数据库DatabaseHelper dbHelper = new DatabaseHelper(getContext());database = dbHelper.getWritableDatabase();return database != null;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {Cursor cursor;switch (uriMatcher.match(uri)) {case CONTACTS:cursor = database.query(Contact.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);break;case CONTACT_ID:selection = Contact.COLUMN_ID + " = ?";selectionArgs = new String[]{uri.getLastPathSegment()};cursor = database.query(Contact.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);break;default:throw new IllegalArgumentException("Unknown URI: " + uri);}cursor.setNotificationUri(getContext().getContentResolver(), uri);return cursor;}@Overridepublic Uri insert(Uri uri, ContentValues values) {long id = database.insert(Contact.TABLE_NAME, null, values);getContext().getContentResolver().notifyChange(uri, null);return Uri.parse(BASE_PATH + "/" + id);}@Overridepublic int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {int rowsUpdated;switch (uriMatcher.match(uri)) {case CONTACTS:rowsUpdated = database.update(Contact.TABLE_NAME, values, selection, selectionArgs);break;case CONTACT_ID:selection = Contact.COLUMN_ID + " = ?";selectionArgs = new String[]{uri.getLastPathSegment()};rowsUpdated = database.update(Contact.TABLE_NAME, values, selection, selectionArgs);break;default:throw new IllegalArgumentException("Unknown URI: " + uri);}if (rowsUpdated != 0) {getContext().getContentResolver().notifyChange(uri, null);}return rowsUpdated;}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {int rowsDeleted;switch (uriMatcher.match(uri)) {case CONTACTS:rowsDeleted = database.delete(Contact.TABLE_NAME, selection, selectionArgs);break;case CONTACT_ID:selection = Contact.COLUMN_ID + " = ?";selectionArgs = new String[]{uri.getLastPathSegment()};rowsDeleted = database.delete(Contact.TABLE_NAME, selection, selectionArgs);break;default:throw new IllegalArgumentException("Unknown URI: " + uri);}if (rowsDeleted != 0) {getContext().getContentResolver().notifyChange(uri, null);}return rowsDeleted;}@Overridepublic String getType(Uri uri) {switch (uriMatcher.match(uri)) {case CONTACTS:return "vnd.android.cursor.dir/" + AUTHORITY + "." + BASE_PATH;case CONTACT_ID:return "vnd.android.cursor.item/" + AUTHORITY + "." + BASE_PATH;default:throw new IllegalArgumentException("Unknown URI: " + uri);}}private static class DatabaseHelper extends SQLiteOpenHelper {private static final String DATABASE_NAME = "contacts.db";private static final int DATABASE_VERSION = 1;public DatabaseHelper(Context context) {super(context, DATABASE_NAME, null, DATABASE_VERSION);}@Overridepublic void onCreate(SQLiteDatabase db) {String createTable = "CREATE TABLE " + Contact.TABLE_NAME + " (" +Contact.COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +Contact.COLUMN_NAME + " TEXT, " +Contact.COLUMN_PHONE + " TEXT" + ");";db.execSQL(createTable);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {db.execSQL("DROP TABLE IF EXISTS " + Contact.TABLE_NAME);onCreate(db);}}
}
3. 在 AndroidManifest.xml 中注册 ContentProvider
在 AndroidManifest.xml
文件中注册 ContentProvider
,并设置权限(如果需要)。
<providerandroid:name=".ContactProvider"android:authorities="com.example.app.provider"android:exported="true" />
4. 使用 ContentProvider
其他应用程序可以通过 ContentResolver
来访问 ContentProvider
提供的数据。例如,查询联系人数据:
Cursor cursor = getContentResolver().query(ContactProvider.CONTENT_URI, null, null, null, null);
if (cursor != null) {while (cursor.moveToNext()) {String name = cursor.getString(cursor.getColumnIndex(Contact.COLUMN_NAME));String phone = cursor.getString(cursor.getColumnIndex(Contact.COLUMN_PHONE));// 处理数据}cursor.close();
}
5.AIDL
在Android平台中,每个应用程序都运行在自己的独立进程中,并且可以启动另一个应用进程的服务,而且经常需要在不同的进程间传递数据对象。但是一个进程不能直接访问另一个进程的内存空间,所以要想对话,需要将对象分解成操作系统可以理解的基本单元,并且有序的进行进程边界。而AIDL就用于生成可以在Android设备上两个进程之间进行进程间通信的代码。
在AIDL文件中,并不是所有的数据类型都是可以使用的,那么到底AIDL文件支持哪些数据类型呢?如下所示。
- 基本数据类型(int、long、char、boolean、double等);
- String 和 CharSequence;
- List:只支持ArrayList,里面每个元素都必须能够被 AIDL支持;
- Map:只支持 HashMap,里面的每个元素都必须被 AIDL支持,包括key和 value;
- Parcelable:所有实现了Parcelable接口的对象:
- AIDL:所有的 AIDL接口本身也可以在 AIDL 文件中使用。
参数定向TAG:
AIDL除了基本数据类型,其他类型的参数都需要一个定向tag来指出数据的流向,不管是in,out,还是inout,基于参数的定向tag默认是并且只能是in。
- in:表示数据只能由客户端流向服务端
- out:表示数据只能由服务端流向客户端
- inout:表示数据可在服务端与客户端之间双向流通
以下是AIDL的基本实现步骤和示例:
1. 创建AIDL文件
首先,在你的Android项目中创建一个AIDL文件。通常,这个文件会放在 src/main/aidl
目录下。
例如,创建一个名为 MyAidl.aidl
的文件,内容如下:
// MyAidl.aidl
package com.ex.binder1;interface MyAidl {void add();
}
2. 编写服务端实现
在服务端,你需要实现这个接口。创建一个服务类,继承 Service
,并实现AIDL接口。
public class AidlService extends Service {private final Binder mbinder=new MyAidl.Stub(){@Overridepublic void add() throws RemoteException {Log.e("xxx","服务端add被调用");}};@Nullable@Overridepublic IBinder onBind(Intent intent) {return mbinder;}
}
3. 在AndroidManifest.xml中注册服务
在 AndroidManifest.xml
中注册你的服务:
<serviceandroid:name=".aidl.AidlService"android:process=":aidltest" />
4. 客户端调用
在客户端,你需要绑定到服务并调用AIDL接口。以下是一个简单的示例:
public class AidlActivity extends AppCompatActivity {private ServiceConnection mServiceConnection=new ServiceConnection(){@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {MyAidl myAidl= MyAidl.Stub.asInterface(service);try {myAidl.add();} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_aidl);Intent intent=new Intent(this,AidlService.class);bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);}
}
运行结果: