Android横竖屏切换卡顿问题

今天遇到一个android系统在切换横竖屏时一直卡着不动,大概3秒以后才能转过来的问题。最后定位到是由于ScreenRotationAnimation类的构造函数调用了

SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
                        SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur);

进行截屏导致的。
截屏函数最终是由SurfaceFlinger完成的。在surfaceflinger中加入日志调试。

nsecs_t st = systemTime();
result = flinger->captureScreenImplLocked(hw,
        producer, reqWidth, reqHeight, minLayerZ, maxLayerZ);
nsecs_t et = systemTime();
ALOGI("surfaceflinger captureScreenImplLocked use time:%dms", (int)ns2ms(et - st));

得到以下输出:

03-11 14:12:44.550 2259-2797/? W/ScreenRotationAnimation: SurfaceControl.screenshot use 3142ms
03-11 14:13:01.950 2259-2669/? W/ScreenRotationAnimation: SurfaceControl.screenshot use 3282ms
03-11 14:23:26.250 2259-2669/? W/ScreenRotationAnimation: SurfaceControl.screenshot use 3176ms
03-11 14:23:36.790 2259-2668/? W/ScreenRotationAnimation: SurfaceControl.screenshot use 3239ms

为什么需要这么长时间?
最后研究发现这个时间跟SurfaceView的数量还有关系,每多一个SurfaceView截屏时间就多个500到600毫秒,我的界面总共添加了3个SurfaceView,所以截屏时间花了3秒。
如果一个SurfaceView都不添加,截屏只需要花费大概600毫秒的时间。
看了一眼captureScreenImplLocked这个函数的实现,发现这个函数很神奇,只调用了几个GL函数,截屏就完成了。

status_t SurfaceFlinger::captureScreenImplLocked(
        const sp<const DisplayDevice>& hw,
        const sp<IGraphicBufferProducer>& producer,
        uint32_t reqWidth, uint32_t reqHeight,
        uint32_t minLayerZ, uint32_t maxLayerZ)
{
    ATRACE_CALL();

    // get screen geometry
    const uint32_t hw_w = hw->getWidth();
    const uint32_t hw_h = hw->getHeight();

    // if we have secure windows on this display, never allow the screen capture
    if (hw->getSecureLayerVisible()) {
        ALOGW("FB is protected: PERMISSION_DENIED");
        return PERMISSION_DENIED;
    }

    if ((reqWidth > hw_w) || (reqHeight > hw_h)) {
        ALOGE("size mismatch (%d, %d) > (%d, %d)",
                reqWidth, reqHeight, hw_w, hw_h);
        return BAD_VALUE;
    }

    reqWidth = (!reqWidth) ? hw_w : reqWidth;
    reqHeight = (!reqHeight) ? hw_h : reqHeight;

    // Create a surface to render into
    sp<Surface> surface = new Surface(producer);
    ANativeWindow* const window = surface.get();

    // set the buffer size to what the user requested
    native_window_set_buffers_user_dimensions(window, reqWidth, reqHeight);

    // and create the corresponding EGLSurface
    EGLSurface eglSurface = eglCreateWindowSurface(
            mEGLDisplay, mEGLConfig, window, NULL);
    if (eglSurface == EGL_NO_SURFACE) {
        ALOGE("captureScreenImplLocked: eglCreateWindowSurface() failed 0x%4x",
                eglGetError());
        return BAD_VALUE;
    }

    if (!eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) {
        ALOGE("captureScreenImplLocked: eglMakeCurrent() failed 0x%4x",
                eglGetError());
        eglDestroySurface(mEGLDisplay, eglSurface);
        return BAD_VALUE;
    }

    renderScreenImplLocked(hw, reqWidth, reqHeight, minLayerZ, maxLayerZ, false);

    // and finishing things up...
    if (eglSwapBuffers(mEGLDisplay, eglSurface) != EGL_TRUE) {
        ALOGE("captureScreenImplLocked: eglSwapBuffers() failed 0x%4x",
                eglGetError());
        eglDestroySurface(mEGLDisplay, eglSurface);
        getDefaultDisplayDevice()->makeCurrent(mEGLDisplay, mEGLContext);
        return BAD_VALUE;
    }

    eglDestroySurface(mEGLDisplay, eglSurface);
    getDefaultDisplayDevice()->makeCurrent(mEGLDisplay, mEGLContext);
    return NO_ERROR;
}

不明觉厉,这个函数我是改不了了,不过还好我们这个设备是墨水屏,刷新特别慢,在旋转时不需要动画,可以想办法去掉动画,这样就不用调用截屏函数了。
首先打印一下调用堆栈,看看谁调用的截屏函数。

 	try {
		throw new Exception();
     } catch(Exception e) {
		e.printStackTrace();
     }
	long st = System.currentTimeMillis();    SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
             SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur);				
	long et = System.currentTimeMillis();
	Slog.w(TAG, "SurfaceControl.screenshot use " + (et - st) + "ms");

