android开发艺术(二)之 IPC

1.多进程模式

主线程(UI线程):UI操作
有限个子线程:耗时操作
不可在主线程做大量耗时操作,会导致ANR(应用无响应)

1.1开启多进程

(不常用)通过JNI在native层fork一个新的进程。
(常用)在AndroidMenifest中给四大组件指定属性android:process
在这里插入图片描述
上面的示例分别为SecondActivity 和ThirdActivity指定了process 属性,并且它们的属性值不同,这意味着当前应用又增加了两个新进程。假设当前应用的包名为“com.ryg.chapter_ 2”,当SecondActivity 启动时,系统会为它创建一个单独的进程,进程名为“com.ryg.chapter_ 2: remote”; 当ThirdActivity启动时,系统也会为它创建一个单独的进程,进程名为“com.ryg chapter_2.remote". 同时入口Activity 是MainActivity, 没有为它指定process属性,那么它运行在默认进程中,默认进程的进程名是包名
以“:”开头的进程:
省略包名,如android:process=":remote",表示进程名为com.example.myapplication:remote
属于当前应用的私有进程,其他进程的组件不能和他跑在同一进程中。
完整命名的进程:
android:process="com.example.myapplication.remote"。
属于全局进程,其他应用可以通过ShareUID方式和他跑在用一个进程中。
UID&ShareUID:

  • Android系统为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。
  • 两个应用通过ShareUID跑在同一进程的条件:ShareUID相同且签名也相同。
  • 满足上述条件的两个应用,无论是否跑在同一进程,它们可共享data目录,组件信息
  • 若跑在同一进程,它们除了可共享data目录、组件信息,还可共享内存数据。它们就像是一个应用的两个部分。

查看进程信息的方法:
通过DDMS视图查看进程信息。
通过shell查看,命令为:adb shell ps|grep 包名。

1.2 多进程造成的影响

多进程造成的影响,总结为以下四方面:
① 静态变量和单例模式失效:由独立的虚拟机造成,同一个类在多个进程中会多次加载
② 线程同步机制失效:原因同上。
③ SharedPreference 的可靠性下降:SharedPreferences不支持两个进程同时进行读写操作,即不支持并发读写,有一定几率导致数据丢失。
④ Application多次创建:Android系统会为新的进程分配独立虚拟机,多个虚拟机相当于系统又把这个应用重新启动了一次

1.3 开启IPC原因

  • 数据传输:一个进程需要将它的数据发送给另一个进程。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

2.IPC基础概念介绍

IPC(Inter-Process Communication,跨进程通信):指两个进程之间进行数据交换的过程。任何一个操作系统都有对应的IPC机制。

Windows:通过剪切板、管道、油槽等进行进程间通讯。
Linux:通过命名空间、共享内容、信号量等进行进程间通讯。
Android:没有完全继承Linux,比如,其独具特色的通讯方式有Binder、Socket等等。

2.1 IPC的使用场景:

1.由于某些原因,应用自身需要采用多进程模式来实现。可能原因有:

  • 某些模块因特殊原因要运行在单独进程中;
  • 为加大一个应用可使用的内存,需通过多进程来获取多份内存空间。

2.当前应用需要向其它应用获取数据。
在这里插入图片描述

Serializable和Parcelable 接口可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要使用Parcelable 或者Serializable。 有的时候我们需要把对象持久化到存储设备上或者通过网络传输给其他客户端,这个时候也需要使用Serializable来完成对象的持久化

2.2 Serializable 接口

Serializable 是Java所提供的一个序列化接口, 它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程:

private static final long serialVersionUID = 8711368828010083044L

进行对象的序列化和反序列化需要采用ObjectOutputStreamObjectInputStream

//序列化过程
User user=new User(0, "jake", true) ;
objectoutputstream out = new objectoutputstream ( new FileOutputStream("cache. txt"));
out.writeobject (user) :
out.close() :
//反序列化过程
ObjectInputStream in = new objectInputstream ( new FileInputStream ("cache. txt"));
User newUser = (User) in.readobject() ;
in.close() ;

