使用Bundle
Activity、Service、BroadcastReciver都支持在Intent中传递Bundle数据。
- Bundle实现了Parcelable接口,所以可以在进程间传输。
- Bundle中可以附加我们需要传输的数据,但是这些数据必须是能够被序列化的。比如基本类型、实现了序列化两接口的类以及一些Android支持的特殊对象。
可以通过这样的方式传输。
Bundle bundle = new Bundle();
bundle.putChar("char", 'a');
Intent intent = new Intent();
intent.putExtra("bundle", bundle);
Intent中的putExtra()系列的方法其实内部也是通过Bundle来封装数据的。
public @NonNull Intent putExtra(String name, double value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putDouble(name, value);
return this;
}
使用文件共享
两个进程通过读写同一个文件实现通信。
Android系统是基于Linux的,并发读写文件没有任何限制,两个线程同时对同一个文件进行写操作都是允许的,尽管可能出问题。
共享文件的方式对文件格式没有要求,可以是文本文件,也可以使用XML文件来存键值对。如果是对象,同样要求序列化,交换对象时可以使用ObjectInputStream和ObjectInputStream。
SharedPreferences特殊
SharedPreferences也是属于文件的一种,但是系统对它的读写有一定的缓存策略,即内存中会有一份SharedPreferences文件的缓存,并发读写时会有很大几率丢失数据,所以不推荐使用SharedPreferences进程间通信。
适用场景
文件共享适用于对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读写的问题。
使用Socket
Socket分为流式套接字和数据报套接字,也就是TCP和UDP。
- 面向连接的协议,提供稳定的双向通信功能。连接需要三次握手,本身提供超时重传机制,具有很高的稳定性。
- UDP也提供双向通信功能,具有更高的性能,但是不能保证数据能正确的传输,尤其是在网络拥塞的情况下。
使用网络请求其实就使用了Socket,设备间通信,自然也就是进程间通信了。
使用Socket通信一定要声明权限,不管是否真的需要网络。
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
使用Messenger
Messenger可以在不同的进程中传递Message对象,是一种轻量级的IPC方案,它的底层实现就是AIDL。
但是Messenger是以串行的方式处理消息的,如果有并发请求,就不太合适了。同时Messenger主要用于传递消息,没有办法实现跨进程调用服务端的方法。
可以从两个构造器看出AIDL的影子。
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
Messenger对AIDL进行了封装,使得可以更简单的使用IPC。它一次只处理一个请求,所以在服务端不需要考虑线程同步。
实现步骤
1. 服务端进程
- 在服务端创建一个Service,来处理客户端的连接请求。
- 创建一个Handler对象,并通过它来创建Messenger。
- 在Service的onBind()中返回Messenger对象底层的Binder。
2. 客户端进程
- 绑定服务端的Service。
- 使用服务端返回的IBinder对象创建一个Messenger,就可以使用这个Messenger发送消息了。
如果需要服务端能回应客户端,那么还需要在客户端创建一个Handler,从Handler中获取一个新的Messenger,并把这个Messenger通过Message的replyTo发送给服务端,服务端通过这个replyTo的参数回应客户端。
代码实现
1. 服务端
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
//根据Handler对象创建Messenger对象
private final Messenger mMessenger = new Messenger(new MessengerHandler());
//定义一个Handler
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
//接收到客户端消息时打印
case MSG_FROM_CLIENT:
Log.d(TAG, "handleMessage: " + msg.getData().getString("msg"));
break;
default:
super.handleMessage(msg);
}
}
}
//onBind返回Messenger对象底层的Binder
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
<service android:name=".MessengerService"
android:process=":remote"/>
2. 客户端
public class MessengerActivity extends AppCompatActivity {
private Messenger mMessenger;
private Message mMessage;
private static final String TAG = "MessengerActivity";
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//连接时使用返回的IBinder创建Messenger
mMessenger = new Messenger(service);
//创建装消息的Message
mMessage = Message.obtain(null, Constants.MSG_FROM_CLIENT);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
Intent intent = new Intent(this, MessengerService.class);
//绑定Service
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
//Activity销毁时解除绑定
unbindService(mConnection);
super.onDestroy();
}
public void onClick(View view) {
Bundle data = new Bundle();
data.putString("msg", "hello from client");
//将需要传递的数据装入Message
mMessage.setData(data);
try {
Log.d(TAG, "onClick: " + "send");
//通过Messenger发送Message
mMessenger.send(mMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
验证正确性的时候千万不要选择log过滤为Show only selected application,因为Service不在当前进程,会被过滤掉。
新增服务端可回应的需求
1. 服务端修改
只需要修改Handler,取出Messenger,发送回复内容。
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
//接收到客户端消息时打印
case MSG_FROM_CLIENT:
Log.d(TAG, "handleMessage: " + msg.getData().getString("msg"));
//从客户端发送的Message中拿到回复的Messenger
Messenger clientMessenger = msg.replyTo;
//创建回复的Message
Message replyMessage = Message.obtain(null, MSG_FROM_SERVER);
Bundle replyData = new Bundle();
replyData.putString("msg", "reply from server.");
//装进Message
replyMessage.setData(replyData);
try {
//发送回复
clientMessenger.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
2. 客户端修改
- 创建一个处理回复消息的Handler
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_FROM_SERVER:
Log.d(TAG, "handleMessage: " + msg.getData().getString("msg"));
break;
default:
super.handleMessage(msg);
}
}
}
- 创建回复的Messenger、用replyTo装进Message并发送。
private Messenger mReplyMessenger = new Messenger(new MessengerHandler());
mMessage.replyTo = mReplyMessenger;
使用ContentProvider
ContentProvide用于在不同应用之间进行数据共享,底层实现也是Binder。
实现一个ContentProvider
自定义一个ContentProvider只需要继承ContentProvider并实现它的六个抽象方法。
- onCreate() 做一些初始化工作。
- getType() 返回一个Uri请求所对应的MIME类型。
如果我们应用不关注,就可以直接返回null或者/。
- insert() 实现增
- delete() 实现删
- update() 实现改
- query() 实现查
这六个方法都运行在ContentProvier的进程中,除了onCreate由系统回调,运行在主线程,其他都是由外界调用的,运行在Binder线程中。
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
@Override
public boolean onCreate() {
Log.d(TAG, "onCreate: " + Thread.currentThread());
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.d(TAG, "query: " + Thread.currentThread());
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.d(TAG, "getType: " + Thread.currentThread());
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.d(TAG, "insert: " + Thread.currentThread());
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.d(TAG, "delete: " + Thread.currentThread());
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.d(TAG, "update: " + Thread.currentThread());
return 0;
}
}
ContentProvider支持表格形式和文件数据,MediaStore就是文件类型的ContentProvider。ContentProvider对底层的数据存储没有任何要求,可以使用SQLite、普通文件、甚至可以是内存中的一个对象。
Manifest中注册这个ContentProvider。
<provider
android:name=".provider.BookProvider"
android:authorities="com.utte.aidltest.provider.BookProvider"
android:permission="com.utte.PROVIDER"
android:process=":provider" />
authorities是ContentProvider的唯一标识,必须是唯一的。可以使用permission属性给ContentProvider指定权限,外界想要访问这个ContentPovider就必须要声明这个permission。权限还可以细分为读权限和写权限,如下,如果需要对ContentProvider进行写操作就需要声明com.utte.write,读操作需要com.utte.read。
android:writePermission="com.utte.write"
android:readPermission="com.utte.read"
为了测试,我们声明这个ContentProvider在provider进程中。
访问BookProvider
public class BookProviderActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_provider);
}
public void onClick(View view) {
Uri uri = Uri.parse("content://com.utte.aidltest.provider.BookProvider");
getContentResolver().query(uri, null, null, null, null);
getContentResolver().query(uri, null, null, null, null);
}
}
uri中的值就是authorities的值,ContentProvider的唯一标识。
看到打印的是这样的,证明了onCreate()运行在main线程,而query()等方法是在Binder线程池中的,所以onCreate()中不能做耗时操作。
06-23 11:28:57.426 18292-18292/com.utte.aidltest:provider D/BookProvider: onCreate: Thread[main,5,main]
06-23 11:28:57.431 18292-18306/com.utte.aidltest:provider D/BookProvider: query: Thread[Binder:18292_3,5,main]
06-23 11:28:57.434 18292-18305/com.utte.aidltest:provider D/BookProvider: query: Thread[Binder:18292_2,5,main]
添加数据
1. 数据库管理类
public class DBBookHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "book_provider.db";
private static final int DB_VERSION = 1;
public static final String BOOK_TABLE_NAME = "book";
public static final String USER_TABLE_NAME = "user";
private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS " + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY, name TEXT)";
private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY, name TEXT, sex INT)";
public DBBookHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK_TABLE);
db.execSQL(CREATE_USER_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
2. Uri UriCode关联
private static final String TAG = "BookProvider";
public static final String AUTHORITY = "com.utte.aidltest.provider.BookProvider";
public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");
public static final int BOOK_URI_CODE = 0;
public static final int USER_URI_CODE = 1;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
}
private String getTableName(Uri uri) {
String tableName = null;
switch (sUriMatcher.match(uri)) {
case BOOK_URI_CODE:
tableName = DBBookHelper.BOOK_TABLE_NAME;
break;
case USER_URI_CODE:
tableName = DBBookHelper.USER_TABLE_NAME;
break;
}
return tableName;
}
外界需要通过Uri来指定要访问的是数据库的什么信息,使用UriMatcher将Uri和UriCode绑定,写方法通过Uri获取对应的表名。
3. 初始数据
@Override
public boolean onCreate() {
Log.d(TAG, "onCreate: " + Thread.currentThread());
mContext = getContext();
initData();
return true;
}
private void initData() {
mDatabase = new DBBookHelper(mContext).getWritableDatabase();
mDatabase.execSQL("insert into book values(0, 'Android');");
mDatabase.execSQL("insert into user values(0, 'pppig');");
}
其实添加数据这种数据库操作是属于耗时的,并不建议在onCreate()中调用。
4. 实现query()
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortO
Log.d(TAG, "query: " + Thread.currentThread());
String table = getTableName(uri);
return mDatabase.query(table, projection, selection, null, null, sortOrder, null);
}
通过上面写的getTableName()获得到表名,再进行数据库操作。
5. 实现增删改
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.d(TAG, "insert: " + Thread.currentThread());
String table = getTableName(uri);
mDatabase.insert(table, null, values);
mContext.getContentResolver().notifyChange(uri, null);
return null;
}
和上面query()的实现非常相似,唯一不同的就是insert()对数据库进行了更改,所以需要通知外界数据已更改,外界可以通过registerContentObserver()来注册观察者。
其他delete()和update()都和insert()非常类似。
注意点
增删改查四个方法是存在多线程并发访问的,所以内部需要做好线程同步的工作。这里的一个SQLiteDatabase内部对数据库的操作是有同步处理的,但是如果有个SQLiteDatabase就无法进行线程同步。如果数据是内存中的List,那么也是需要自己做好线程同步工作的。
IPC方式比较
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Bundle | 使用简单 | 只能传输Bundle支持的类型 | 四大组件IPC |
| 文件共享 | 使用简单 | 不适合高并发且无法做到及时通信 | 无并发、交换简单实时性不高 |
| AIDL | 支持并发,支持及时通信 | 使用稍复杂,需要处理好线程同步 | 一对多且有RPC需求 |
| Messenger | 支持及时通信 | 不能处理并发、只能传输Bundle支持的数据类型 | 无RPC需求,低并发一对多即时通信 |
| ContentProvider | 在数据源访问方面强大,支持一对多并发数据共享 | 可理解为受约束的AIDL,只提供CRUD操作 | 一对多进程间数据共享 |
| Socket | 支持一对多并发实时通信 | 使用稍微繁琐 | 网络数据交互 |
RPC就是远程调用。







网友评论