得到以下输出:

03-11 14:27:39.840 2259-2439/? W/System.err: java.lang.Exception
03-11 14:27:39.840 2259-2439/? W/System.err:     at com.android.server.wm.ScreenRotationAnimation.<init>(ScreenRotationAnimation.java:260)
03-11 14:27:39.840 2259-2439/? W/System.err:     at com.android.server.wm.WindowManagerService.startFreezingDisplayLocked(WindowManagerService.java:9945)
03-11 14:27:39.840 2259-2439/? W/System.err:     at com.android.server.wm.WindowManagerService.updateRotationUncheckedLocked(WindowManagerService.java:5960)
03-11 14:27:39.840 2259-2439/? W/System.err:     at com.android.server.wm.WindowManagerService.updateOrientationFromAppTokensLocked(WindowManagerService.java:3708)
03-11 14:27:39.840 2259-2439/? W/System.err:     at com.android.server.wm.WindowManagerService.updateOrientationFromAppTokensLocked(WindowManagerService.java:3645)
03-11 14:27:39.840 2259-2439/? W/System.err:     at com.android.server.wm.WindowManagerService.updateOrientationFromAppTokens(WindowManagerService.java:3633)
03-11 14:27:39.840 2259-2439/? W/System.err:     at com.android.server.am.ActivityManagerService.setRequestedOrientation(ActivityManagerService.java:3596)
03-11 14:27:39.840 2259-2439/? W/System.err:     at android.app.ActivityManagerNative.onTransact(ActivityManagerNative.java:955)
03-11 14:27:39.840 2259-2439/? W/System.err:     at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2084)
03-11 14:27:39.840 2259-2439/? W/System.err:     at android.os.Binder.execTransact(Binder.java:404)
03-11 14:27:39.850 2259-2439/? W/System.err:     at dalvik.system.NativeStart.run(Native Method)

根据日志查看WindowManagerService.java 9945行附近,有如下代码:

if (CUSTOM_SCREEN_ROTATION) {
	mExitAnimId = exitAnim;
	mEnterAnimId = enterAnim;
	final DisplayContent displayContent = getDefaultDisplayContentLocked();
	final int displayId = displayContent.getDisplayId();
	ScreenRotationAnimation screenRotationAnimation =
			mAnimator.getScreenRotationAnimationLocked(displayId);
	if (screenRotationAnimation != null) {
		screenRotationAnimation.kill();
	}

	// TODO(multidisplay): rotation on main screen only.
	screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
			mFxSession, inTransaction, mPolicy.isDefaultOrientationForced());
	mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation);
}

重点是if (CUSTOM_SCREEN_ROTATION) {判断条件,再搜索一下CUSTOM_SCREEN_ROTATION变量,发现只有一个初始值,中间没有任何赋值的地方。

    /**
     * If true, the window manager will do its own custom freezing and general
     * management of the screen during rotation.
     */
    static final boolean CUSTOM_SCREEN_ROTATION = true;

既然是这样,把CUSTOM_SCREEN_ROTATION 修改为false试试效果看。
哇!果然很快,就是效果有点儿差,能看得见黑屏。
又研究了一会儿,没什么进展,看来这种方式只能这样了。
截屏函数到底能不能快点儿呢?
又去看了一眼,发现原来captureScreenImplLocked函数在surfaceflinger中还有另一种实现。

#ifdef NO_FBO_SCREENSHOT
/* mx6sl_evk don't have GPU 3D core, it will use software OpenGL ES1.1 (libagl)
 * which can't support FBO feature, so mx6sl_evk screenshot can only use below
 * implementation although it may not fast as FBO path.
 */
#include 
#include 
status_t SurfaceFlinger::captureScreenImplLocked(
        const sp<const DisplayDevice>& hw,
        const sp<IGraphicBufferProducer>& producer,
        uint32_t reqWidth, uint32_t reqHeight,
        uint32_t minLayerZ, uint32_t maxLayerZ)
{
...
#else
status_t SurfaceFlinger::captureScreenImplLocked(
        const sp<const DisplayDevice>& hw,
        const sp<IGraphicBufferProducer>& producer,
        uint32_t reqWidth, uint32_t reqHeight,
        uint32_t minLayerZ, uint32_t maxLayerZ)
{
...
#endif

代码的注释明确的说明了我这个设备没有GPU 3D core,所以截屏函数是通过软件来完成的。所以就慢了。

你可能感兴趣的:(Android横竖屏切换卡顿问题)