package com.glview.hwui; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLSurface; import android.opengl.GLSurfaceView; import android.util.Log; import android.view.SurfaceHolder; import com.glview.App; import com.glview.animation.Animator; import com.glview.animation.AnimatorListenerAdapter; import com.glview.animation.ValueAnimator; import com.glview.animation.ValueAnimator.AnimatorUpdateListener; import com.glview.graphics.Bitmap; import com.glview.hwui.font.FontRenderer; import com.glview.hwui.task.Task; import com.glview.hwui.task.TaskHandler; import com.glview.thread.Looper; import com.glview.util.FPSUtils; import com.glview.view.GLRootView; /** * This is the context of our RenderThread. * It holds EGLContext and Canvas instance, and the state that ensure * our drawing is enable {@link #isEnable()}. * * @author lijing.lj */ class CanvasContext { private final static String TAG = "CanvasContext"; private final static boolean DEBUG = true; private final static boolean DEBUG_FRAME = false; private final static boolean DEBUG_FPS = true; private final static boolean DEBUG_ANIMATING = false; /** * EGL Management. * Manage the EGL Context. */ static EglManager sEglManager; EGLSurface mEglSurface; /** * Viewport width and height. */ int mWidth = -1, mHeight = -1; GLCanvas mCanvas; /** * The root node, we begin our frame with this node. * It contains the real CanvasOp list. * @see RenderNode#replay(GLCanvas) */ RenderNode mRootNode; FPSUtils mFpsUtils = null; RenderState mRenderState; SurfaceHolder mSurfaceHolder; boolean mExited = false; TaskHandler mHandler; /** * This task is used to schedule the RT-driven animation's draw frame. * It's running in render thread. * @see #scheduleAnimatingDrawTask() * @see #unscheduleAnimatingDrawTask() */ AnimatingDrawTask mAnimatingDrawTask = new AnimatingDrawTask(); boolean mAnimatingDrawRequested = false; boolean mAnimating = false; AnimationStartTask mAnimationStartTask = new AnimationStartTask(); AnimationStopTask mAnimationStopTask = new AnimationStopTask(); /** * The RT-driven animations. These animations run in the render thread. * We will schedule a frame draw in {@link #mUpdateListener}. */ List<Animator> mAnimators = new ArrayList<Animator>(); AnimatorListenerAdapter mListener = new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { if (DEBUG_ANIMATING) Log.d(TAG, "onAnimationStart, scheduleAnimatingDrawTask. animation=" + animation); animationStarted(animation); } @Override public void onAnimationCancel(Animator animation) { if (DEBUG_ANIMATING) Log.d(TAG, "onAnimationCancel. animation=" + animation); animationStopped(animation); } @Override public void onAnimationEnd(Animator animation) { if (DEBUG_ANIMATING) Log.d(TAG, "onAnimationEnd. animation=" + animation); animationStopped(animation); } }; /** * If we are in animating (RenderThread-driven animation), we should call * the method {@link #scheduleAnimatingDrawTask()} to schedule a draw operation * in {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}. * This flag is just running in {@link RenderThread}. */ AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if (DEBUG_ANIMATING) Log.d(TAG, "onAnimationUpdate. animation=" + animation); scheduleAnimatingDrawTask(); } }; public CanvasContext(RenderNode rootRenderNode) { mHandler = new TaskHandler(Looper.myLooper()); ensureEglManager(); mRootNode = rootRenderNode; if (DEBUG_FPS) mFpsUtils = new FPSUtils(this); } public static void ensureEglManager() { // now we only support OpenGL 2.0. if (sEglManager == null) sEglManager = new EglManager(); sEglManager.initializeEgl(); } /** * Initialize the EGL surface. * Called by {@link GLRootView#surfaceCreated(android.view.SurfaceHolder)} * @param surface */ public void initialize(Object surface) { if (surface != mSurfaceHolder) { if (surface instanceof SurfaceHolder) { mSurfaceHolder = (SurfaceHolder) surface; } else { mSurfaceHolder = null; } } if (createSurface(surface)) { if (mCanvas == null) { mCanvas = createGLCanvas(); } } } private GLCanvas createGLCanvas() { if (!sEglManager.hasEglContext()) { Log.w(TAG, "Why u reach here!"); throw new IllegalStateException("No egl context exists."); } if (mRenderState == null) { mRenderState = new RenderState(App.getGL20()); } return new GL20Canvas(mRenderState); } /** * Set the canvas's size. * Called by {@link GLSurfaceView#surfaceChanged(android.view.SurfaceHolder, int, int, int)}. * @param width * @param height */ void setSize(Object surface, int width, int height) { if (DEBUG) Log.d(TAG, "setSize called in CanvasContext, width=" + width + ", height=" + height); if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { initialize(surface); } if (ensureCurrentSurface()) { mWidth = width; mHeight = height; mCanvas.setSize(mWidth, mHeight); } } /** * If is able to draw. * @return */ boolean isEnable() { return mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE && mWidth > 0 && mHeight > 0 && sEglManager.hasEglContext(); } private boolean ensureCurrentSurface() { return sEglManager.makeCurrent(mEglSurface); } /** * Draw one frame, called by RenderPolicy {@link RenderPolicy#innerDraw()} * @return */ public boolean draw() { if (isEnable() && ensureCurrentSurface()) { if (DEBUG_FRAME) Log.d(TAG, "One frame begin!"); mCanvas.beginFrame(); mCanvas.drawRenderNode(mRootNode); mCanvas.endFrame(); swapBuffers(); if (DEBUG_FPS) mFpsUtils.fps(); if (DEBUG_FRAME) Log.d(TAG, "One frame end!"); return true; } return false; } private boolean createSurface(Object surface) { if (DEBUG) Log.d(TAG, "createSurface called in CanvasContext, surface=" + surface); destroySurface(); mEglSurface = sEglManager.createSurface(surface); return mEglSurface != null; } void destroy(boolean full) { if (DEBUG) Log.d(TAG, "destroy called in CanvasContext, full=" + full); destroySurface(); mSurfaceHolder = null; if (full) { mRootNode.destroy(); mCanvas = null; mHandler.removeCallbacksAndMessages(null); clearAnimations(); mExited = true; } } void destroyHardwareResources() { Caches.getInstance().clear(); destroyContext(); ensureEglManager(); } void swapBuffers() { if (DEBUG_FRAME) Log.d(TAG, "swapBuffers called in CanvasContext."); if (!sEglManager.swapBuffers(mEglSurface)) { if (DEBUG_FRAME) Log.d(TAG, "swapBuffers failed, maybe the surface is no longer valid."); destroySurface(); } } void destroySurface() { if (mEglSurface != null) { if (DEBUG) Log.d(TAG, "destroy EGLSurface called in CanvasContext."); sEglManager.destroySurface(mEglSurface); mEglSurface = null; } } void destroyContext() { if (DEBUG) Log.d(TAG, "destroy EGLContext called in CanvasContext."); sEglManager.destroy(); } public void startAnimation(List<Animator> animators) { mAnimationStartTask.mAnimators = animators; mHandler.postAtFrontOfQueueAndWait(mAnimationStartTask); } public void stopAnimation(List<Animator> animators) { mAnimationStopTask.mAnimators = animators; mHandler.postAtFrontOfQueueAndWait(mAnimationStopTask); } public Bitmap buildDrawingCache(RenderNode renderNode) { if (mCanvas != null) { return renderNode.buildDrawingCache(mCanvas); } return null; } private void scheduleAnimatingDrawTask() { if (mExited) return; if (!mAnimatingDrawRequested) { mAnimatingDrawRequested = true; mHandler.post(mAnimatingDrawTask); } } private void unscheduleAnimatingDrawTask() { mAnimatingDrawRequested = false; mHandler.remove(mAnimatingDrawTask); } private void animationStarted(Animator animation) { scheduleAnimatingDrawTask(); } private void animationStopped(Animator animation) { mAnimators.remove(animation); if (mAnimators.size() == 0) { Log.i(TAG, "running animations is empty."); } } private void clearAnimations() { for (Iterator<Animator> iter = mAnimators.iterator(); iter.hasNext(); ) { iter.next().cancel(); } } /* * Run in render thread. */ class AnimatingDrawTask extends Task { @Override public void doTask() { mAnimatingDrawRequested = false; draw(); } } class AnimationStartTask extends Task { List<Animator> mAnimators; @Override public void doTask() { if (mAnimators != null) { for (Animator animator : mAnimators) { animator.addListener(mListener); ((ValueAnimator) animator).addUpdateListener(mUpdateListener); animator.start(); } } mAnimators = null; } } class AnimationStopTask extends Task { List<Animator> mAnimators; @Override public void doTask() { if (mAnimators != null) { for (Animator animator : mAnimators) { animator.cancel(); } } mAnimators = null; } } /** * Level for {@link #onTrimMemory(int)}: the process is nearing the end * of the background LRU list, and if more memory isn't found soon it will * be killed. */ static final int TRIM_MEMORY_COMPLETE = 80; /** * Level for {@link #onTrimMemory(int)}: the process is around the middle * of the background LRU list; freeing memory can help the system keep * other processes running later in the list for better overall performance. */ static final int TRIM_MEMORY_MODERATE = 60; /** * Level for {@link #onTrimMemory(int)}: the process has gone on to the * LRU list. This is a good opportunity to clean up resources that can * efficiently and quickly be re-built if the user returns to the app. */ static final int TRIM_MEMORY_BACKGROUND = 40; /** * Level for {@link #onTrimMemory(int)}: the process had been showing * a user interface, and is no longer doing so. Large allocations with * the UI should be released at this point to allow memory to be better * managed. */ static final int TRIM_MEMORY_UI_HIDDEN = 20; /** * Level for {@link #onTrimMemory(int)}: the process is not an expendable * background process, but the device is running extremely low on memory * and is about to not be able to keep any background processes running. * Your running process should free up as many non-critical resources as it * can to allow that memory to be used elsewhere. The next thing that * will happen after this is {@link #onLowMemory()} called to report that * nothing at all can be kept in the background, a situation that can start * to notably impact the user. */ static final int TRIM_MEMORY_RUNNING_CRITICAL = 15; /** * Level for {@link #onTrimMemory(int)}: the process is not an expendable * background process, but the device is running low on memory. * Your running process should free up unneeded resources to allow that * memory to be used elsewhere. */ static final int TRIM_MEMORY_RUNNING_LOW = 10; /** * Level for {@link #onTrimMemory(int)}: the process is not an expendable * background process, but the device is running moderately low on memory. * Your running process may want to release some unneeded resources for * use elsewhere. */ static final int TRIM_MEMORY_RUNNING_MODERATE = 5; static TaskHandler sTaskHandler = new TaskHandler(RenderThread.getRenderThreadLooper()); static void trimMemory(final int level) { sTaskHandler.postAndWait(new Task() { @Override public void doTask() { ensureEglManager(); Log.v(TAG, "trimMemory level=" + level); if (level >= TRIM_MEMORY_COMPLETE) { FontRenderer.instance().release(); Caches.getInstance().clear(); } if (level >= TRIM_MEMORY_MODERATE) { FontRenderer.instance().release(); Caches.getInstance().textureCache.clear(); } else if (level >= TRIM_MEMORY_UI_HIDDEN) { FontRenderer.instance().release(); Caches.getInstance().textureCache.flush(); } else { if (level == TRIM_MEMORY_RUNNING_MODERATE) { Caches.getInstance().textureCache.flush(); } else { Caches.getInstance().clear(); } } } }); } }