8.3.2 Surface之乾坤大挪移
1.乾坤大挪移的表象
relayout的函数是一个跨进程的调用,由WMS完成实际处理。先到ViewRoot中看看调用方的用法,代码如下所示:
[—>ViewRoot.java]
private int relayoutWindow(WindowManager.LayoutParams params,
int viewVisibility,boolean insetsPending)
throws RemoteException{
int relayoutResult=sWindowSession.relayout(
mWindow,params,
(int)(mView.mMeasuredWidth*appScale+0.5f),
(int)(mView.mMeasuredHeight*appScale+0.5f),
viewVisibility,insetsPending,mWinFrame,
mPendingContentInsets,mPendingVisibleInsets,
mPendingConfiguration,mSurface);//mSurface传了进去。
……
return relayoutResult;
}
再看接收方的处理。它在WMS的Session中,代码如下所示:
[—>WindowManagerService.java:Session]
public int relayout(IWindow window,WindowManager.LayoutParams attrs,
int requestedWidth,int requestedHeight,int viewFlags,
boolean insetsPending,Rect outFrame,Rect outContentInsets,
Rect outVisibleInsets,Configuration outConfig,
Surface outSurface){
//注意最后这个参数的名字,叫outSurface。
//调用外部类对象的relayoutWindow。
return relayoutWindow(this,window,attrs,
requestedWidth,requestedHeight,viewFlags,insetsPending,
outFrame,outContentInsets,outVisibleInsets,outConfig,
outSurface);
}
[—>WindowManagerService.java]
public int relayoutWindow(Session session,IWindow client,
WindowManager.LayoutParams attrs,int requestedWidth,
int requestedHeight,int viewVisibility,boolean insetsPending,
Rect outFrame,Rect outContentInsets,Rect outVisibleInsets,
Configuration outConfig,Surface outSurface){
……
try{
//win就是WinState,这里将创建一个本地的Surface对象。
Surface surface=win.createSurfaceLocked();
if(surface!=null){
//先创建一个本地surface,然后在outSurface的对象上调用copyFrom。
//将本地Surface的信息拷贝到outSurface中,为什么要这么麻烦呢?
outSurface.copyFrom(surface);
}
}
……
}
[—>WindowManagerService.java:WindowState]
Surface createSurfaceLocked(){
……
try{
//mSurfaceSession就是在Session上创建的SurfaceSession对象。
//这里以它为参数,构造一个新的Surface对象。
mSurface=new Surface(
mSession.mSurfaceSession,mSession.mPid,
mAttrs.getTitle().toString(),
0,w,h,mAttrs.format,flags);
}
Surface.openTransaction();//打开一个事务处理。
……
Surface.closeTransaction();//关闭一个事务处理。关于事务处理以后再分析。
……
}
上面的代码段好像有点混乱。用图8-7来表示一下这个流程:
根据图8-7可知:
WMS中的Surface是“乾坤”中的“乾”,它的构造使用了带SurfaceSession参数的构造函数。
ViewRoot中的Surface是“乾坤”中的“坤”,它的构造使用了无参构造函数。
copyFrom就是挪移,它将乾中的Surface信息,拷贝到坤中的Surface即outSurface里。
要是觉得乾坤大挪移就是这两三下,未免就太小看它了。为了彻底揭示这期间的复杂过程,我们将使用必杀技——aidl工具。
图 8-7 复杂的Surface创建流程
2.揭秘Surface的乾坤大挪移
aidl可以把XXX.aidl文件转换成对应的Java文件。刚才所说的乾坤大挪移发生在ViewRoot调用IWindowSession的relayout函数中,它在IWindowSession.adil中的定义如下:
[—>IWindowSesson.aidl]
interface IWindowSession{
……
int relayout(IWindow window,in WindowManager.LayoutParams attrs,
int requestedWidth,int requestedHeight,int viewVisibility,
boolean insetsPending,out Rect outFrame,out Rect outContentInsets,
out Rect outVisibleInsets,out Configuration outConfig,
out Surface outSurface);
下面,拿必杀技aidl来编译一下这个aidl文件,使用方法如下:
在命令行下可以输入:
aidl-Ie:\froyo\source\frameworks\base\core\java-Ie:\froyo\source\frameworks\
base\Graphics\java e:\froyo\source\frameworks\base\core\java\android\view\IWindowSession.aidl test.java
新生成的Java文件叫test.java。其中,-I参数指定include目录,这是因为aidl文件中可能使用了别的Java文件中的类,所以需要指定这些Java文件所在的目录。
先看ViewRoot这个客户端生成的代码,如下所示:
[—>test.java:Bp端:relayout]
public int relayout(android.view.IWindow window,
android.view.WindowManager.LayoutParams attrs,
int requestedWidth,int requestedHeight,
int viewVisibility,boolean insetsPending,
android.graphics.Rect outFrame,
android.graphics.Rect outContentInsets,
android.graphics.Rect outVisibleInsets,
android.content.res.Configuration outConfig,
android.view.Surface outSurface)//outSurface是第11个参数。
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.writeStrongBinder((((window!=null))?(window.asBinder()):(null)));
if((attrs!=null)){
_data.writeInt(1);
attrs.writeToParcel(_data,0);
}
else{
_data.writeInt(0);
}
_data.writeInt(requestedWidth);
_data.writeInt(requestedHeight);
_data.writeInt(viewVisibility);
_data.writeInt(((insetsPending)?(1):(0)));
//奇怪,outSurface的信息没有写到请求包_data中,就直接发送请求消息了。
mRemote.transact(Stub.TRANSACTION_relayout,_data,_reply,0);
_reply.readException();
_result=_reply.readInt();
if((0!=_reply.readInt())){
outFrame.readFromParcel(_reply);
}
……
if((0!=_reply.readInt())){
outSurface.readFromParcel(_reply);//从Parcel中读取信息来填充outSurface。
}
}
……
return_result;
}
奇怪!ViewRoot调用requestlayout竟然没有把outSurface信息传进去,这么说,服务端收到的Surface对象应该就是空吧?那怎么能调用copyFrom呢?还是来看服务端的处理,首先看收到消息的onTransact函数,代码如下所示:
[—>test.java:Bn端:onTransact]
public boolean onTransact(int code,android.os.Parcel data,
android.os.Parcel reply,int flags)
throws android.os.RemoteException
{
switch(code)
{
case TRANSACTION_relayout:
{
data.enforceInterface(DESCRIPTOR);
android.view.IWindow_arg0;
android.view.Surface_arg10;
//刚才讲了,Surface信息并没有传过来,那么在relayOut中看到的outSurface是怎么
//出来的呢?看下面这句话便可知,原来在服务端这边竟然new了一个新的Surface!
_arg10=new android.view.Surface();
int_result=this.relayout(_arg0,_arg1,_arg2,_arg3,_arg4,
_arg5,_arg6,_arg7,_arg8,_arg9,_arg10);
reply.writeNoException();
reply.writeInt(_result);
//_arg10就是调用copyFrom的那个outSurface,那怎么传到客户端呢?
if((_arg10!=null)){
reply.writeInt(1);
//调用Surface的writeToParcel,把信息写到reply包中。
//注意最后一个参数为PARCELABLE_WRITE_RETURN_VALUE。
_arg10.writeToParcel(reply,
android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
}
……
return true;
}
看完这个你是否会觉得大吃一惊?我最开始一直在JNI文件中寻找大挪移的踪迹,但是有几个关键点始终不能明白,万不得已就使用了这个aidl必杀技,终于揭露了真相。
3.乾坤大挪移的真相
这里总结一下乾坤大挪移的整个过程,如图8-8表示:
上图非常清晰地列出了乾坤大挪移的过程,我们可结合代码来加深理解。
注意 这里将BpWindowSession作为了IWindowSessionBinder在客户端的代表。
图 8-8 乾坤大挪移的真面目