注意事项:

  1. serialVersionUIDSerializable接口中用来辅助序列化和反序列化过程。原则上序列化后的数据中的serialVersionUID要和当前类的serialVersionUID 相同才能正常的序列化。手动指定serialVersionUID可以避免反序列化失败。
  2. 类结构发生非常规性改变(修改了类名或成员变量类型)反序列化会失败
  3. 静态成员变量属于类,不属于对象,所以不会参加序列化
  4. 使用transient关键字标记的成员变量不参与序列化过程

2.3 Parcelable接口

实现Parcelable接口的类对象可以实现序列化并可以通过Intent和Binder传递

public class User implements Parcelable{
	public int userId;
	public String userName;
	public boolean isMale;
	public Book book; 
	public User (int userId, String userName, boolean i sMale) {
		this.userId = userId;
		this.userName = userName ;
		this.isMale = isMale;
	}
	public int describeContents() (
		return 0
	}
	public void writeToParcel (Parcel out, int flags) {
		out.writeInt(userId) ;
		out.writeString(userName) ;
		out.writeInt(isMale ? 1 : 0) ;
		out.writeParcelable(book, 0) ;
	}	
	public static final Parcelable.Creator<User> CREATOR=new Parcelable.Creator<User> (){
		public User createFromParcel(Parcel in) (
			return new User(in) ;
		}
		public User[] newArray(int size) (
			return new User[size] ;
		}
	};
	private User (Parcel in) {
		userId=in.readInt() ;
		userName= in.readstring() ;
		isMale = in.readInt() == 1;
		book = in.readParcelable(Thread.currentThread().getContextClassLoader());
		}
}

Parcel 内部包装了可序列化的数据,可以在Binder中自由传输。
序列化功能由writeToParcel方法来完成,最终是通过Parcel中的一系列 write方法来完成的;
反序列化功能由CREATOR 来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化过程;
内容描述功能由describeContents方法来完成,几乎在所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时,此方法返回1。需要注意的是,在User(Parcel in)方法中,由于book是另一个可序列化对象,所以它的反序列化过程需要传递当前线程的上下文类加载器,否则会报无法找到类的错误
在这里插入图片描述
如何选取
Serializable 是Java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量IO操作。而Parcelable是Android中的序列化方式,因此更适合用在Android 平台上,它的缺点就是使用麻烦,但是它的效率很高。Parcelable主要用在内存序列化上,通过Parcelable 将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但是这个过程会稍显复杂,因此在这两种情况下建议大家使用Serializable

3.IPC方法

在这里插入图片描述

1.Binder

Binder

2.使用Bundle

Bundle在Android开发中十分常见,常用于以下场合:
1.Activity状态数据的保存与恢复涉及到的两个回调:void onSaveInstanceState (Bundle outState)、void onCreate (Bundle savedInstanceState)
2.Fragment的setArguments方法:void setArguments (Bundle args)
3.消息机制中的Message的setData方法:void setData (Bundle data)
4.使用Bundle在Activity之间传递数据,传递的数据可以是boolean、byte、int、long、float、double、String等基本类型或它们对应的数组,也可以是对象或对象数组。当Bundle传递的是对象或对象数组时,必须实现Serializable 或Parcelable接口。实现Parcelable接口,它可方便的在不同的进程中传输。支持在Activity、Service和Receiver之间通过Intent.putExtra() 传递Bundle数据

Bundle bundle = new Bundle(); //得到bund1e对象
bundle.putString("sff""value值"); //key-"sff" ,通过key得到value- "value值" 
bundle.putInt("iff", 175); //key-"iff" ,value-175
intent.putExtras(bund1e); //通过intent将 bundle传到另个Activity
startActivity(intent);
读取数据
Bundle bundle =this.getIntent().getExtras(); //读取intent的数据给bundle对象
String str1 = bundle.getString("sff"); //通过key得到value

详细了解:通过Bundle在Android Activity间传递数据

3.文件共享

两个进程通过读/写同一个文件来交换数据。比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。适用情况:对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题
不建议采用SharedPreferences,原因是系统对SharedPreferences的读/写有一定的缓存策略,即在内存中有一份该文件的缓存,因此在多进程模式下,其读/写会变得不可靠,甚至丢失数据。

