窗口管理使用到的 DisplayContent,WindowToken 和 WindowState。
DisplayContent
用来管理一个逻辑屏上的所有窗口,有几个屏幕就会有几个 DisplayContent。使用 displayId 来区分。
处于不同 DisplayContent 的两个窗口在布局、显示顺序以及动画处理上不会产生任何耦合。因此,就这几个方面来说,DisplayContent 就像一个孤岛,所有这些操作都可以在其内部独立执行。
DisplayContent 类声明:
class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer>{ // Mapping from a token IBinder to a WindowToken object on this display. private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();}
public int addWindow(Session session, ..){ final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId); ..}
mRoot 是 RootWindowContainer 类型的对象,看名字就知道其是窗口容器的根。说明在 Window 体系中,RootWindowContainer 节点是容器最顶端的父容器。
class RootWindowContainer extends WindowContainer<DisplayContent> { DisplayContent getDisplayContentOrCreate(int displayId) { DisplayContent dc = getDisplayContent(displayId); if (dc == null) { final Display display = mService.mDisplayManager.getDisplay(displayId); if (display != null) { dc = createDisplayContent(display); } } return dc; } DisplayContent getDisplayContent(int displayId) { for (int i = mChildren.size() - 1; i >= 0; --i) { final DisplayContent current = mChildren.get(i); if (current.getDisplayId() == displayId) { return current; } } return null; }}
其继承了 WindowContainer,这里的 DisplayContent 是一个泛型声明,表示其子容器的类型是 DisplayContent, 在 getDisplayContent 方法中也可知,其 mChildren 列表是 DisplayContent 的集合。这也变相的说明 DisplayContent 也是一个容器。
WindowToken
类声明:
class WindowToken extends WindowContainer<WindowState>
表明 WindowToken 也是子容器,其子容器是 WindowState,所以 WindowState 也是一个容器。
WindowToken 在窗口体系中有两个作用:
但是系统窗口是个例外,并不需要提供 token,WMS 会隐式声明一个WindowToken。那是不是说谁都可以添加系统窗口了呢?非也,在 addWindow 开始处就会调用下面代码:
mPolicy.checkAddPermission()
它要求客户端必须拥有 SYSTEM_ALERT_WINDOW 或INTERNAL_SYSTEM_WINDOW 权限才能创建系统类型的窗口。Window 和WindowToken 关系如下:
WindowState
类声明:
class WindowState extends WindowContainer<WindowState>
表明 WindowState 也是一个 WindowContainer 容器,但是其子容器也是WindowState,一般窗口有子窗口 SUB_WINDOW 的情况下,WindowState 才有子容器节点。WindowState 在 WMS 中表示一个 Window 窗口状态属性,其内部保存了一个 Window 所有的属性信息。其与 View 以及 WindowToken 关系如下:
如何查看当前设备 Window 窗口状态命令?
adb shell dumpsys window windows
Window #9 Window{884cb45 u0 com.android.messaging/com.android.messaging.ui.conversationlist.ConversationListActivity}: mDisplayId=0 stackId=3 mSession=Session{f1b7b8e 4307:u0a10065} mClient=android.os.BinderProxy@a512fbc mOwnerUid=10065 mShowToOwnerOnly=true package=com.android.messaging appop=NONE mAttrs={(0,36)(828xwrap) gr=BOTTOM CENTER sim={adjust=pan forwardNavigation} ty=APPLICATION fmt=TRANSLUCENT wanim=0x7f130015 fl=DIM_BEHIND ALT_FOCUSABLE_IM HARDWARE_ACCELERATED vsysui=LIGHT_STATUS_BAR LIGHT_NAVIGATION_BAR} Requested w=828 h=290 mLayoutSeq=220 mBaseLayer=21000 mSubLayer=0 mToken=AppWindowToken{3f9efb8 token=Token{2b272cc ActivityRecord{55a41e u0 com.android.messaging/.ui.conversationlist.ConversationListActivity t8}}} mAppToken=AppWindowToken{3f9efb8 token=Token{2b272cc ActivityRecord{55a41e u0 com.android.messaging/.ui.conversationlist.ConversationListActivity t8}}}...
下面笔者以窗口的添加操作为例讲解 WMS 的窗口管理。
窗口的添加操作
public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { ... int res = mPolicy.checkAddPermission(attrs, appOp);//1 ... synchronized(mWindowMap) { final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);//2 if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) { parentWindow = windowForClientLocked(null, attrs.token, false);//3 } ... WindowToken token = displayContent.getWindowToken( hasParent ? parentWindow.mAttrs.token : attrs.token);//4 if (token == null) { final IBinder binder = attrs.token != null ? attrs.token : client.asBinder(); token = new WindowToken(this, binder, type, false, displayContent, session.mCanAddInternalSystemWindow);//5 } ... final WindowState win = new WindowState(this, session, client, token, parentWindow, appOp[0], seq, attrs, viewVisibility, session.mUid, session.mCanAddInternalSystemWindow);//6 ... mPolicy.adjustWindowParamsLw(win.mAttrs);//7 ... if (openInputChannels) { win.openInputChannel(outInputChannel);//8 } ... mWindowMap.put(client.asBinder(), win);//9 ... win.mToken.addWindow(win);//10 ... displayContent.assignWindowLayers(false /* setLayoutNeeded */);//11 //12 if (focusChanged) { mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/); } mInputMonitor.updateInputWindowsLw(false /*force*/); } ... return res;}
这里小结下 addWindow 方法,主要就是创建了一个和 Window 一一对应的 WindowState 对象,并将 WindowState 插入到父容器 WindowToken 的子容器集合中,而 WindowToken 又保存在 DisplayContent 的键值对集合中。三种关系可以简单总结如下:
我们知道在 Android 内部有两种动画,Window 切换移动动画以及 app 层的 View 的动画, 动画操作的是 View 而 Window 切换操作的是 Surface,对不同层级的 SurfaceControl 进行操纵,会产生不同的动画效果,注意区分。
我们这里涉及到的是 Window 切换移动动画。
但是不管是 View 的动画还是 Window 切换操作,对底层屏幕刷新来说都是针对不同帧动画来说,所以会涉及到 VSync 同步信号相关知识。
WindowStateAnimator
类声明:
Keep track of animations and surface operations for a single WindowState.
用来管理一个 Window 的动画操作的,在 WindowState 构造方法中创建,说明每个 Window 窗口都对应一个 WindowStateAnimator。
WindowState(WindowManagerService service...){ mWinAnimator = new WindowStateAnimator(this);}
/** * Singleton class that carries out the animations and Surface operations in a separate task * on behalf of WindowManagerService. */ public class WindowAnimator
看方法说明,这个类还是用于 WMS 中的窗口动画以及 Surface 操作的单例工具类,WMS 将动画的工作都委托他来处理。其在 WMS 构造的时候创建了实例。
WindowAnimator(final WindowManagerService service) { mService = service; .. mWindowPlacerLocked = service.mWindowPlacerLocked;//1 这个类用于Surface的摆放 AnimationThread.getHandler().runWithScissors( () -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */);//2mAnimationFrameCallback = frameTimeNs -> {//3 synchronized (mService.mWindowMap) { mAnimationFrameCallbackScheduled = false; } animate(frameTimeNs);};}
注释1处创建了一个 WindowSurfacePlacer 对象,这个对象是用于 Surface 的摆放的操作,说明 WindowAnimator 还支持 Surface 的各种操作 注释2处使用AnimationThread 线程进行 Window 的动画操作,AnimationThread 内部使用的是 HandlerThread 机制,说明其内部也创建了一个异步消息处理机制。注释3处 mAnimationFrameCallback 类型是 Choreographer.FrameCallback。
FrameCallback 在这篇文章中有讲过,其就是给 Choreographer 设置一个回调,在 Choreographer 接收到 VSync 信号时,在 doFrame 中触发这个回调,一般是用来监听帧率等操作。
而这里是在接收到 doFrame 的时候回调的是一个 animate(frameTimeNs) 动画处理的方法。animate 函数执行流程很长,包括更新壁纸、转屏动画等逻辑均包含在其中。那么 mAnimationFrameCallback 回调是什么时候注册到 Choreographer 中去的呢?
WindowAnimator的scheduleAnimation 方法:
void scheduleAnimation() { if (!mAnimationFrameCallbackScheduled) { mAnimationFrameCallbackScheduled = true; mChoreographer.postFrameCallback(mAnimationFrameCallback); }}
在外部需要进行动画的时候,就会优先 scheduleAnimation,将 mAnimationFrameCallback 注册到 Choreographer 中去。我们重点来看 animate 方法,这个方法内部有这么段代码。
dc.updateWindowsForAnimator(this);
表示为了动画去更新 Windows,可以进入看看。
void updateWindowsForAnimator(WindowAnimator animator) { mTmpWindowAnimator = animator; forAllWindows(mUpdateWindowsForAnimator, true /* traverseTopToBottom */);}boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {...final int count = mChildren.size();for (int i = 0; i < count; i++) { final DisplayChildWindowContainer child = mChildren.get(i); if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) { // In this case the Ime windows will be processed above their target so we skip // here. continue; } if (child.forAllWindows(callback, traverseTopToBottom)) { return true; }}return false;}
forAllWindows 方法会遍历整个容器树都去调用 mUpdateWindowsForAnimator 回调。这个回调内部就会去执行 winAnimator.stepAnimationLocked 去更新 Window 的更新操作。stepAnimationLocked,代表单步动画。这里面的操作大家自行查看也不难。
这里对动画做个小结,通过在需要动画的时候,post 一个 FrameCallBack 给Choreographer,在 VSync 信号到来的时候,会优先执行动画操作。动画回调内部会去遍历整个容器树模型,依次更改每个 Window 对应的 Surface 的状态。然后在绘制完成后,提交给 SurfaceFlinger。过程图示:
关于 Input 事件在这篇文章中已经有讲过。输入子系统从驱动文件中读取事件后,再封装提交给 IMS,IMS 再发送给 WMS 进行处理。
输入系统整体架构:
今天我们从WMS的角度来分析下输入事件。
类声明:
/** * An input channel specifies the file descriptors used to send input events to * a window in another process. It is Parcelable so that it can be sent * to the process that is to receive events. Only one thread should be reading * from an InputChannel at a time. * @hide */ public final class InputChannel implements Parcelable {
注释中说明了 InputChannel 是一个使用文件描述符 fd 来发送 input 事件给其他进程的一个输入通道,且只有一个线程可以同时读取 InputChannel 中的数据,说明 InputChannel 是线程安全的。其内部使用的是 socket 通讯。
前面在分析 Window 添加过程的时候说过在 WMS 的 addWindow 中会调用。
win.openInputChannel(outInputChannel)void openInputChannel(InputChannel outInputChannel) { ... String name = getName(); InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);//1 mInputChannel = inputChannels[0]; mClientChannel = inputChannels[1]; mInputWindowHandle.inputChannel = inputChannels[0]; if (outInputChannel != null) { mClientChannel.transferTo(outInputChannel);//2 mClientChannel.dispose(); mClientChannel = null; } ... mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);//3}InputChannel.java:public static InputChannel[] openInputChannelPair(String name) { ... return nativeOpenInputChannelPair(name);}android_view_InputChannel.cpp:static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env...) { ... sp<InputChannel> serverChannel; sp<InputChannel> clientChannel; status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);jobject serverChannelObj = android_view_InputChannel_createInputChannel(env, std::make_unique<NativeInputChannel>(serverChannel));jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, std::make_unique<NativeInputChannel>(clientChannel));...env->SetObjectArrayElement(channelPair, 0, serverChannelObj);env->SetObjectArrayElement(channelPair, 1, clientChannelObj);return channelPair;}status_t InputChannel::openInputChannelPair(const String8& name, sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) { int sockets[2]; if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) { ... return result; } ... outServerChannel = new InputChannel(serverChannelName, sockets[0]); outClientChannel = new InputChannel(clientChannelName, sockets[1]); return OK;}
通过以上代码可以看出 InputChannel 使用的是 sockets 通讯,且 WindowState 的 openInputChannel 中注释1处:
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name),
返回的 inputChannels 是一个服务端和客户端的输入通道数组。其中:
在注释3处 registerInputChannel 传入的是 server 端 InputChannel 给 IMS。而注释2处将 client 端的 InputChannel 与 app 端传入的 outInputChannel 关联起来了。
这样服务端在 InputChannel 就可以写入 input 事件,然后在 app 端的InputChannel 就可以接受到数据了。输入事件通讯模型如下:
WMS 负责创建 Surface 以及对 Surface 的摆放工作,之后将 Surface 提交给SurfaceFlinger 进行合并。在 App 层也创建了一个 Surface 对象,但是那个是空对象,用于 WMS 的填充。
Surface 的创建在 WMS 中使用 WindowStateAnimator 代理创建,而WindowStateAnimator 中又创建了一个 WindowSurfaceController 对 Surface 进行管理。
public WindowSurfaceController(SurfaceSession s, String name, int w, int h...){ mSurfaceControl = new SurfaceControl(s, name, w, h, format, flags, windowType, ownerUid);}public SurfaceControl(SurfaceSession session, String name, ...){ mNativeObject = nativeCreate(session, name, w, h, format, flags,...);}
在其构造方法中创建了一个 SurfaceControl,SurfaceControl 最终进入 native 层,在 native 层创建了一个 Surface,并返回 native surface 地址。实际在 native 层也是创建一个 SurfaceControl。
static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj, jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject, jint windowType, jint ownerUid) { ``` sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj)); SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject); sp<SurfaceControl> surface = client->createSurface( String8(name.c_str()), w, h, format, flags, parent, windowType, ownerUid); ... return reinterpret_cast<jlong>(surface.get()); ```}
那 app 层是什么时候发起 WMS 的 Surface 创建任务的?看 ViewRootImpl 的 relayoutWindow 方法:
private int relayoutWindow(WindowManager.LayoutParams params..){ int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params...mSurface);}
调用 mWindowSession 的 relayout 方法,并传入最后 mSurface 对象,这是空Surface,在 WMS 中会被填充返回。最终调用到 WMS 中的 relayoutWindow。
public int relayoutWindow(Session session, IWindow client, int seq..){ ... result = createSurfaceControl(outSurface, result, win, winAnimator); }private int createSurfaceControl(Surface outSurface, int result, WindowState win, WindowStateAnimator winAnimator) { ``` WindowSurfaceController surfaceController; try { surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type, win.mOwnerUid); } finally { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } if (surfaceController != null) { surfaceController.getSurface(outSurface);//1 if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " OUT SURFACE " + outSurface + ": copied"); } return result; ``` }
最终会调用 WindowStateAnimator 的 createSurfaceLocked 这个前面已经分析过了。返回的 surfaceController 对象在注释1处调用 getSurface(outSurface),将 native 层的 Surface 填充到 App 传递过来的 outSurface 进入 getSurface 看看。
WindowSurfaceController.javavoid getSurface(Surface outSurface) { outSurface.copyFrom(mSurfaceControl);}Surface.javapublic void copyFrom(SurfaceControl other) { if (other == null) { throw new IllegalArgumentException("other must not be null"); } ``` long surfaceControlPtr = other.mNativeObject; if (surfaceControlPtr == 0) { throw new NullPointerException( "null SurfaceControl native object. Are you using a released SurfaceControl?"); } long newNativeObject = nativeGetFromSurfaceControl(surfaceControlPtr); synchronized (mLock) { if (mNativeObject != 0) { nativeRelease(mNativeObject); } setNativeObjectLocked(newNativeObject); } ```}private void setNativeObjectLocked(long ptr) { if (mNativeObject != ptr) { mNativeObject = ptr; }}
getSurface 方法将 WMS 中创建的 WindowSurfaceController 中 SurfaceControl 对象的 mNativeObject 对象传递给新的 Surface,并使用这个对象去 native 层获取一个新的 NativeObject 赋值给当前 Surface 的 mNativeObject,这样 App 层的 Surface 就获取到了 WMS 在 native 中创建的 SurfaceControl 对象,可以在 app 层操作 native 层的 Surface 了。
那么为什么谷歌要绕这么大圈来创建 Surface 呢?直接在 App 层去创建不就可以了么?
个人见解谷歌是希望统一管理 Surface 而不是单独让某个应用持有,且 Surface 的摆放操作等都是得由 WMS 进行处理,所以就直接让 WMS 去创建,然后返回给 App 层去绘制 Surface 操作。
Surface 在创建之后还需要进行屏幕位置的确认,那这个在哪里操作呢?
WMS 在构造的时候就创建了 WindowSurfacePlacer 对象。这个对象主要用来给Surface 进行位置的定位。定位到 WindowSurfacePlacer 的 performSurfacePlacement 方法,这个方法可以说是 WMS 最核心的方法,其负责了所有窗口的摆放工作。如何显示?显示在屏幕什么位置?区域大小等。这些将在确认后,下发给 SurfaceFlinger 进行处理。
WMS 中任何窗口状态发生改变都会触发该方法,整个方法进行容器树的遍历,确认窗口可见性等。
final void performSurfacePlacement(boolean force) { ``` int loopCount = 6; do { mTraversalScheduled = false; performSurfacePlacementLoop(); loopCount--; } while (mTraversalScheduled && loopCount > 0); ``` }private void performSurfacePlacementLoop() { ... mInLayout = true; mService.mRoot.performSurfacePlacement(recoveringMemory); mInLayout = false; if (mService.mRoot.isLayoutNeeded()) { if (++mLayoutRepeatCount < 6) { requestTraversal(); } else { Slog.e(TAG, "Performed 6 layouts in a row. Skipping"); mLayoutRepeatCount = 0; } } else { mLayoutRepeatCount = 0; } }
performSurfacePlacement 最终会调用到 mService.mRoot.performSurfacePlacement, mService.mRoot.performSurfacePlacement 中最终会执行到对窗口容器树做以下遍历操作,中间代码跳转太多,就略过了。主要做了下面三件事:
private final Consumer<WindowState> mPerformLayout = w -> { ... mService.mPolicy.layoutWindowLw(w, null); ...}mPolicy = PhoneManagerPolicyPhoneManagerPolicy.javapublic void layoutWindowLw(WindowState win, WindowState attached) { //这里面都是对不同给的Window的位置进行确认 computeFrameLw(....);//这个方法会计算所有DisplayFrame以及WindowFrame的大小和位置}
private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> { ..前面一大推处理 winAnimator.setSurfaceBoundariesLocked(mTmpRecoveringMemory /* recoveringMemory */); } winAnimator = WindowStateAnimator WindowStateAnimator.java void setSurfaceBoundariesLocked(final boolean recoveringMemory) { calculateSurfaceBounds(w, attrs);//1 mSurfaceResized = mSurfaceController.setSizeInTransaction( mTmpSize.width(), mTmpSize.height(), recoveringMemory);//2 ... mSurfaceController.setPositionInTransaction((float) Math.floor(posX), (float) Math.floor(posY), recoveringMemory);//3}
注释1处计算 Surface 的 size 大小,然后在注释2处使用 mSurfaceController 设置到 native 层的 SurfaceController 对象中, 注释3处在计算好位置后,也使用 mSurfaceController 设置到 native 层的 SurfaceController 对象中。这样就将 Surface 在屏幕中给的位置以及大小都确认下来了。
boolean commitFinishDrawingLocked() { ``` mDrawState = READY_TO_SHOW; boolean result = false; final AppWindowToken atoken = mWin.mAppToken; if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) { result = mWin.performShowLocked(); } return result; ```}boolean performShowLocked() { final int drawState = mWinAnimator.mDrawState; if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mAttrs.type != TYPE_APPLICATION_STARTING && mAppToken != null) { mAppToken.onFirstWindowDrawn(this, mWinAnimator); } ``` if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) { return false; } logPerformShow("Showing "); mService.enableScreenIfNeededLocked(); mWinAnimator.applyEnterAnimationLocked(); mWinAnimator.mDrawState = HAS_DRAWN; mService.scheduleAnimationLocked();//1 if (mHidden) { mHidden = false; final DisplayContent displayContent = getDisplayContent(); //2 for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowState c = mChildren.get(i); if (c.mWinAnimator.mSurfaceController != null) { c.performShowLocked(); } } } ``` } ````
performShowLocked 方法中有大量的对窗口状态的判断,窗口的显示过程共有五个状态。
在创建 WindowState 后的默认状态,表示当前窗口还创没有执行 relayout() 方法创建 Surface。
执行 relayout() 方法后,创建完成 Surface 后的状态,表示等待绘制。
窗口 Surface 上完成绘制后的状态,执行WindowStateAnimator#finishDrawingLocked() 方法设置,表示已经完成绘制,等待下次刷帧进行提交。
表示窗口已经绘制完成并且完成提交,此时如果该窗口的兄弟窗口全部完成绘制且满足显示要求,则直接进行 HAS_DRAWN 的转变完成显示,否则等待其他兄弟窗口完成绘制后,再进行 HAS_DRAWN 转变。
表示该窗口正式显示。
在注释2处又对窗口容器树进行了遍历,都指向 performShowLocked 方法。在注释1处调用了 WMS 的 scheduleAnimationLocked 方法,如果你还有印象,在前面分析窗口动画的时候说过,scheduleAnimationLocked 方法会将动画帧回调 FrameCallback 设置到 Choreographer 中去。然后在 VSYNC 信号到来的时候,指向 CallBack 动画回调、最后执行 animate。
private void animate(long frameTimeNs) { //.. for (int i = 0; i < numDisplays; i++) { final int displayId = mDisplayContentsAnimators.keyAt(i); final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId); ... dc.prepareWindowSurfaces();}void prepareWindowSurfaces() { forAllWindows(mPrepareWindowSurfaces, false / traverseTopToBottom */);}private final Consumer<WindowState> mPrepareWindowSurfaces = w -> w.mWinAnimator.prepareSurfaceLocked(true);void prepareSurfaceLocked(final boolean recoveringMemory) { boolean prepared = mSurfaceController.prepareToShowInTransaction(mShownAlpha,..);//1 ``` mSurfaceController.setLayer(mAnimLayer);//2 showSurfaceRobustlyLocked()//3 ```}private boolean showSurfaceRobustlyLocked() { final Task task = mWin.getTask(); if (task != null && StackId.windowsAreScaleable(task.mStack.mStackId)) { mSurfaceController.forceScaleableInTransaction(true); }boolean shown = mSurfaceController.showRobustlyInTransaction(); if (!shown) return false; if (mWin.mTurnOnScreen) { if (DEBUG_VISIBILITY) Slog.v(TAG, "Show surface turning screen on: " + mWin); mWin.mTurnOnScreen = false; mAnimator.mBulkUpdateParams |= SET_TURN_ON_SCREEN; } return true; ```}
最终在 showSurfaceRobustlyLocked 中调mSurfaceController.showRobustlyInTransaction() 方法进行 Surface 的提交给 SurfaceFlinger 进行合成并显示在屏幕上。
可以看到 Surface的size,postion 以及状态管理,提交执行等操作还是一个比较繁琐的过程。
时序图如下(原图可以去原文查看):
以上是Android开发中,以及在车载开发之中起到很重要的地位。有关更多Android开发核心技术以及车载开发技术学习;大家可以点击参考《Android核心技术手册》查看更多学习资料。
WMS在系统中的几大职责
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总