android开发艺术探索(二)
来源:互联网 发布:知乎怎么挣钱 编辑:程序博客网 时间:2024/04/26 13:56
本章主要讲android中的IPC机制。首先介绍Android中的多进程与多进程开发中常见的注意事项,android序列化与Binder机制。然后介绍Bundle、文件共享、AIDL、Messenger、ContentProvider和Socket等跨进程通讯的方式。
IPC为进程间通讯,或者跨进程通讯,是指两个进程间进行数据交换的过程。
一、如何开启多进程
<activity android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".ActivityA" android:process=":romote"></activity> <activity android:name=".ActivityB" android:process="com.example.myaidl.romote"></activity>
如上代码所示,activityA和activityB分别指定了process属性,当我们启动这两个activity时,就会给系统增加两个进程。另外MainActivity处于系统默认分配的进程中,总共就有了三个进程。
二、多进程造成的影响?
(1)静态成员和单例会失效
(2)线程同步会完全失效
(3)SharePreferences的可靠性完全下降
(4)Application会多次创建
我们对以上问题进行分析:
问题1:两个进程(进程1和进程2),我们再进程1中持有一个静态成员变量,我们知道静态成员变量的生命周期是跟随Application的,我们再进程1中修改静态成员变量的值。并不会影响进程2中静态成员变量的值。
问题2:不同进程使用的是不同的内存区域,不管是锁对象还是锁全局类,都无法保证线程同步的问题。
问题3:SharePreferences不支持两个进程同时进行读写操作,有可能会导致数据丢失。因为SharePreferences底层是通过读写XML文件来实现的,并发读写是可能出问题的。
问题四:我们自定义MyApplication继承自Application,在oncreate()方法中打印出当前进程的名称。
然后连续启动三个位于不同进程的activity。
public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); Log.d("MyAPPlication","application start , processName:"+getCurProcessName(getApplicationContext())); } public String getCurProcessName(Context context) { int pid = android.os.Process.myPid(); ActivityManager mActivityManager = (ActivityManager) context .getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager .getRunningAppProcesses()) { if (appProcess.pid == pid) { return appProcess.processName; } } return null; }}
三、序列化与反序列化
Parcelable与Serializable都可以完成序列化与反序列化的过程。
Serializable是java提供的一个序列化接口,我们只需要实现这个接口就可以进行序列化与反序列化。但是Serialzable内部是通过java I/O进行,效率较为低下。需要注意的是:如果需要实现反序列化的时候,我们必须指定seriaVersionUID的值。否则会反序列化失败。
Parcelable是安卓推荐使用的序列化接口。在这里要推荐一个自动实现序列化方案的插件(基于Android studio)Android Parcelabe code generator 可以自动帮我们的实体类实现序列化。
public class User implements Parcelable { public String name; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.name); } public User() { } protected User(Parcel in) { this.name = in.readString(); } public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() { @Override public User createFromParcel(Parcel source) { return new User(source); } @Override public User[] newArray(int size) { return new User[size]; } };}
如何选择两种序列化方式呢?
Serializable是java提供的序列化接口,使用起来简单,但是开销比较大,序列化和反序列化过程需要进行大量的I/O操作。Parcelable 是android推荐使用的序列化方式,适用于Android平台。因此我们首选项是Parcelable 。
Parcelable 主要用于内存序列上,通过Parcelable 将对象序列化到存储设备中或者将对象序列化后通过网路传输也都是可以的,但是过程较为复杂,因此在这两种情况下推荐使用Serializable。
四、Binder
Binder貌似很难理解的样子。
(1)直观来说,Binder是Android中的一个类,它实现了IBinder接口。
(2)从IPC角度来说,Binder是Android中的一种跨进程通讯方式,Binder还可以理解为一种物理设备,设备驱动为/dev/binder,该通讯方式在Linux中没有。
(3)从Android Framework角度来说,Binder是ServiceManageer链接各种Manager(ActivityManager、WindowManager,等等)相应ManagerService的桥梁。
(4)从android应用层来说,Binder是客户端与服务端进行通讯的媒介,当我们绑定Service的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务,和基于AIDL的服务。
在Android开发中,Binder主要用于Service中,包括AIDL和Messenger,其中普通的Service中的binder是不涉及进程间的通信,无法触及到Binder核心。而Messenger的底层实际上是AIDL,在这里让我们来做一个 例子,客户端通过服务端进行加法计算。
step1:首先我们新建一个项目AIDL,我们把它作为服务端。新建aidl文件夹,如下图
step2:创建我们的AIDL文件(IImoccAIDL.aidl),然后我们发现会出错,创建不了这个文件,这里需要我们先在aidl文件夹下创建一个包com.example.aidl。然后我们就可以在这个包下创建这个文件了。
我们删除掉了不必要的代码。然后我们再接口中写了一个提供加法服务的方法。
// IRenAIDL.aidlpackage com.example.aidl;// Declare any non-default types here with import statementsinterface IImoccAIDL{ int add(int num1,int num2);}
step3:android studio不会对aidl文件自动编译,我们需要通过一个Service,当我们客户端绑定这个Service的时候,进行编译。在Activity同级目录中新建IRemoteService.java文件,如下所示:
step4:新建一个Module,我们作为客户端来使用。再此客户端要实现连个数字相加,通过访问服务端方法求和。和服务端一样,我们新建一个aidl文件夹,然后将IImoccAIDL.aidl拷贝在此文件夹下。(注意此文件所存放的包名,和服务端一致)我们要想客户端调用服务端的内容,那么就一定要定义标准的语言,所以客户端的aidl和服务端必须一致。
记住,每次不通过的时候先编译一遍
step5:我们开始编写用户交互界面,如下图:
step6:编写代码如下:我们再activity启动的时候,去绑定了服务,然后点击计算按钮的时候通过Binder对象进行计算。
public class MainActivity extends AppCompatActivity { @InjectView(R.id.et_num1) EditText etNum1; @InjectView(R.id.et_num2) EditText etNum2; @InjectView(R.id.tv_result) TextView tvResult; @InjectView(R.id.btn_get_result) Button btnGetResult; IImoccAIDL iImoccAIDL=null; ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { //拿到了远程的服务 iImoccAIDL= IImoccAIDL.Stub.asInterface(iBinder); } @Override public void onServiceDisconnected(ComponentName componentName) { //回收资源 iImoccAIDL=null; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); /**step1:获取到绑定的服务(已启动就绑定服务)*/ bindeService(); } private void bindeService() { Intent intent = new Intent(); //新版本必须显示启动绑定服务 intent.setComponent(new ComponentName("com.example.aidl", "com.example.aidl.IRemoteService")); bindService(intent, connection, Context.BIND_AUTO_CREATE); } private void initView() { ButterKnife.inject(this); btnGetResult.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { int num1=Integer.parseInt(etNum1.getText().toString()); int num2=Integer.parseInt(etNum2.getText().toString()); try { Log.e("TAG",iImoccAIDL+"11111"+num1+"---"+num2); /**step2:调用远程的服务*/ int result= iImoccAIDL.add(num1,num2); tvResult.setText(result+""); } catch (RemoteException e) { e.printStackTrace(); tvResult.setText("出错啦!"); } } }); } @Override protected void onDestroy() { super.onDestroy(); unbindService(connection); }}
step7:最最重要的一点,注册服务,在我们服务端的mainfest文件中注册
<service android:name=".IRemoteService" android:process=":remote" android:exported="true" > <intent-filter> <category android:name="android.intent.category.DEFAULT"></category> <action android:name="com.example.aidl.IRemoteService"></action> </intent-filter> </service>
我们先启动服务端,然后启动客户端,结果如下
至此:我们来分析一下,我们表面上看到的是,客户端绑定服务端的Service,然后返回Binder对象。然后再ServiceConnection中的onServiceConnected()方法中,获取到服务端aidl。然后调用其方法完成业务。
/* * This file is auto-generated. DO NOT MODIFY. * Original file: E:\\other\\aidl\\AIDL\\aidlclient\\src\\main\\aidl\\com\\example\\aidl\\IImoccAIDL.aidl */package com.example.aidl;// Declare any non-default types here with import statementspublic interface IImoccAIDL extends android.os.IInterface { /** * Local-side IPC implementation stub class.\ * 存根。 */ public static abstract class Stub extends android.os.Binder implements com.example.aidl.IImoccAIDL { /**Binder的唯一标识*/ private static final java.lang.String DESCRIPTOR = "com.example.aidl.IImoccAIDL"; static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); /** * 构造函数中链接到接口 */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * 返回一个IBinder对象为com.example.aidl.iimoccaidl接口 * 用于将服务端的Binder对象转换为客户端所需要的AIDL接口类型的对象。 */ public static com.example.aidl.IImoccAIDL asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.example.aidl.IImoccAIDL))) { return ((com.example.aidl.IImoccAIDL) iin); } return new com.example.aidl.IImoccAIDL.Stub.Proxy(obj); } /** * 用于返回当前的Binder对象 * @return */ @Override public android.os.IBinder asBinder() { return this; } /**根据code确定用户请求的目标,然后分类处理*/ @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_add: { data.enforceInterface(DESCRIPTOR); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this.add(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true; } } return super.onTransact(code, data, reply, flags); } /**代理*/ private static class Proxy implements com.example.aidl.IImoccAIDL { private android.os.IBinder mRemote; /**构造方法*/ Proxy(android.os.IBinder remote) { mRemote = remote; } /**用于返回Binder对象*/ @Override public android.os.IBinder asBinder() { return mRemote; } /**返回Binder的唯一标识*/ public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } //计算两个数的和 @Override public int add(int num1, int num2) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(num1); _data.writeInt(num2); mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } } }//计算两个数的和 public int add(int num1, int num2) throws android.os.RemoteException;}
以上是系统对aidl文件编译后产生的java类,首先呢它本身是一个接口,又继承自IInterface接口。里面主要有接口提供的add()方法和一个Stub抽象类。
Stub抽象类继承自Binder同时又实现了我们的IImoccAIDL接口。所以说Stub也是一个Binder,
private static final java.lang.String DESCRIPTOR = "com.example.aidl.IImoccAIDL";
DESCRIPTOR
Binder的唯一标识,一般用Binder的类名标识。
asInterface(android.os.IBinder obj)
用于将服务端的Binder对象转换成为客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。
onTransact
次方发运行于Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交给此方法来处理。我们从上面的代码可以看到,使用了switch语句判断INTERFACE_TRANSACTION、TRANSACTION_add的值来判断客户端请求的目标方法是什么,接着从data中取出目标方法所需要的参数,(如果目标参数有参数的haunted),当目标方法执行完毕后,就像reply中写入返回值(如果有返回值的话)。需要注意的是:如果此方法返回false,那么客户端的请求会响应失败。
当服务端异常终止时,如何处理?
Binder有两个很重要的方法,linkToDeath()与unlinkToDeath(),Binder运行在服务端进程,如果由于某种原因异常终止,会导致我们远程调用失败,会影响到客户端的功能。
解决方法一:
通过linkToDeath()方法给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求而恢复链接。
首先声明一个DeathRecipient 对象,其内部只有一个binderDied()方法,当Binder死亡时,会回调这个方法,我们在这里就可以重新绑定远程服务。
private IBinder.DeathRecipient deathRecipient=new IBinder.DeathRecipient(){ @Override public void binderDied() { iImoccAIDL.asBinder().unlinkToDeath(deathRecipient,0); iImoccAIDL=null; /**在这里重新绑定远程的service*/ } };
在客户端绑定远程服务之后,需要重新给Binder设置死亡代理。
iImoccAIDL= IImoccAIDL.Stub.asInterface(iBinder); try { iBinder.linkToDeath(deathRecipient,0); } catch (RemoteException e) { e.printStackTrace(); }
建议去慕课网看aidl教程,讲的很详细。
- Android开发艺术探索读书笔记(二)
- Android开发艺术探索读书笔记(二)
- android开发艺术探索(二)
- android开发艺术探索(二补充)
- 【Android开发艺术探索】IPC机制(二)
- Android开发艺术探索学习摘要(二)
- Android开发艺术探索读书笔记(二)
- 《Android开发艺术探索读书笔记二》
- Android开发艺术探索_IPC机制(二)
- IPC机制--开发艺术探索(二)
- Android开发艺术探索
- Android开发艺术探索读书笔记(一)
- Android开发艺术探索读书笔记(三)
- Android开发艺术探索读书笔记(第一章)
- Android开发艺术探索读书笔记(一)
- Android开发艺术探索读书笔记(三)
- android开发艺术探索(一)
- android开发艺术探索(三)
- 测试
- 导入c标签等,以及${pageContext.request.contextPath}的作用
- List数据生成CSV文件
- leetcode():Permutations
- git 的初级使用
- android开发艺术探索(二)
- iTunes无法验证服务器"s.mzstatic.com"的身份 如何解决
- 安卓点击键盘外侧不会获取焦点
- 0-1背包
- redis使用(linux环境)
- 动态规划之装配线调度理解
- U盘安装ubuntu系统
- 生成带微信头像的用户二维码
- SuperMap iServer常见问题解答集锦(三)