您的位置:首页 > 移动开发 > Android开发

Android IPC机制学习笔记(二)

2017-11-10 17:30 435 查看

一、多进程模式的运行机制

Android为每一个应用分配了一个独立的虚拟机,或者说为每个进程都分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。

所有运行在不同进程中的四大组件,只要他们之间需要通内存来共享数据,都会失败

一般来说,使用多进程会造成如下几方面的问题:

1,静态成员和单例模式完全失效;

2,线程同步机制完全失效;

3,SharedPreferences的可靠性下降;

4,Application会多次创建。

SharedPreferences不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失。

由于不同的进程系统需要分配独立的虚拟机,相当于把这个应用重新启动了一遍,所以当处于多进程的情况下,Application会重复启动

二、Android中IPC的方式

1,使用Bundle

四大组件中的三大组建(Activity、Service、Receiver)都是支持在Intent 中传递 Bundle 数据的。由于 Bundle 实现了 Parcelable 接口,所以它可以方便的在不同的进程间传输。传输的数据必须能够被序列化。 

2,使用文件共享

共享文件也是一种不错的进程间通信方式,两个进程通过读/写同一个文件来交换数据。这种方式对文件格式没有具体的要求,只要读/写双方约定数据格式即可。

但是通过文件共享的方式也是有局限的:比如并发读/写的问题。

文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。

3,使用Messenger

Messenger 是一种轻量级的 IPC 方案,它的底层实现是 AIDL。

由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端中不存在并发执行的情形。

服务端用Service实现,设置process:

<service android:name=".MessengerService"
android:process=":remote"/>


服务端代码:

public class MessengerService extends Service {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.i("WZX",msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message message = Message.obtain();
message.what = 2;
Bundle bundle = new Bundle();
bundle.putString("msg","这里是服务端,已经收到你的请求");
message.setData(bundle);
try {
client.send(message);
}catch (RemoteException e){

}
break;
default:
super.handleMessage(msg);
break;
}
}
};
private final Messenger mMessenger = new Messenger(mHandler);
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder() ;
}
}


这里有几个注意的地方,如果服务端需要返回信息给客户端,需要获得客户端的Messenger,而获得的方法是:



而这个Messenger是在客户端发送Message的时候传递过来的:



下面是客户端代码:

public class MainActivity extends AppCompatActivity {
private Button btnSend;
private TextView tvMsg;
private Messenger mService,mClient;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 2:
tvMsg.setText(msg.getData().getString("msg"));
break;
default:
super.handleMessage(msg);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
Intent intent = new Intent(this,MessengerService.class);
bindService(intent,mConnection,BIND_AUTO_CREATE);
}

private void initView() {
mClient = new Messenger(mHandler);
btnSend = (Button)
4000
findViewById(R.id.btn_send);
tvMsg = (TextView)findViewById(R.id.tv_msg);
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message message = Message.obtain();
message.what = 1;
Bundle data = new Bundle();
data.putString("msg","这里是客户端,正在发送请求");
message.setData(data);

//传递客户端 Messenger
message.replyTo = mClient;

try{
mService.send(message);
}catch (RemoteException e){

}

}
});
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}


4,使用AIDL

Messenger底层实现也是AIDL,Messenger是封装好的AIDL。

(一) 服务端:

首先创建一个Service来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。

Service:

public class MessengerService extends Service {
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private AtomicBoolean mIsDestroyed = new AtomicBoolean(false);
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookLis() throws RemoteException {
return mBookList;
}

@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}

@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
}

@Override
public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
};

@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1,"Android"));
mBookList.add(new Book(2,"ios"));
new Thread(new ServiceWork()).start();
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}

@Override
public void onDestroy() {
mIsDestroyed.set(true);
super.onDestroy();
}

private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
int N = mListenerList.beginBroadcast();
for (int i=0;i<N;i++){

IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
if (l!=null){
l.onNewBookArrived(book);
}
}
mListenerList.finishBroadcast();
}

private class ServiceWork implements Runnable{

@Override
public void run() {
while (!mIsDestroyed.get()){
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
int bookId = mBookList.size()+1;
Book newBook = new Book(bookId,"new Book"+bookId);
Log.i("TAG","add new Book");
try{
onNewBookArrived(newBook);
}catch (RemoteException e){
e.printStackTrace();
}
}
}
}
}


几个注意的地方:

a. 使用 CopyOnWriteArrayList 而不是 ArrayList.

b. RemoteCallbackList 是系统专门提供的用于删除跨进程 listener 的接口。RemoteCallbackList是一个泛型,支持管理任意的 AIDL 接口。注册/注销 listener调用的是 RemoteCallbackList 的 register/unRegister 方法。

特别注意:

我们不能像操作 List 一样去操作 RemoteCallbackList,遍历 RemoteCallbackList,必须要按照下面的方式进行。



其中 beginBroadcast() 和 finishBroadcast() 必须配对使用。

(二)客户端