4.使用AIDL

AIDL(Android Interface Definition Language,Android接口定义语言):如果在一个进程中要调用另一个进程中对象的方法,可使用AIDL生成可序列化的参数,AIDL会生成一个服务端对象的代理类,通过它客户端实现间接调用服务端对象的方法。
3.1可支持的数据类型:
●基本数据类型(int、 long、 char、 boolean、 double 等);
●String 和CharSequence;
●List: 只支持ArrayList, 里面每个元素都必须能够被AIDL支持;
●Map: 只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value;
●Parcelable: 所有实现了Parcelable接口的对象;
●AIDL: 所有的AIDL接口本身也可以在AIDL文件中使用;
3.2其他类型:
其它类型的参数必须标上方向: in、 out或inout, 用于表示在跨进程通信中数据的流向。

  • in 表示数据只能由客户端流向服务端。
    • 服务端将会接收到这个对象的完整数据,但在服务端修改它不会对客户端输入的对象产生影响。
  • out表示数据只能由服务端流向客户端。
    • 服务端将会接收到这个对象的的空对象,但在服务端对接收到的空对象有任何修改之后客户端将会同步变动
  • inout 表示数据可在服务端与客户端之间双向流通。
    • 服务端将会接收到客户端传来对象的完整信息,且客户端将会同步服务端对该对象的任何变动。

3.3两种AIDL文件:
自定义的Parcelable对象必须把java文件和自定义的AIDL文件显式的import进来,无论是否在同一包内;AIDL文件用到自定义Parcelable的对象,必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型;AIDL接口中只支持方法,不支持声明静态常量

1.Book.aidl  用于定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型
package com.ryg.chapter_2.aidl;

parcelable Book;

2.IBookManager.aidl 用于定义方法接口,完成进程间通信
package com.ryg.chapter_2.aidl;

import com.ryg.chapter_2.aidl.Book;
import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener;

interface IBookManager {
     List<Book> getBookList();
     void addBook(in Book book);
     void registerListener(IOnNewBookArrivedListener listener);
     void unregisterListener(IOnNewBookArrivedListener listener);
}

在这里插入图片描述
服务端中的Service给与其绑定的客户端提供Binder对象,客户端通过ADL接口中的asInterfacel()将这个Binder对象转换为代理Proxy,并通过它发起RPC请求。客户端发起请求时会挂起当前线程,并将参数写入data然后调用transact(),RPC请求会通过系统底层封装后由服务端的onTransact()处理,并将结果写入reply,最后返回调用结果并唤醒客户端线程。

可能产生ANR的情形及解决办法:

  1. 对于客户端,且假设在主线程调用方法:
    调用服务端的方法是运行在服务端的Binder线程池中,若所调用的方法里执行了较耗时的任务,同时会导致客户端线程长时间阻塞,易导致客户端ANR。
    onServiceConnected()onServiceDisconnected()里直接调用服务端的耗时方法,易导致客户端ANR。
  2. 对于服务端:
    服务端的方法本身就运行在服务端的Binder线程中,可在其中执行耗时操作,而无需再开启子线程。
    回调客户端Listener的方法是运行在客户端的Binder线程中,若所调用的方法里执行了较耗时的任务,易导致服务端ANR。
  3. 解决客户端频繁调用服务器方法导致性能极大损耗的办法:实现观察者模式。即当客户端关注的数据发生变化时,再让服务端通知客户端去做相应的业务处理。
    比如:每个客户端的请求Listener传递给服务端,服务端用一个list保存,当数据变化时服务器再依次通知,此时客户端就用Listener进行回调处理。注意要用Handler切换到主线程。

RemoteCallbackList:
跨进程删除listener的接口,泛型,支持管理任意的AIDL接口;内部自动实现线程同步
客户端进程终止后,它能够自动移除客户端注册的listener
工作原理:在它的内部有一个Map结构专门用来保存所有的AIDL回调,Map的key是IBinder类型,value 是Callback类型

ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback> (); 

