【转】SurfaceView理解与Demo
-
原文【1】是java,以下是修改为kotlin语法
先上代码
SurfaceHolder.Callback接口
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
//一般横竖屏会进入
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
}
override fun surfaceCreated(holder: SurfaceHolder?) {
}
- 模板
class MySurfaceView : SurfaceView,SurfaceHolder.Callback,Runnable{
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
initView(context);
}
//SurfaceHolder
private var mHolder: SurfaceHolder? = null
//用于绘图的Canvas
private var mCanvas: Canvas? = null
//子线程标志位
private var mIsDrawing: Boolean = false
private fun initView(context: Context) {
mHolder = holder
//添加回调
mHolder?.addCallback(this)
setFocusable(true);
setFocusableInTouchMode(true);
setKeepScreenOn(true);
}
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
mIsDrawing=false;
}
override fun surfaceCreated(holder: SurfaceHolder?) {
mIsDrawing=true;
Thread(this).start();
}
override fun run() {
val start = System.currentTimeMillis()
while (mIsDrawing) {
mydraw()
//通过线程休眠以控制刷新速度
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private fun mydraw() {
try {
//锁定画布并返回画布对象
mCanvas = mHolder?.lockCanvas()
//接下去就是在画布上进行一下draw
//TODO 具体画法
} catch (e: Exception) {
} finally {
//当画布内容不为空时,才post,避免出现黑屏的情况。
if (mCanvas != null)
mHolder?.unlockCanvasAndPost(mCanvas)
}
}
以下是一个简单的Demo
(绘图板,即通过监听触摸事件完成内容的绘制)
class MySurfaceView : SurfaceView,SurfaceHolder.Callback,Runnable{
// @JvmOverloads
// constructor(
// context: Context,
// attrs: AttributeSet? = null,
// defStyleAttr: Int = 0)
// : super(context, attrs, defStyleAttr)
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
initView(context);
}
private val TAG = "SurfaceView"
//SurfaceHolder
private var mHolder: SurfaceHolder? = null
//用于绘图的Canvas
private var mCanvas: Canvas? = null
//子线程标志位
private var mIsDrawing: Boolean = false
//画笔
private var mPaint=Paint()
//路径
private var mPath = Path();
private fun initView(context: Context) {
mHolder = holder
//添加回调
mHolder?.addCallback(this)
//初始化画笔
mPaint.setStyle(Paint.Style.STROKE)
mPaint.setStrokeWidth(6.toFloat())
mPaint.setAntiAlias(true)
mPaint.setColor(Color.RED)
setFocusable(true);
setFocusableInTouchMode(true);
setKeepScreenOn(true);
}
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
mIsDrawing=false;
}
override fun surfaceCreated(holder: SurfaceHolder?) {
mIsDrawing=true;
Thread(this).start();
}
override fun run() {
val start = System.currentTimeMillis()
while (mIsDrawing) {
mydraw()
val end = System.currentTimeMillis()
if (end - start < 100) {
try {
Thread.sleep(100 - end + start)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
}
private fun mydraw() {
try {
//锁定画布并返回画布对象
mCanvas = mHolder?.lockCanvas()
//接下去就是在画布上进行一下draw
mCanvas?.drawColor(Color.WHITE)
mCanvas?.drawPath(mPath, mPaint)
} catch (e: Exception) {
} finally {
//当画布内容不为空时,才post,避免出现黑屏的情况。
if (mCanvas != null)
mHolder?.unlockCanvasAndPost(mCanvas)
}
}
/**
* 绘制触摸滑动路径
* @param event MotionEvent
* @return true
*/
override fun onTouchEvent(event: MotionEvent): Boolean {
var x = event.x
var y = event.y
when (event.action) {
MotionEvent.ACTION_DOWN -> {
Log.d(TAG, "onTouchEvent: down")
mPath.moveTo(x, y)
}
MotionEvent.ACTION_MOVE -> {
Log.d(TAG, "onTouchEvent: move")
mPath.lineTo(x, y)
}
MotionEvent.ACTION_UP -> Log.d(TAG, "onTouchEvent: up")
}
return true
}
/**
* 清屏
* @return true
*/
fun reDraw(): Boolean {
mPath.reset()
return true
}
}
SurfaceView的双缓冲机制
问题的由来
CPU访问内存的速度要远远快于访问屏幕的速度。如果需要绘制大量复杂的图像时,每次都一个个从内存中读取图形然后绘制到屏幕就会造成多次地访问屏幕,从而导致效率很低。这就跟CPU和内存之间还需要有三级缓存一样,需要提高效率。
第一层缓冲
在绘制图像时不用上述一个一个绘制的方案,而采用先在内存中将所有的图像都绘制到一个Bitmap对象上,然后一次性将内存中的Bitmap绘制到屏幕,从而提高绘制的效率。Android中View的onDraw()方法已经实现了这一层缓冲。onDraw()方法中不是绘制一点显示一点,而是都绘制完后一次性显示到屏幕。
第二层缓冲
onDraw()方法的Canvas对象是和屏幕关联的,而onDraw()方法是运行在UI线程中的,如果要绘制的图像过于复杂,则有可能导致应用程序卡顿,甚至ANR。因此我们可以先创建一个临时的Canvas对象,将图像都绘制到这个临时的Canvas对象中,绘制完成之后再将这个临时Canvas对象中的内容(也就是一个Bitmap),通过drawBitmap()方法绘制到onDraw()方法中的canvas对象中。这样的话就相当于是一个Bitmap的拷贝过程,比直接绘制效率要高,可以减少对UI线程的阻塞。
SurfaceView
在SurfaceView中,我们一般都会开启一个子线程,然后在子线程的run方法中通过SurfaceHolder的lockCanvas方法获取到Canvas进行绘制操作,绘制完以后再通过SurfaceHolder的unlockCanvasAndPost方法释放canvas并提交更改。
SurfaceView的特点
- View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁的刷新
- View在主线程中对画面进行刷新,而SurfaceView通常会通过一个子线程来进行页面的刷新
- View在绘图时没有使用双缓冲机制,而SurfaceView在底层实现机制中就已经实现了双缓冲机制
SurfaceView主要用在视频播放以及游戏等应用中
(1)SurfaceView必须实现SurfaceHolder的Callback接口,主要是3个方法,分别是surfaceCreated、surfaceChanged、surfaceDestroyed。从名字就可以看出来这个是监听SurfaceView状态的,跟Activity的生命周期有点像。
- 当SurfaceView被创建时,surfaceCreated方法会被调用,surfaceCreated方法中一般做初始化动作,比如设置绘制线程的标记位,创建用于绘制的子线程等
- 当SurfaceView的状态改变时,比如尺寸大小、格式等,常见的操作就是旋转屏幕了,这个时候surfaceChanged方法会被调用。
- 当SurfaceView被销毁时,surfaceDestroyed方法会被调用。surfaceDestroyed被调用后,就不能再对Surface对象进行任何操作,所以我们需要在surfaceDestroyed方法中将绘制的子线程停掉。
(2)由于SurfaceView常被用于游戏、视频等场景,绘制操作会相对复杂很多,通常都需要开启子线程,在子线程中执行绘制操作,以免阻塞UI线程。在子线程中,我们通过SurfaceHolder的lockCanvas方法获取Canvas对象来进行具体的绘制操作,此时Canvas对象被当前线程锁定,绘制完成后通过SurfaceHolder的unlockCanvasAndPost方法提交绘制结果并释放Canvas对象。
(3)用于控制子线程绘制的标记参数,如上面代码中的isDrawing变量,需要用volatile关键字修饰,以保证多线程安全。
(4)通过将绘制操作移到子线程中,这也是双缓冲的体现。
SurfaceView、SurfaceHolder和Surface的简单介绍
要分析SurfaceView,就得和其他2个类一起分析,那就是SurfaceHolder和Surface,这3者之间其实是典型的MVC模式,其中SurfaceView对应的就是View层,SurfaceHolder就是controler接口,而Surface就是对应的Model层,它里面持有Canvas,保存着绘制的数据。
(1)SurfaceView中持有SurfaceHolder和Surface,SurfaceHolder中的接口可以分为2类,一类是Callback接口,也就是我们上面模版代码中实现的3个接口方法,这类接口主要是用于监听SurfaceView的状态,以便我们进行相应的处理,比如创建绘制子线程,停止绘制等。另一类方法主要用于和Surface以及SurfaceView交互,比如lockCanvas方法和unlockCanvasAndPost方法用于获取Canvas以及提交绘制结果等。
public interface SurfaceHolder {
...
public interface Callback {
public void surfaceCreated(SurfaceHolder holder);
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height);
public void surfaceDestroyed(SurfaceHolder holder);
}
public interface Callback2 extends Callback {
public void surfaceRedrawNeeded(SurfaceHolder holder);
}
public void addCallback(Callback callback);
public void removeCallback(Callback callback);
public Canvas lockCanvas();
public Canvas lockCanvas(Rect dirty);
public void unlockCanvasAndPost(Canvas canvas);
public Surface getSurface();
...
}
(2)SurfaceView继承自View,但是其实和View是有很大的不同的,除了文章前面介绍的几点SurfaceView的特性外,在底层SurfaceView也很大的不同,包括拥有自己独立的绘图表面等。从下面SurfaceView的源码中我们可以看到,我们调用SurfaceHolder的lockCanvas方法实际上调用的是Surface的lockCanvas方法,返回的是Surface中的Canvas。并且调用过程加了一个可重入锁mSurfaceLock。所以绘制过程中只能绘制完一帧内容并提交更改以后才会释放Canvas,也就是才能继续下一帧的绘制操作
public class SurfaceView extends View {
...
final Surface mSurface = new Surface();
final ReentrantLock mSurfaceLock = new ReentrantLock();
private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
private static final String LOG_TAG = "SurfaceHolder";
...
@Override
public void addCallback(Callback callback) {
synchronized (mCallbacks) {
// This is a linear search, but in practice we'll
// have only a couple callbacks, so it doesn't matter.
if (mCallbacks.contains(callback) == false) {
mCallbacks.add(callback);
}
}
}
@Override
public void removeCallback(Callback callback) {
synchronized (mCallbacks) {
mCallbacks.remove(callback);
}
}
@Override
public Canvas lockCanvas() {
return internalLockCanvas(null);
}
@Override
public Canvas lockCanvas(Rect inOutDirty) {
return internalLockCanvas(inOutDirty);
}
private final Canvas internalLockCanvas(Rect dirty) {
mSurfaceLock.lock();
Canvas c = null;
if (!mDrawingStopped && mWindow != null) {
try {
c = mSurface.lockCanvas(dirty);
} catch (Exception e) {
Log.e(LOG_TAG, "Exception locking surface", e);
}
}
if (c != null) {
mLastLockTime = SystemClock.uptimeMillis();
return c;
}
long now = SystemClock.uptimeMillis();
long nextTime = mLastLockTime + 100;
if (nextTime > now) {
try {
Thread.sleep(nextTime-now);
} catch (InterruptedException e) {
}
now = SystemClock.uptimeMillis();
}
mLastLockTime = now;
mSurfaceLock.unlock();
return null;
}
@Override
public void unlockCanvasAndPost(Canvas canvas) {
mSurface.unlockCanvasAndPost(canvas);
mSurfaceLock.unlock();
}
@Override
public Surface getSurface() {
return mSurface;
}
@Override
public Rect getSurfaceFrame() {
return mSurfaceFrame;
}
};
...
}
(3)Surface实现了Parcelable接口,因为它需要在进程间以及本地方法间传输。Surface中创建了Canvas对象,用于执行具体的绘制操作
/**
* Handle onto a raw buffer that is being managed by the screen compositor.
* ...
*/
public class Surface implements Parcelable {
final Object mLock = new Object(); // protects the native state
private final Canvas mCanvas = new CompatibleCanvas();
...
public Canvas lockCanvas(Rect inOutDirty)
throws Surface.OutOfResourcesException, IllegalArgumentException {
synchronized (mLock) {
checkNotReleasedLocked();
if (mLockedObject != 0) {
throw new IllegalArgumentException("Surface was already locked");
}
mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
return mCanvas;
}
}
public void unlockCanvasAndPost(Canvas canvas) {
synchronized (mLock) {
checkNotReleasedLocked();
if (mHwuiContext != null) {
mHwuiContext.unlockAndPost(canvas);
} else {
unlockSwCanvasAndPost(canvas);
}
}
}
private void unlockSwCanvasAndPost(Canvas canvas) {
if (canvas != mCanvas) {
throw new IllegalArgumentException("canvas object must be the same instance that "
+ "was previously returned by lockCanvas");
}
if (mNativeObject != mLockedObject) {
Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
Long.toHexString(mLockedObject) +")");
}
if (mLockedObject == 0) {
throw new IllegalStateException("Surface was not locked");
}
try {
nativeUnlockCanvasAndPost(mLockedObject, canvas);
} finally {
nativeRelease(mLockedObject);
mLockedObject = 0;
}
}
...
}
Last
主要是对双缓冲机制的理解