下图是EFDC_EE的运行时间参数的设置界面,在时间步长的设置中,如果“Safety Factor”参数,设置为非0,即可实现自适应时间步长的设置,但要注意如下几点:
1、“Safety Factor”参数大小:如果小于1大于0,一般来说,安全系数较大,不容易出现迭代问题,如0.7或0.8,1也是不错的选择,但此时应尽量将Time Step设置得小一些;
2、尽管设置了自适应时间步长,但时间步长在变化过程中,好像是time step的倍数,即如果设置的time step是3的话,自适应时间步长的变化可能是3、6、9、12、15、18等,所以time step的值越大,自适应时间步长最后能不能迭代收敛,风险也越大;
3、time step和“Safety Factor”参数密切相关,所以两者的选择要适当。
本文链接:http://www.cnblogs.com/China3S/p/3189228.html,转载请注明。
一、Binder架构
在Android中,Binder用于完成进程间通信(IPC),即把多个进程关联在一起。比如,普通应用程序可以调用音乐播放服务提供的播放、暂停、停止等功能。
Binder工作在Linux层面,属于一个驱动,只是这个驱动不需要硬件,或者说其操作的硬件是基于一小段内存。从线程的角度来讲,Binder驱动代码运行在内核态,客户端程序调用Binder是通过系统调用完成的。
Binder是一种架构,这种架构提供了服务端接口、Binder驱动、客户端接口三个模块。
服务端:一个Binder服务端实际上就是一个Binder类的对象,该对象一旦创建,内部就启动一个隐藏线程。该线程接下来会接收Binder驱动发送的消息,收到消息后,会执行到Binder对象中的onTransact()函数,并按照该函数的参数执行不同的服务代码。因此,要实现一个Binder服务,就必须重载onTransact()方法。重载onTransact()函数的主要内容是把onTransact()函数的参数转换为服务函数的参数,而onTransact()函数的参数来源是客户端调用transact()函数时输入的,因此,如果transact()有固定格式的输入,那么onTransact()就会有固定格式的输出。
Binder驱动:任意一个服务端Binder对象被创建时,同时会在Binder驱动中创建一个mRemote对象,该对象的类型也是Binder类。客户端要访问远程服务时,都是通过mRemote对象。
客户端:客户端要想访问远程服务,必须获取远程服务在Binder对象中对应的mRemote引用,至于如何获取,下面将会介绍。获得该mRemote对象后,就可以调用其transact()方法,而在Binder驱动中,mRemote对象也重载了transact()方法,重载的内容主要包括以下几项。
- 以线程间消息通信的模式,向服务端发送客户端传递过来的参数。
- 挂起当前线程,当前线程正是客户端线程,并等待服务端线程执行完指定服务函数后通知(notify)。
- 接收到服务端线程的通知,然后继续执行客户端线程,并返回到客户端代码区。
从这里可以看出,对应用程序开发员来讲,客户端似乎是直接调用远程服务对应的Binder,而实际上是通过Binder驱动进行了中转。即存在两个Binder对象,一个是服务端的Binder对象,另一个则是Binder驱动中的Binder对象,不同的是Binder驱动中的对象不会再额外产生一个线程。
二、Service端
设计Service端很简单,从代码的角度来讲,只要基于Binder类新建一个Servier类即可。以下以设计一个Service类为例。
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException { // 接收客户端传过来的消息参数
return super.onTransact(code, data, reply, flags);
}
public void start(String name) {
}
public void stop() {
}
}
当要启动该服务时,只需要初始化一个MyService对象即可。之后可以在DDMS中看到多了一个线程
定义了服务类后,接下来需要重载onTrasact()方法,并从data变量中读出客户端传递的参数,比如start()方法所需要的name变量。然而,服务端如何知道这个参数在data变量中的位置?因此,这就需要调用者和服务者双方有个约定。假定客户端在传入的包裹data中放入的第一个数据就是filePath变量,则onTransact()的代码可以如下所示:
case 0x100:
data.enforceInterface("MyService");
String name = data.readString();
start(name);
break;
}
code变量用于标识客户端期望调用服务端的哪个函数,因此,双方需要约定一组int值,不同的值代表不同的服务端函数,该值和客户端的transact()函数中第一个参数code的值是一致的。这里假定0x100是双方约定要调用start()函数的值。
enforceInterface()是为了某种校验,它与客户端的writeInterfaceToken()对应,具体见下一小节。
readString()用于从包裹中取出一个字符串。取出filePath变量后,就可以调用服务端的start()函数了。如果该IPC调用的客户端期望返回一些结果,则可以在返回包裹reply中调用Parcel提供的相关函数写入相应的结果。Parcel.writeXXX();
三、Binder客户端设计
要想使用服务端,首先要获取服务端在Binder驱动中对应的mRemote变量的引用,获取的方法后面将介绍。获得该变量的引用后,就可以调用该变量的transact()方法。该方法的函数原型:
public final boolean transact(int code, Parcel data, Parcel reply,int flags)
其中data表示的是要传递给远程Binder服务的包裹(Parcel),远程服务函数所需要的参数必须放入这个包裹中。包裹中只能放入特定类型的变量,这些类型包括常用的原子类型,比如String、int、long等,要查看包裹可以放入的全部数据类型,可以参照Parcel类。除了一般的原子变量外,Parcel还提供了一个writeParcel()方法,可以在包裹中包含一个小包裹。因此,要进行Binder远程服务调用时,服务函数的参数要么是一个原子类,要么必须继承于Parcel类,否则,是不能传递的。
String name = "Livingstone";
int code = 0x100;
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("MyService");
data.writeString(name);
mRemote.transact(code, data, reply, 0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();
现在来分析以上代码。首先,包裹不是客户端自己创建的,而是调用Parcel.obtain()申请的,这正如生活中的邮局一样,用户一般只能用邮局提供的信封(尤其是EMS)。其中data和reply变量都由客户端提供,reply变量用户服务端把返回的结果放入其中。
writeInterfaceToken()方法标注远程服务名称,理论上讲,这个名称不是必需的,因为客户端既然已经获取指定远程服务的Binder引用,那么就不会调用到其他远程服务。该名称将作为Binder驱动确保客户端的确想调用指定的服务端。
writeString()方法用于向包裹中添加一个String变量。注意,包裹中添加的内容是有序的,这个顺序必须是客户端和服务端事先约定好的,在服务端的onTransact()方法中会按照约定的顺序取出变量。
接着调用transact()方法。调用该方法后,客户端线程进入Binder驱动,Binder驱动就会挂起当前线程,并向远程服务发送一个消息,消息中包含了客户端传进来的包裹。服务端拿到包裹后,会对包裹进行拆解,然后执行指定的服务函数,执行完毕后,再把执行结果放入客户端提供的reply包裹中。然后服务端向Binder驱动发送一个notify的消息,从而使得客户端线程从Binder驱动代码区返回到客户端代码区。
transact()的最后一个参数的含义是执行IPC调用的模式,分为两种:一种是双向,用常量0表示,其含义是服务端执行完指定服务后会返回一定的数据;另一种是单向,用常量1表示,其含义是不返回任何数据。
最后,客户端就可以从reply中解析返回的数据了,同样,返回包裹中包含的数据也必须是有序的,而且这个顺序也必须是服务端和客户端事先约定好的。
四、使用Service类
以上手工编写Binder服务端和客户端的过程存在两个重要问题。
第一,客户端如何获得服务端的Binder对象引用。
第二,客户端和服务端必须事先约定好两件事情。
服务端函数的参数在包裹中的顺序。
服务端不同函数的int型标识。
关于第一个问题,为什么要用Binder。答案很简单,即为了提供一个全局服务,所谓的“全局”,是指系统中的任何应用程序都可以访问。很明显,这是一个操作系统应该提供的最基本的功能之一,Android的工程师自然也是这么认为的,因此,他们提供了一个更傻瓜的解决方法,那就是Service。它是Android应用程序四个基本程序片段(Component)之一,四个基本片段包括Activity、Service、Content Provier、Receiver。
无论是否使用Service类,都必须要解决以上两个重要问题。
1、获取Binder对象
事实上,对于有创造力的程序员来讲,可以完全不使用Service类,而仅仅基于Binder类编写服务程序,但只是一部分。具体来讲,可以仅使用Binder类扩展系统服务,而对于客户端服务则必须基于Service类来编写。所谓的系统服务是指可以使用getSystemService()方法获取的服务,所谓的客户端服务是指应用程序提供的自定义服务。
那么,Service类是如何解决本节开头所提出的两个重要问题的呢?
首先,AmS提供了startService()函数用于启动客户服务,而对于客户端来讲,可以使用以下两个函数来和一个服务建立连接,其原型在android.app. ContextImpl类中。
public ComponentName startService(Intent intent);
该函数用于启动intent指定的服务,而启动后,客户端暂时还没有服务端的Binder引用,因此,暂时还不能调用任何服务功能。
没有评论:
发表评论