其中Callback 中封装了真正的远程listener. 当客户端注册listener 的时候,它会把这个listener的信息存入mCallbacks中,其中key和value分别通过下面的方式获得:

IBinder key= listener.asBinder()
Callback value =new Callback (listener, cookie)

做的什么:
多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但他们的底层Binder对象是同一个,客户端解注册时,遍历服务器所有的listener, 找到和解注册有相同Binder对象的服务端listener删掉
需要注意:
使用RemoteCallbackList需要注意,无法像操作List 一样去操作它

5.使用Messager

Messenger:轻量级的IPC方案,通过它可在不同进程中传递Message对象
特点:

  1. 底层实现是AIDL,即对AIDL进行了封装,更便于进行进程间通信。

  2. 其服务端以串行的方式来处理客户端的请求,不存在并发执行的情形,无需考虑线程同步的问题。

  3. 可在不同进程中传递Message对象,Messager可支持的数据类型:

    • arg1、arg2、what字段:int型数据
    • obj字段:Object对象,支持系统提供的Parcelable对象
    • setData:Bundle对象
  4. 有两个构造函数,分别接收Handler对象和Binder对象。

Message的缺点:
主要作用是传递 Message,难以实现远程方法调用。
以串行的方式处理客户端发来的消息的,不适合高并发的场景。
在这里插入图片描述

6.使用ContentProvider

除了onCreat()运行在UI线程中,其他的query()、update()、insert()、delete()和getType()都运行在Binder线程池中。
CRUD四大操作存在多线程并发访问,要注意在方法内部要做好线程同步。
一个SQLiteDatabase内部对数据库的操作有同步处理,但多个SQLiteDatabase之间无法同步
1.ContentProvider主要以表格的形式来组织数据,并且可以包含多个表;
2.ContentProvider还支持文件数据,比如图片、视频等,系统提供的MediaStore就是文件类型的ContentProvider;
3.ContentProvider对底层的数据存储方式没有任何要求,可以是SQLite、文件,甚至是内存中的一个对象都行;
4.要观察ContentProvider中的数据变化情况,可以通过ContentResolver的registerContentObserver方法来注册观察者;
5.ContentProvider除了支持对数据源的增删改查操作,还支持自定义调用,这个过程是通过ContentProvider的Call方法和ContentResolver的Call方法完成
实例使用代码可以看pdf100页分析 容易理解

7.使用Socket

Socket(套接字):不仅可跨进程,还可以跨设备通信。分为两种:
流套接字:基于TCP协议,采用流的方式提供可靠的字节流服务。
数据报套接字:基于UDP协议,采用数据报文提供数据打包发送的服务。
实现方法:TCP/UDP

服务端:
创建一个Service,在线程中建立TCP服务、监听相应的端口等待客户端连接请求;
与客户端连接时,会生成新的Socket对象,利用它可与客户端进行数据传输;
与客户端断开连接时,关闭相应的Socket并结束线程。
客户端:
开启一个线程、通过Socket发出连接请求;
连接成功后,读取服务端消息;
断开连接,关闭Socket。
在这里插入图片描述
注意:
1.不能在主线程中访问网络
2.需要声明权限

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

IPC方法比较

在这里插入图片描述

4.Binder连接池

有多个业务模块都需要AIDL来进行IPC,此时需要为每个模块创建特定的aidl文件,那么相应的Service就会很多。必然会出现系统资源耗费严重、应用过度重量级的问题。
作用:将每个业务模块的Binder请求统一转发到一个远程Service中去执行,从而避免重复创建Service
整个工作机制是
1.每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来
2.然后向服务端提供自己的唯一标识和其对应的Binder对象;
3.为Binder连接池创建AIDL接口IBinderPool.aidl并具体实现;
4.远程服务BinderPoolService的实现,在onBind()返回实例化的IBinderPool实现类对象;
5.Binder连接池的具体实现,来绑定远程服务。
6.对于服务端来说,只需要一个Service,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们
6.客户端就可以通过它的queryBinder方法去获取各自对应的Binder,拿到所需的Binder
Binder连接池

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值