1、WindowContainerTransaction是什么:
windowContainerTransaction类的对象是用来存储对windowContainer的修改的一个集合,windowContainer。因为应用侧是无法直接操作windowContainer的,如果应用侧需要修改windowContainer的话,需要通过系统侧对windowContainer进行修改,这就涉及了信息的跨进程传输了。所以首先,WindowContainerTransaction类应该是一个磕跨进程传输的类。看到定义:
public final class WindowContainerTransaction implements Parcelable
也证实了这个猜测,其实现了Parcelable接口。然后就是另一个问题,应用通过系统侧修改WindowContainer的话,必定会告诉他修改窗口的什么属性,然后进去这个类找找代码:
* Sets whether a container or its children should be hidden. When {@code false}, the existing
* visibility of the container applies, but when {@code true} the container will be forced
* to be hidden.
*/
@NonNull
public WindowContainerTransaction setHidden(
@NonNull WindowContainerToken container, boolean hidden) {
Change chg = getOrCreateChange(container.asBinder());
chg.mHidden = hidden;
chg.mChangeMask |= Change.CHANGE_HIDDEN;
return this;
}
这里可以看到一个设置窗口及其子窗口是否被隐藏的接口,暂时不详细叙述这个方法的实现。这里可以看到修改这个hidden属性是通过一个Change类的对象实现的,Change类是WindowContainerTransaction的内部类,也是实现了Parcelable接口的。而且WindowContainerTransaction类内部也维护了一个包含Change类的Map:
private final ArrayMap<IBinder, Change> mChanges = new ArrayMap<>();
所以可以大概知道,对窗口的修改应该是通过Change类的对象去描述的。
所以目前可以得知WindowContainerTransaction类的作用,他的对象就是应用侧需要对窗口进行的修改的集合。而这个修改,则是通过Change类的对象描述的。
这里通过一个例子来展示下WindowContainerTransaction类的使用,该段代码位于TaskView.java中:
private void updateTaskVisibility() {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
mSyncQueue.queue(wct);
if (mListener == null) {
return;
}
int taskId = mTaskInfo.taskId;
mSyncQueue.runInSync((t) -> {
mListenerExecutor.execute(() -> {
mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated);
});
});
}
由上述示例可见这个WindowContainerTransaction类的使用过程为:1.创建一个WindowContainerTransaction对象;2.通过该对象设置你需要修改的属性;3.将包含步骤2设置的修改的wct对象放入队列,runInsync方法就放到后面再讲。
2、如何通过WindowContainerTransaction修改窗口属性:
前面已经讲到了WindowContainerTransaction中的setHidden方法,那就继续分析这个方法:
public WindowContainerTransaction setHidden(
@NonNull WindowContainerToken container, boolean hidden) {
Change chg = getOrCreateChange(container.asBinder());
chg.mHidden = hidden;
chg.mChangeMask |= Change.CHANGE_HIDDEN;
return this;
}
1)、看参数列表:
参数列表第一个是一个WindowContainerToken对象,看一下定义,这个类是什么含义:
public final class WindowContainerToken implements Parcelable {
private final IWindowContainerToken mRealToken;
/** @hide */
public WindowContainerToken(IWindowContainerToken realToken) {
mRealToken = realToken;
}
private WindowContainerToken(Parcel in) {
mRealToken = IWindowContainerToken.Stub.asInterface(in.readStrongBinder());
}
/** @hide */
public IBinder asBinder() {
return mRealToken.asBinder();
}
…………………………………
}
从代码上看,这个类也是实现了Parcelable,然后主要是包装了一个IWindowContainerToken类型的对象。因为系统侧的WMS里要管理所有的WindowContainer,那么WMS中该如何区分每一个WindowContainer呢?那就得用唯一的身份标识,即这个IWindowContainerToken,通过这个Token,WMS可以区分不同的WindowContainer,可以简单理解成,我们每个人其实在国家的户籍管理的制度里,就是用一个身份证号进行辨识的,在人口管理的系统中,我们是以身份证号进行区分的,所以身份证号就是这个Token。而WMS也可以通过WindowContainerToken的asBinder()方法访问到对应WindowContainer的方法。
而我们怎么获取到这个WindowContainerToken方法呢?从示例代码中可见:
wct.setWindowingMode(topRunningTask.token, WINDOWING_MODE_UNDEFINED);
可以通过TaskInfo类的token属性获取。看到TaskInfo类的token属性,正是WindowContainerToken类型的对象。那问题又来了,我们的这个token属性是在什么时候进行的赋值操作的呢?
这里得找到Task.java中的fillTaskInfo()方法了,这里可见info.token的赋值:
void fillTaskInfo(TaskInfo info, boolean stripExtras, @Nullable TaskDisplayArea tda) {
………………………………………………………………
info.token = mRemoteToken.toWindowContainerToken();
………………………………………………………………
}
当然,根据这个RemoteToken类型的实例化可以找到,只有DisplayArea、TaskFragment和Task类初始化时会初始化RemoteToken对象。
这里再提一个小小的疑问:mRemoteToken又是什么?看到Task类中没有定义,于是找到其父类中,最后在WindowContainer类中发现如下定义:
首先他实现了一个aidl接口的Stub类,所以其实RemoteToken就是一个binder的server端的抽象类Stub的子类的实现 。他包含了对WindowContainer的引用和WindowContainerToken。
static class RemoteToken extends IWindowContainerToken.Stub {
final WeakReference<WindowContainer> mWeakRef;
private WindowContainerToken mWindowContainerToken;
RemoteToken(WindowContainer container) {
mWeakRef = new WeakReference<>(container);
}
@Nullable
WindowContainer getContainer() {
return mWeakRef.get();
}
static RemoteToken fromBinder(IBinder binder) {
return (RemoteToken) binder;
}
WindowContainerToken toWindowContainerToken() {
if (mWindowContainerToken == null) {
mWindowContainerToken = new WindowContainerToken(this);
}
return mWindowContainerToken;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("RemoteToken{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(' ');
sb.append(mWeakRef.get());
sb.append('}');
return sb.toString();
}
}
而且在WindowContainerToken中的mRealToken其实就是IWindowContainerToken的对象,也就是说,mRealToken其实就是一个RemoteToken类型的对象,他才是能够通过弱引用真正的指向WindowContainer,同时还包含WindowContainerToken属性。这里再多说一个点,就是WindowContainerToken,只有Task和DisplayArea才会有(RemoteToken只有这两个类才会初始化,Task类和TaskFragment都算做Task)。
然后第二个参数就是需要设置的属性了,就不再多说;
2)、setHidden方法的实现:
public WindowContainerTransaction setHidden(
@NonNull WindowContainerToken container, boolean hidden) {
Change chg = getOrCreateChange(container.asBinder());
chg.mHidden = hidden;
chg.mChangeMask |= Change.CHANGE_HIDDEN;
return this;
}
该方法第一步,定义一个Change对象chg,定义后通过getOrCreateChange方法对chg进行赋值。继续查看该方法的实现。
private Change getOrCreateChange(IBinder token) {
Change out = mChanges.get(token);
if (out == null) {
out = new Change();
mChanges.put(token, out);
}
return out;
}
定义一个Change类型的对象out,然后在mChanges中去get一个Change,看下mChanges的定义发现,这是一个ArrayMap。Key是IBinder(这个IBinder的对象就是一个WindowContainerToken的对象,对应唯一的WindowContainer),value是Change。
private final ArrayMap<IBinder, Change> mChanges = new ArrayMap<>();
那就知道了,肯定有有地方会把这个键值对IBinder和Change组成的键值对放进mChanges中。这里如果是刚初始化的WindowContainerTransaction对象,mChanges肯定是空的,所以out == null,则会将token和out放进mChanges,并返回out。注意这里out只是一个默认的Change()方法构造出来的对象。
public static class Change implements Parcelable {
public static final int CHANGE_FOCUSABLE = 1;
public static final int CHANGE_BOUNDS_TRANSACTION = 1 << 1;
public static final int CHANGE_PIP_CALLBACK = 1 << 2;
public static final int CHANGE_HIDDEN = 1 << 3;
public static final int CHANGE_BOUNDS_TRANSACTION_RECT = 1 << 4;
public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 5;
public static final int CHANGE_FORCE_NO_PIP = 1 << 6;
public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 7;
public static final int CHANGE_DRAG_RESIZING = 1 << 8;
private final Configuration mConfiguration = new Configuration();
private boolean mFocusable = true;
private boolean mHidden = false;
private boolean mIgnoreOrientationRequest = false;
private boolean mForceTranslucent = false;
private boolean mDragResizing = false;
private int mChangeMask = 0;
private @ActivityInfo.Config int mConfigSetMask = 0;
private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
private Rect mPinnedBounds = null;
private SurfaceControl.Transaction mBoundsChangeTransaction = null;
private Rect mBoundsChangeSurfaceBounds = null;
private int mActivityWindowingMode = -1;
private int mWindowingMode = -1;
public Change() {}
…………………………………………………………………………………………………
}
所以Out的属性都是默认值,接下来就是对这个Change进行赋值了,我们看的是setHidden方法,所以需要修改Hidden相关的属性。首先是修改chg.mHidden属性,然后修改chg.mChangeMask。
mHidden属性就可以简单的认为是否需要隐藏,下面的mChangeMask就是一个标志位,通过这个属性能得知提交的WindowContainerTransaction进行了哪些属性的修改。
通过Change的初始值可知,mChangeMask = 0;是int类型。CHANGE_HIDDEN = 1<<3; 通过将二者进行异或运算,就能将标志着HIDDEN属性变化的第三位设置为1.
于是,在系统侧应用WindowContainerTransaction的时候,可以看到,这里就会通过mChangeMask属性与标志位的结果进行判断后,再对修改进行应用。
private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) {
int effects = applyChanges(tr, c, null /* errorCallbackToken */);
final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
if (tr.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, c.getHidden())) {
effects = TRANSACT_EFFECTS_LIFECYCLE;
}
}
if ((c.getChangeMask()
& WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT) != 0) {
tr.setForceTranslucent(c.getForceTranslucent());
effects = TRANSACT_EFFECTS_LIFECYCLE;
}
…………………………………………………………………………………………
}
3)reparent方法的实现:
public WindowContainerTransaction reparent(@NonNull WindowContainerToken child,
@Nullable WindowContainerToken parent, boolean onTop) {
mHierarchyOps.add(HierarchyOp.createForReparent(child.asBinder(),
parent == null ? null : parent.asBinder(),
onTop));
return this;
}
这里不是通过Change保存reparent操作,而是通过mHierarchyOps来保存修改的。看下定义,mHierarchyOps 是一个ArrayList类型的对象。里面存储的是HierarchyOp类型的变量。
private final ArrayList<HierarchyOp> mHierarchyOps = new ArrayList<>();
然后再看到HierarchyOp的构造方法,这里container就是需要重新设置parent的WindowContainer,然后reparent就是新的parent,toTop代表reparent操作后是否需要将子WindowContainer移动到父
WindowContainer的Top。
public static HierarchyOp createForReparent(
@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
.setContainer(container)
.setReparentContainer(reparent)
.setToTop(toTop)
.build();
}
本文代码都是基于Android13。