首先绑定服务端的 Service,绑定成功后,将服务端返回的 Binder 对象转成 AIDL 接口所属的类型,接着就可以调用 AIDL 中的方法了。

client:

public class MainActivity extends AppCompatActivity {
private Button btnSend,btnAdd;
private TextView tvMsg;
private IBookManager mIBookManager;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIBookManager = IBookManager.Stub.asInterface(service);
try {
mIBookManager.registerListener(mListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
if (msg.what == 1){
showBookList();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
Intent intent = new Intent(this,MessengerService.class);
bindService(intent,mConnection,BIND_AUTO_CREATE);

}
private IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener.Stub(){

@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
mHandler.obtainMessage(1,newBook).sendToTarget();
}
};
private void initView() {
btnSend = (Button)findViewById(R.id.btn_send);
btnAdd = (Button)findViewById(R.id.btn_add);
tvMsg = (TextView)findViewById(R.id.tv_msg);
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showBookList();
}
});
btnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mIBookManager != null && mIBookManager.asBinder().isBinderAlive()){
try {
mIBookManager.unRegisterListener(mListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
}
@Override
protected void onDestroy() {
if (mIBookManager != null && mIBookManager.asBinder().isBinderAlive()){
try {
mIBookManager.unRegisterListener(mListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
super.onDestroy();
}
private void showBookList(){
List<Book> bookList = null;
try {
bookList = mIBookManager.getBookLis();
} catch (RemoteException e) {
e.printStackTrace();
}
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("size:"+bookList.size());
stringBuffer.append("\r\n");
for (Book item :
bookList) {
stringBuffer.append("ID:"+item.getBookId()+"       name:"+item.getBookName());
stringBuffer.append("\r\n");
}
tvMsg.setText(stringBuffer.toString());
}
}


注意几个地方:

a. 客户端调用远程服务的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端线程会被挂起,如果服务端方法执行比较耗时,会造成客户端线程长时间阻塞在这里,如果客户端线程是 UI 线程的话,就会导致 ANR。

b. 为了程序的健壮性,Binder 是有可能意外死亡的。在这种情况下我们需要重新连接服务。

给Binder设置 DeathRecpient 监听,当 Binder 死亡时,我们会收到 binderDied 方法回调,在此方法中重连远程服务。

在 onServiceDisconnected 中重连远程服务。

这两种方法的区别是 onServiceDisconnected 在 UI 线程中被回调,binderDied 在客户端的 Binder 线程池中被回调,也就是说,在 binderDied 中我们不能访问 UI。

(三)如何在 AIDL 中使用权限验证功能。

在 onBind() 中进行验证,验证不通过就直接返回 null,可以使用 permission 进行验证等。

可以在服务端的 onTransact 方法中进行验证,如果验证失败返回 false。方法可以采用 permission 也可以验证包名等。

5,使用ContentProvider

ContentProvider 是 Android 中提供的专门用于不同应用间进行数据共享的方式,他的底层实现也是 Binder 。

使用ContentProvider步骤:

Step1:

创建 BookProvider 继承 ContentProvider 并实现方法:

public class BookProvider extends ContentProvider {
/**
* ContentProvider创建
* 初始化
* 运行在 UI 线程, 不能进行耗时操作
* */
@Override
public boolean onCreate() {
Log.i("WZX","create");
return false;
}

@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.i("WZX","query");
return null;
}
/**
* 用来返回一个 Uri 请求所对应的 MIME 类型(媒体类型)
* 如果我们不关注这个选项,可以直接在这个方法中返回 null 或 "* / *"
* */
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}

@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.i("WZX","insert");
return null;
}

@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.i("WZX","delete");
return 0;
}

@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.i("WZX","update");
return 0;
}
}


Step2:

注册 BookProvider:

<provider
android:authorities="com.example.ex_wangzexin.book.provider"
android:name=".BookProvider"
android:process=":provider"
/>


authorities 是 ContentProvider 的唯一标识,通过这个属性外部应用就可以访问我们的 BookProvider,这个属性必须是唯一的。

在注册 Provider 的时候还可以给他添加权限,这样外界应用如果想访问 BookProvider 就必须声明权限。

Step3:

调用BookProvider:

Uri uri = Uri.parse("content://com.example.ex_wangzexin.book.provider");
getContentResolver().query(uri,null,null,null,null);


虽然 ContentProvider 的底层数据库看起来很像一个 SQLite 数据库,但是 ContentProvider 对底层的数据存储方式没有任何要求,我们既可以使用 SQLite 数据库,也可以使用普通的文件,甚至可以采用内存中的一个对象来进行数据的存储。

需要注意的是,qurey、update、insert、delete 四个方法是存在多线程并发访问的,因此方法内部要做好线程同步。

6,使用Socket

使用 Socket 注意两点:

声明权限

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>


不能在主线程中访问网络,因为这会导致我们的程序无法在 Android 4.0及以上的设备中运行,会抛出如下异常:android.os.NetworkOnMainThreadException。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: