该系列文章总纲链接:专题分纲目录 Android Framework 窗口子系统


本章关键点总结 & 说明:

导图是不断迭代的,这里主要关注➕ 左上角 Android 窗口动画系统部分(因为导图是在太大,因此这里做了分层处理)。子导图展开后如下所示:

在这张图⬆️上,我们主要关注下方的动画系统框架即可。一方面从WMS入口开始解读动画系统,了解Animator各种类型和作用,另一方面从解读WindowStateAnimator开始解读了动画关键流程:动画选择和设置、Transformation计算、动画渲染。

1 动画系统框架

这里以Animator为研究入口研究WMS动画子系统框架,即WMS的scheduleAnimationLocked为入口,用于启动动画,代码如下

/** Note that Locked in this case is on mLayoutToAnim */
void scheduleAnimationLocked() {if (!mAnimationScheduled) {//避免重复发送mAnimationScheduled = true;mChoreographer.postCallback( //处理mAnimator.mAnimationRunnable,mAnimator是WMS所有动画的管理者Choreographer.CALLBACK_ANIMATION, mAnimator.mAnimationRunnable, null);}
}

这里专注分析Animator(属于WindowAnimator类)的mAnimationRunnable实现,代码如下:

WindowAnimator(final WindowManagerService service) {mService = service;mContext = service.mContext;mPolicy = service.mPolicy;mAnimationRunnable = new Runnable() {@Overridepublic void run() {synchronized (mService.mWindowMap) {mService.mAnimationScheduled = false;//渲染一帧动画animateLocked();}}};}

动画的渲染在animateLocked函数中。WindowAnimator的animateLocked实现如下:

// Locked on mService.mWindowMap.
private void animateLocked() {if (!mInitialized) {return;}//获取时间戳mCurrentTime = SystemClock.uptimeMillis();mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;boolean wasAnimating = mAnimating;mAnimating = false;SurfaceControl.openTransaction();SurfaceControl.setAnimationTransaction();try {//遍历,处理旋转动画和窗口的动画final int numDisplays = mDisplayContentsAnimators.size();for (int i = 0; i < numDisplays; i++) {final int displayId = mDisplayContentsAnimators.keyAt(i);//关键点1:处理AppWindowAnimator动画,更新Transform,影响surface位置updateAppWindowsLocked(displayId);DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);//关键点2:处理旋转动画final ScreenRotationAnimation screenRotationAnimation =displayAnimator.mScreenRotationAnimation;if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {if (screenRotationAnimation.stepAnimationLocked(mCurrentTime)) {mAnimating = true;} else {mBulkUpdateParams |= SET_UPDATE_ROTATION;screenRotationAnimation.kill();displayAnimator.mScreenRotationAnimation = null;if (mService.mAccessibilityController != null&& displayId == Display.DEFAULT_DISPLAY) {mService.mAccessibilityController.onRotationChangedLocked(mService.getDefaultDisplayContentLocked(), mService.mRotation);}}}//关键点3:处理WindowStateAnimator动画,更新Transform值updateWindowsLocked(displayId);updateWallpaperLocked(displayId);final WindowList windows = mService.getWindowListLocked(displayId);final int N = windows.size();for (int j = 0; j < N; j++) {//关键点4:渲染动画,将集合AppWindowAnimator、ScreenRotationAnimator、WindowStateAnimator的Transformation集合到一起//修改surface的layer、matrix、alpha、等属性,进而实现动画的渲染。windows.get(j).mWinAnimator.prepareSurfaceLocked(true);}}//关键点5:屏幕旋转效果for (int i = 0; i < numDisplays; i++) {final int displayId = mDisplayContentsAnimators.keyAt(i);testTokenMayBeDrawnLocked(displayId);final ScreenRotationAnimation screenRotationAnimation =mDisplayContentsAnimators.valueAt(i).mScreenRotationAnimation;if (screenRotationAnimation != null) {screenRotationAnimation.updateSurfacesInTransaction();}mAnimating |= mService.getDisplayContentLocked(displayId).animateDimLayers();if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) {mService.mAccessibilityController.drawMagnifiedRegionBorderIfNeededLocked();}}if (mAnimating) {mService.scheduleAnimationLocked();}mService.setFocusedStackLayer();if (mService.mWatermark != null) {mService.mWatermark.drawIfNeeded();}} catch (RuntimeException e) {} finally {SurfaceControl.closeTransaction();}boolean hasPendingLayoutChanges = false;final int numDisplays = mService.mDisplayContents.size();//关键点6:渲染Dimming 效果for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {final DisplayContent displayContent = mService.mDisplayContents.valueAt(displayNdx);final int pendingChanges = getPendingLayoutChanges(displayContent.getDisplayId());if ((pendingChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) {mBulkUpdateParams |= SET_WALLPAPER_ACTION_PENDING;}if (pendingChanges != 0) {hasPendingLayoutChanges = true;}}//关键点7:安排下一帧处理boolean doRequest = false;if (mBulkUpdateParams != 0) {//布局系统 "解压到" 动画系统doRequest = mService.copyAnimToLayoutParamsLocked();}//如果有必要,重新布局,WMS 安排下一帧处理if (hasPendingLayoutChanges || doRequest) {mService.requestTraversalLocked();}//如果有必要,有动画处于运行状态,WMS 安排下一帧处理if (!mAnimating && wasAnimating) {mService.requestTraversalLocked();}
}

对该函数流程做个总结:

  1. 通过updateAppWindowsLocked 计算AppWindowToken动画在当前时间所需要的变化。
  2. 对每一个DisplayContentAnimator,计算旋转动画在当前时间所需要的变化。
  3. 针对DisplayConten的每一个窗口,计算其动画在当前时间所需要的变化。
  4. 针对DisplayConten的每一个窗口,将上述3个动画同时应用到窗口的surface上,实现窗口帧的渲染。
  5. 渲染效果动画,包括屏幕旋转和dim效果。
  6. 动画系统“解压到”布局系统。
  7. WMS安排下一帧处理。

该函数并不只是运行一个动画,而是一批保存在各个Animator中的动画。

WindowAnimator的组成分析:窗口管理结构由以下三种成员组成:DisplayContent、WindowToken和WindowState,分别对应屏幕、显示组件和窗口本身。动画系统也是与之对应的组成结构,分别是:

  1. 以屏幕为动画目标的DisplayContentAnimator
  2. 以Activty为目标动画的AppWindowAnimator
  3. 以窗口为动画目标的WindowStateAnimator

而WindowAnimator是协调它们的管理者,另外WMS不仅管理窗口动画,还有特效动画,比如Dimming和屏幕旋转等,因此与之对应的还有DimAnimator和ScreenRotationAnimation。。。这里对上面的Animator作用进行说明:

  1. DisplayContentAnimator:存储位于其屏幕上的其他类型Animator。(比如ScreenRotateAnimation、DimAnimator。。)
  2. AppWindowAnimator:对AppWindowToken进行动画处理,动画用于表现Activity的进入和退出
  3. WindowStateAnimator:窗口动画处理,窗口动画就是surface动画
  4. ScreenRotateAnimation:处理转屏动画,窗口应用在其他所属屏幕所有窗口之上
  5. DimAnimator:实现Dimming效果,这个动画对象本身是一块黑色surface,需要Dimming效果时,DimAnimator会将这块surface以一定透明度衬于需要Dimming效果的窗口之下(why用Animator来做?提供舒服的淡入淡出效果)

同时,Animator类的从属关系如下所示:

接下来分析各个Animator的原理;影响窗口显示和位置的Animator主要有三类:WindowStateAnimator、AppWindowAnimator、ScreenRotationAnimation。后面主要以WindowStateAnimator为主。

2 WindowStateAnimator

窗口对象WindowState中定义了类型为WindowStateAnimator的成员变量mWinAnimator,用来表示窗口的动画对象。 WindowState保存了窗口管理方面的属性,WindowStateAnimator保存了窗口的surface属性,前者注重窗口管理,后者注重窗口显示,WindowStateAnimator的关键成员变量如下所示:

成员变量 含义
mSurface 保存了窗口Surface,同时也是WSA的动画目标
mWin 保存了WSA所对应的窗口
mAnimDw和mAnimDh 屏幕上排除状态栏/导航栏等系统窗口后供应用程序显示的区域尺寸
mAnimation 由WMS设置给Animation类的动画对象,保存了WMS要求窗口执行的动画
mAnimLayer 最终显示次序,基于mLayer的修正
mAlpha和mShownAlpha 均表示窗口透明度
mAnimating  表示窗口是否处于正在显示动画的过程中
mLocalAnimating 表示窗口的动画是否已经初始化过了。一个动画只有经过初始化后才会开始执行
mAnimationIsEntrance 记录动画类型,是退出还是进入动画。applyAnimationLocked()函数中会更新这个变量
mHasTransformation 表示Activity组件是否具有切换动画
mHasLocalTransformation 表示窗口本身是否具有切换动画,这个也不太好理解
mLocalAnimating 表示正在播放窗口本身动画
mTransformation 它是一个矩阵变换类Transformation对象,里面保存着一个apha通道值,一个3*3矩阵Matrix,这个变量只有在播窗口动画的时候才会更改。{android定义了一些可用的动画,比如渐变、缩小、放大、切换等,这些动画都实现了applyTransformation()函数,如果自定义动画,那么也要实现这个方法,这个方法的输入是当前的一个描述进度的浮点数(0.0 ~ 1.0), 输出是一个Transformation,保存在mTransformation中。}
mDsDx、mDtDx、mDsDy、mDtDy 这4个变量组合起来就是surface的变换矩阵,通过Surface.setMatrix方法可以改变Surface最终显示角度和缩放尺寸
mSurfaceControl SurfaceControl对象
mDrawState 表示该窗口的绘制状态,有5个状态:NO_SURFACE、DRAW_PENDING、COMMIT_DRAW_PENDING、READY_TO_SHOW、HAS_DRAWN.

@1 动画选择与设置

WMS的relayoutWindow函数中,窗口由不可见到可见时,执行了WSA中关键的语句WSA.applyAnimationLocked,该方法会为窗口开始一个淡入动画,将窗口显示出来,代码如下:

boolean applyAnimationLocked(int transit, boolean isEntrance) {if ((mLocalAnimating && mAnimationIsEntrance == isEntrance)|| mKeyguardGoingAwayAnimation) {if (mAnimation != null && mKeyguardGoingAwayAnimation&& transit == WindowManagerPolicy.TRANSIT_PREVIEW_DONE) {applyFadeoutDuringKeyguardExitAnimation();}return true;}if (mService.okToDisplay()) {//mPolicy.selectAnimationLw是为状态栏、导航栏分配特殊的动画//通过 selectAnimationLw 获取合适的资源int anim = mPolicy.selectAnimationLw(mWin, transit);int attr = -1;Animation a = null;if (anim != 0) {//如果WMP为当前窗口指定了动画资源,则选择此动画a = anim != -1 ? AnimationUtils.loadAnimation(mContext, anim) : null;} else {switch (transit) {case WindowManagerPolicy.TRANSIT_ENTER:attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;//...case WindowManagerPolicy.TRANSIT_HIDE:attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;break;}if (attr >= 0) {a = mService.mAppTransition.loadAnimationAttr(mWin.mAttrs, attr);}}if (a != null) {//关键点:保存选取的animation对象setAnimation(a);mAnimationIsEntrance = isEntrance;}} else {clearAnimation();}return mAnimation != null;
}

继续分析setAnimation,代码如下:

public void setAnimation(Animation anim) {setAnimation(anim, -1);
}public void setAnimation(Animation anim, long startTime) {if (localLOGV) Slog.v(TAG, "Setting animation in " + this + ": " + anim);mAnimating = false;mLocalAnimating = false;//保存选择的anim到mAnimationmAnimation = anim;mAnimation.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);mAnimation.scaleCurrentDuration(mService.getWindowAnimationScaleLocked());// Start out animation gone if window is gone, or visible if window is visible.//初始化mTransformationmTransformation.clear();mTransformation.setAlpha(mLastHidden ? 0 : 1);mHasLocalTransformation = true;mAnimationStartTime = startTime;
}

WMS通过transit向WSA提出动画意图,WMP和WSA据此意图选择一个动画保存在mAnimation中。在WMS的relayoutWindow函数后,调用performLayoutAndPlaceSurfacesLocked函数,所有需要绘制的窗口根据各自动画的谁知重新调整窗口Surface的变化矩阵和透明度;如果还有窗口动画需要显示,继续调用scheduleAnimationLocked方法准备下一帧。

@2 Transformation计算(animateLocked延续分析)

当前正在显示的动画有两种类型,一种的窗口切换动画,一种是Activity切换动画,这里使用了mLocalAnimating和mHasLocalTransformation分别表示窗口的动画状态。WindowAnimator在执行animateLocked时,调用updateWindowLocked()方法,代码实现如下:

private void updateWindowsLocked(final int displayId) {//...for (int i = windows.size() - 1; i >= 0; i--) {WindowState win = windows.get(i);WindowStateAnimator winAnimator = win.mWinAnimator;final int flags = win.mAttrs.flags;boolean canBeForceHidden = mPolicy.canBeForceHidden(win, win.mAttrs);boolean shouldBeForceHidden = shouldForceHide(win);if (winAnimator.mSurfaceControl != null) {//...计算WSA的变换final boolean nowAnimating = winAnimator.stepAnimationLocked(mCurrentTime);//...}// If the window doesn't have a surface, the only thing we care about is the correct// policy visibility.else if (canBeForceHidden) {if (shouldBeForceHidden) {win.hideLw(false, false);} else {win.showLw(false, false);}}//处理WSA绘制状态,当状态为READY_TO_SHOW时,执行performShowLocked,将状态切换为HAS_DRAWNfinal AppWindowToken atoken = win.mAppToken;if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) {if (atoken == null || atoken.allDrawn) {if (winAnimator.performShowLocked()) {setPendingLayoutChanges(displayId,WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);}}}/*更新的AppWindowAnimator的thumbnailLayer遍历所有窗口过程中,找到最靠前的窗口显示次序作为窗口所属的AppWindowToken缩略图的显示次序*/final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;if (appAnimator != null && appAnimator.thumbnail != null) {//...}//...} // end forall windows//...
}

updateWindowLocked中调用了stepAnimationLocked来更新Transform的值,该值在后面调用prepareSurfaceLocked时会影响surface的最终位置。这里的stepAnimationLocked是WindowStateAnimator类中显示动画首先调用的方法,它会初始化WindowStateAnimator对象的一些成员变量,代码实现如下:

boolean stepAnimationLocked(long currentTime) {mWasAnimating = mAnimating;final DisplayContent displayContent = mWin.getDisplayContent();if (displayContent != null && mService.okToDisplay()) {// We will run animations as long as the display isn't frozen.if (mWin.isDrawnLw() && mAnimation != null) {//窗口准备好绘制了,窗口动画对象不为空mHasTransformation = true;mHasLocalTransformation = true;if (!mLocalAnimating) {//还没有初始化窗口对象mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(),mAnimDw, mAnimDh);//初始化窗口对象final DisplayInfo displayInfo = displayContent.getDisplayInfo();mAnimDw = displayInfo.appWidth;mAnimDh = displayInfo.appHeight;mAnimation.setStartTime(mAnimationStartTime != -1? mAnimationStartTime: currentTime);// 设置为true代表已经初始化窗口对象mLocalAnimating = true;mAnimating = true;}//没有设置窗口动画或者窗口动画结束了if ((mAnimation != null) && mLocalAnimating) {mLastAnimationTime = currentTime;if (stepAnimation(currentTime)) {return true;}}}mHasLocalTransformation = false;if ((!mLocalAnimating || mAnimationIsEntrance) && mAppAnimator != null&& mAppAnimator.animation != null) {// 如果有Activity动画,将mAnimating设为true mAnimating = true;mAnimating = true;mHasTransformation = true;mTransformation.clear();return false;} else if (mHasTransformation) {mAnimating = true;} else if (isAnimating()) {mAnimating = true;}} else if (mAnimation != null) {mAnimating = true;}if (!mAnimating && !mLocalAnimating) {return false;}mAnimating = false;mKeyguardGoingAwayAnimation = false;mLocalAnimating = false;//...mAnimLayer = mWin.mLayer;//...mTransformation.clear();//...return false;
}

这里继续分析stepAnimation,代码如下:

private boolean stepAnimation(long currentTime) {if ((mAnimation == null) || !mLocalAnimating) {return false;}mTransformation.clear();final boolean more = mAnimation.getTransformation(currentTime, mTransformation);return more;}

通过mAnimation.getTransformation来更新Transformation的值。

@3 动画渲染

在WindowAnimator的animateLocked中,更新Transform后,调用WindowList中每个WindowStateAnimator的prepareSurfaceLocked 方法来完成计算一帧动画并显示工作,代码实现如下:

public void prepareSurfaceLocked(final boolean recoveringMemory) {final WindowState w = mWin;//...计算要显示的动画帧参数computeShownFrameLocked();//设置变换到surfacesetSurfaceBoundariesLocked(recoveringMemory);//如果是壁纸窗口,隐藏if (mIsWallpaper && !mWin.mWallpaperVisible) {hide();//如果窗口不可见,隐藏} else if (w.mAttachedHidden || !w.isOnScreen()) {hide();mAnimator.hideWallpapersLocked(w);if (w.mOrientationChanging) {w.mOrientationChanging = false;}//每个值是否有变化} else if (mLastLayer != mAnimLayer || mLastAlpha != mShownAlpha|| mLastDsDx != mDsDx || mLastDtDx != mDtDx|| mLastDsDy != mDsDy || mLastDtDy != mDtDy|| w.mLastHScale != w.mHScale || w.mLastVScale != w.mVScale|| mLastHidden) {displayed = true;mLastAlpha = mShownAlpha;mLastLayer = mAnimLayer;mLastDsDx = mDsDx;mLastDtDx = mDtDx;mLastDsDy = mDsDy;mLastDtDy = mDtDy;w.mLastHScale = w.mHScale;w.mLastVScale = w.mVScale;if (mSurfaceControl != null) {try {//设置透明度mSurfaceAlpha = mShownAlpha;mSurfaceControl.setAlpha(mShownAlpha);//设置窗口显示次序mSurfaceLayer = mAnimLayer;mSurfaceControl.setLayer(mAnimLayer);//设置窗口矩阵mSurfaceControl.setMatrix(mDsDx * w.mHScale, mDtDx * w.mVScale,mDsDy * w.mHScale, mDtDy * w.mVScale);if (mLastHidden && mDrawState == HAS_DRAWN) {//输出动画帧 if (showSurfaceRobustlyLocked()) {mLastHidden = false;if (mIsWallpaper) {mService.dispatchWallpaperVisibility(w, true);}mAnimator.setPendingLayoutChanges(w.getDisplayId(),WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);} else {w.mOrientationChanging = false;}}if (mSurfaceControl != null) {w.mToken.hasVisible = true;}} catch (RuntimeException e) {if (!recoveringMemory) {mService.reclaimSomeSurfaceMemoryLocked(this, "update", true);}}}} else {displayed = true;}if (displayed) {if (w.mOrientationChanging) {if (!w.isDrawnLw()) {mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;mAnimator.mLastWindowFreezeSource = w;} else {w.mOrientationChanging = false;}}w.mToken.hasVisible = true;}
}

关键数据说明:

  1. mAnimLayer表示窗口的Z轴
  2. mShownAlpha窗口透明度;
  3. mDsDx、mDtDx、mDsDy和mDtDy表示二维变换矩阵;
  4. w.mHScale、w.mVScale表示窗口的缩放比例

该函数先执行computeShownFrameLocked函数,计算当前需要显示的动画帧数据,之后分析计算出的数据,设置surface,这一帧中窗口的位置发生了变化,同时,这里只有计算出的数据和上一次数据不一样才会调用showSurfaceRobustlyLocked输出动画帧。

系统框架整体总结:

  1. WindowAnimator是一个强大的驱动器,多种类型Animator在此基础上有条不紊完成各自渲染工作。
  2. 在WindowAnimator之下有各种类型的Animator,掌管不同类型对象的动画。它们是WindowStateAnimator、AppWindowAnimator、ScreenRotationAnimation、DimAnimator以及DisplayContentsAnimator,除DimAnimator和DisplayContentsAnimator外,其他类型Animator都有一个stepAnimationLocked()函数用以计算当前时间下动画对象所需的变换。
  3. WindowStateAnimator不仅要进行动画变换的计算,还要管理窗口的Surface,且所有其他类型的Animator变换都要汇集到WindowStateAnimator中完成窗口最终的变换计算。

Android Framework 窗口子系统 (08)窗口动画之动画系统框架相关推荐

  1. Android Framework 电源子系统(04)核心方法updatePowerStateLocked分析-2 循环处理  更新显示设备状态

    该系列文章总纲链接:专题分纲目录 Android Framework 电源子系统 本章关键点总结 & 说明: 本章节主要关注➕ updatePowerStateLocked 方法中 循环处理 ...

  2. Android Framework 电源子系统(05)核心方法updatePowerStateLocked分析-3 更新屏保  发送通知  更新wakelock

    该系列文章总纲链接:专题分纲目录 Android Framework 电源子系统 本章关键点总结 & 说明: 本章节主要关注➕ updatePowerStateLocked 方法中 更新屏保 ...

  3. Android Framework 电源子系统(01)PowerManagerService启动分析

    该系列文章总纲链接:专题分纲目录 Android Framework 电源子系统 本章关键点总结 & 说明: 本章节主要关注➕ 以上思维导图即可.该章节 主要是 对 PMS 启动的分析,从sy ...

  4. 专题总纲目录 Android Framework 总纲

    专题总纲说明: 本系列文章虽说是 Android 的知识体系专题,同时也是学习Android Framework 系统的一个思路,尤其是当我们对Android 框架层 一点都不了解的时候,但前提是要有 ...

  5. Android源码分析(三)-----系统框架设计思想

    一 : 术在内而道在外 Android系统的精髓在源码之外,而不在源码之内,代码只是一种实现人类思想的工具,仅此而已...... 近来发现很多关于Android文章都是以源码的方向入手分析Androi ...

  6. Android添加系统级顶层窗口 和 WindowManager添加view的动画问题

    当Dialog有编辑框时如果选择会弹菜单窗口就不要用 Context applicationContext = mainActivity.getApplicationContext(); AlertD ...

  7. android窗口退出动画,如何在Android中为弹出窗口制作动画

    PopupWindow自定义布局更方便,并且显示位置自由,没有任何限制.使用下面的代码并享受动画.在此动画中,使用底部滑入和滑出,但是您只能更改滑入/滑出动画,并根据您的动画对应用程序中的任何位置进行 ...

  8. Android WindowManagerService机制分析:窗口的显示层级

    WindowManagerService(以下简称WMS)是Android Framework中一个重要的系统服务,用来管理系统中窗口(Window)的行为.Window是一个抽象的概念,它是一个矩形 ...

  9. Android实现一键开启自由窗口、分屏、画中画模式——自由窗口模式

    转载请注明出处:https://blog.csdn.net/sunmmer123 忙过一段时间后,新需求又来了"多个应用/页面间在不用退出或者切换的情况下,可同时操作" 咋一听是不 ...

最新文章

  1. 应用市场自然量预估_VIVO市场ASO实战详解
  2. python手机版idle-如何在Ubuntu上安装Python IDE IDLE
  3. LibreOJ - 3083 与或和(单调栈+位运算)
  4. Md5 Md5实现原理
  5. Linux API函数总结
  6. Vue.use()与Vue.prototype
  7. git命令之git rebase 的用法
  8. 让工作效率起飞的11个神奇网站~~
  9. HTML 制作一个通讯录
  10. Android应用开发性能优化完全分析,完美收官
  11. win10 安装 HP LaserJet P1108 教程
  12. Centos8.3安装
  13. 三个免费的无版权图片站
  14. 记录java使用EasyExcel进行单元格内换行操作
  15. 微信授权文件放到域名根目录下
  16. python forward函数_PyTorch之前向传播函数自动调用forward
  17. appdesigner与simulink交互
  18. 国产芯片传来好消息,纯国产CPU测试数据“曝光”
  19. Js获取字符串asc码
  20. Android 4.2 Wifi Display核心分析 (一)

热门文章

  1. zookeeper集群在线迁移(扩容)详解
  2. 南充市浏览器市场份额
  3. Google Mail
  4. 防止计算机病毒和木马的方法,防止电脑中病毒、木马的方法(windows清理助手)...
  5. js实现鼠标点击自动选中点击元素内的文字
  6. android 状态栏挡住app,【已解决】react-navigation导航栏被状态栏遮盖挡住了一部分...
  7. IE11离线安装包ie离线升级ie11离线安装ie11补丁
  8. CANoe的数据回放(Replay Block),还是要结合CAPL脚本才能说的明白
  9. Ubuntu 14.04连接上海大学ShuWlan-1X与eduroam
  10. 上海大学计算机考研难,求知道上海大学计算机专业考研有哪些科目???