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。
相关文章推荐
- Android IPC进程间通讯机制学习笔记
- 2011年Android IPC进程间通讯机制学习笔记之一
- Android IPC进程间通讯机制学习笔记
- Android IPC进程间通讯机制学习笔记
- Android IPC机制学习笔记(一)
- Android IPC机制学习笔记(三) Binder
- Android源码学习之五-Android的IPC机制
- [转]Android Activity和Intent机制学习笔记
- Android Activity和Intent机制学习笔记
- Android Activity和Intent机制学习笔记
- Android进程间通信(IPC)机制Binder简要介绍和学习计划
- Android进程间通信(IPC)机制Binder简要介绍和学习计划
- Android源码学习之五-Android的IPC机制
- android Handler 机制研究学习笔记
- Android Activity和Intent机制学习笔记(转)
- Android Activity和Intent机制学习笔记
- Android进程间通信(IPC)机制Binder简要介绍和学习计划
- [转]Android Activity和Intent机制学习笔记
- Android进程间通信(IPC)机制Binder简要介绍和学习计划
- Android Activity和Intent机制学习笔记