A SurfaceView,
Take a mobile phone with a refresh rate of 60Hz as an example, the screen refresh interval is 16ms. If the View does not complete the drawing operation within 16ms, it will cause frame drop. Therefore, some frequently drawn and complex controls will be difficult to avoid the lag if they simply inherit View to draw in the main thread
SurfaceView based on double buffering technology is a good solution to the common View in the main thread drawing frequent and heavy caused by the problem of lag, SurfaceView has its own canvas and support to update the canvas content in the child thread
1.1 Double buffering technology
Double-buffering involves two graphics buffers, one for the front end, which corresponds to what is being displayed on the screen, and the other for the graphics to render next. Through the surfaceHolder. UnloackCanvas () to obtain the back-end buffer, to draw calls after he surfaceHolder. UnlockCanvasAndPost (mCanvas) will the back-end buffer and front-end buffer swapping, back-end buffer into a front-end buffer, And to display content on the screen, while the original front-end buffer into a back-end buffer, waiting for the next surfaceHolder. LoackCanvas () is returned to the user and so on
1.2 Basic use of SurfaceView
class MySurfaceView @JvmOverloads constructor(
context: Context.attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : SurfaceView(context, attrs, defStyleAttr), SurfaceHolder.Callback {
private var mSurfaceHolder: SurfaceHolder = holder
private lateinit var mCanvas: Canvas
private var mIsDrawing = false
private var mBgColor: Int = Color.RED
private var mColors =
intArrayOf(Color.RED, Color.BLACK, Color.BLUE, Color.DKGRAY, Color.GREEN, Color.YELLOW)
init {
mSurfaceHolder.addCallback(this)
isFocusable = true
isFocusableInTouchMode = true
keepScreenOn = true
}
override fun surfaceCreated(p0: SurfaceHolder) {
mIsDrawing = true
Thread{
while (mIsDrawing) {
drawContent()
mBgColor = mColors[Random().nextInt(mColors.size)]
}
}.start()
}
override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {}override fun surfaceDestroyed(p0: SurfaceHolder) {
mIsDrawing = false
}
private fun drawContent(a) {
try {
Thread.sleep(1000)
mCanvas = mSurfaceHolder.lockCanvas()
mCanvas.drawColor(mBgColor)
} catch (e: Exception) {
e.printStackTrace()
} finally {
mSurfaceHolder.unlockCanvasAndPost(mCanvas)
}
}
}
Copy the code
The above control inherits SurfaceView to implement surfaceHolder. Callback interface, implement 1s change control background color
Unlike normal views, SurfaceView has its own Surface in WMS and SF, which is separate from the host window
Also, the Surface is not in the hierachy of the View, and its display is not controlled by the attributes of the View, so the SurfaceView cannot be nested. No panning, zooming, or other viewgroups can be performed before version 7.0. Panning, zooming, or other viewgroups can be performed after version 7.0.
SurfaceView involves three concepts
- Surface: Stores various information about cached canvas and drawing content
- SurfaceView: Represents the user interface
- SurfaceHolder: The SurfaceView operates the Surface through the SurfaceHolder
Surfaceview-surfaceholder is similar to MVC Model. View interacts with Model layer through Control. SurfaceView is responsible for displaying Surface data. And you need the SurfaceHolder control layer to interact with the Surface
1.3 SurfaceView Usage Scenario
The chestnut above through mSurfaceHolder. LockCanvas () to obtain the canvas, after the completion of the drawing, through mSurfaceHolder. UnlockCanvasAndPost (mCanvas) the canvas to the front-end buffer and exchange, In a normal view call, draw(Canvas Canvas) directly draws the Canvas by invalidate().
Calling invalidate() in the SurfaceView does not trigger the draw(Canvas Canvas) method because it sets setWillNotDraw(true) in the constructor. Similar to setWillNotDraw(true) and container controls such as LinearLayout generally do not need to execute draw()
This also indicates that the Google developers want us to use the SurfaceView to fetch the cache canvas through child threads to draw
In general, SurfaceView usage scenarios:
- SurefaceView is best used when the interface needs to be actively updated and the amount of data being drawn is large
- When the interface needs to be updated passively, scenarios such as gesture touches are not appropriate because onTouch receives events in the main thread and SurfaceView draws in the child thread. Triggering a draw also involves communication between threads
Second, the TextureView
Unlike SurfaceView, TextureView does not create a separate window in the WMS. Instead, it acts as a normal View in the View hierachy, so it can be moved, rotated, scaled, animated, etc., just like any other normal View. It is worth noting that TextureView must be used in a hardware-accelerated window
2.1 Basic usage of TextureView
class MyTextureView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : TextureView(context, attrs, defStyleAttr), TextureView.SurfaceTextureListener {
private var mIsRunning = false
private lateinit var mThread: Thread
private var mFps = 1F
private var mCanvas: Canvas? = null
private var mColors =
intArrayOf(Color.RED, Color.BLACK, Color.BLUE, Color.DKGRAY, Color.GREEN, Color.YELLOW)
init {
surfaceTextureListener = this
}
override fun onSurfaceTextureAvailable(p0: SurfaceTexture, p1: Int, p2: Int) {
mIsRunning = true
mThread = Thread {
while (true) {
val startTime = System.currentTimeMillis()
drawContent()
val needTime = startTime - System.currentTimeMillis()
val oneFrameTime = 1000 / mFps
if (needTime < oneFrameTime) {
Thread.sleep((oneFrameTime - needTime).toLong())
}
}
}
mThread.start()
}
private fun drawContent(a) {
try{ mCanvas = lockCanvas() mCanvas? .drawColor(mColors[Random().nextInt(mColors.size)]) }catch (e: Exception) {
e.printStackTrace()
} finally{ mCanvas? .let { unlockCanvasAndPost(it) } } }override fun onSurfaceTextureSizeChanged(p0: SurfaceTexture, p1: Int, p2: Int){}override fun onSurfaceTextureDestroyed(p0: SurfaceTexture): Boolean {
mIsRunning = false
return mIsRunning
}
override fun onSurfaceTextureUpdated(p0: SurfaceTexture){}}Copy the code
Similar to SurefaceView chestnuts above, the background color changes once for 1s
2.2 Differences between SurfaceView and TextView
This is a common interview question. It can be compared in the following ways
SurfaceView | TextureView | |
---|---|---|
memory | low | high |
draw | In a timely manner | 1 to 3 frame delay |
Power consumption | low | high |
Animations and screenshots | (Versions prior to Android 7.0) not supported | support |
SurfaceView instance – Frame animation optimization to avoid OOM
Android provides an AnimationDrawable for frame animation
val duration = 60
val animationDrawable = AnimationDrawable()
val intArray = Util.getSourceId()
for (sourceId in intArray) {
ContextCompat.getDrawable(this, sourceId)? .let { animationDrawable.addFrame(it, duration) } } imgView.setImageDrawable(animationDrawable) animationDrawable.start()Copy the code
In practice, however, this will cause memory problems because AnimationDrawable will load every frame into memory as a bitmap, which will cause OOM usage
The memory usage of a Bitmap is calculated as the number of pixels x the color mode of the Bitmap. The color mode ARGB_8888 occupies 4 bytes, and the RGB_565 without transparent channels occupies 2 bytes
The optimization solution to this problem is the optimization of Bitmap memory occupancy. We can draw Bitmap frame by frame in the sub-thread through SurfaceView, and the Bitmap of the next frame will be multiplexed with the Bitmap of the previous frame to achieve the purpose of memory multiplexing, avoiding OOM caused by loading all the pictures of all frames into the memory
The general implementation is as follows
class FrameAnimSurface @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : SurfaceView(context, attrs, defStyleAttr), SurfaceHolder.Callback {
private var mSurfaceHolder: SurfaceHolder = holder
private lateinit var mCanvas: Canvas
private var mIsDrawing = false
// The subscript of the currently playing image
private var mCurrentIndex = 0
// How long an image takes to execute
private var mDuration = 60
// Reuse the bitmap
private var mBitmap: Bitmap? = null
private var mResources = Util.getSourceId()
private var mBitmapRect = Rect()
private var mViewRect: Rect = Rect()
private var mPaint: Paint = Paint()
private var mOptions: BitmapFactory.Options = BitmapFactory.Options()
init {
// Bitmap reuse needs to be set to variable type
mOptions.inMutable = true
mOptions.inPreferredConfig = Bitmap.Config.RGB_565
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeResource(resources, mResources[0], options)
mBitmapRect.set(0.0, options.outWidth, options.outHeight)
mSurfaceHolder.addCallback(this)
isFocusable = true
isFocusableInTouchMode = true
keepScreenOn = true
}
override fun surfaceCreated(p0: SurfaceHolder) {
mViewRect.set(0.0, width, height)
mIsDrawing = true
Thread {
while (mIsDrawing) {
for (resourceId in mResources) {
val beginTime = System.currentTimeMillis()
createBitmap(resourceId)
draw()
val userTime = System.currentTimeMillis() - beginTime
if (userTime < mDuration) {
Thread.sleep(mDuration - userTime)
}
}
}
}.start()
}
override fun surfaceDestroyed(p0: SurfaceHolder) {
mIsDrawing = false
}
private fun draw(a) {
try{ mCanvas = mSurfaceHolder.lockCanvas() mBitmap? .let { bmp -> mCanvas.drawBitmap(bmp, mBitmapRect, mViewRect, mPaint) } }catch (e: Exception) {
e.printStackTrace()
} finally {
mSurfaceHolder.unlockCanvasAndPost(mCanvas)
}
}
override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {}
private fun createBitmap(resourceId: Int) {
mBitmap = BitmapFactory.decodeResource(resources, resourceId, mOptions)
/ / reuse bitmap
mOptions.inBitmap = mBitmap
}
}
Copy the code
The effect is as follows
Profile analysis shows that the memory usage is low and stable
For memory stack analysis, an object was allocated to mBitmap, occupying 184,118 Native memory
The single image size is 7201280 and bitmap. Config is RGB_565. 7201280*2=1843200 is approximately 184118, achieving the purpose of memory overcommitment
Four, the source address
Github.com/daleige/And…