最近打算开发一个属于自己的视频播放器,上网查了很多资料,好像都有SurfaceView的身影。虽然之前自定义摄像机有用过它,但是也是看着别人怎么用,没怎么去详细了解它,觉得是时候注意它了(并没有打算从源码角度分析它)。
View的更新
先聊聊View的更新!想更新View,必须主动调用View的invalidate()或postInvalidate()方法,然后onDraw()方法才会执行,完成View的更新。Android系统规定每16ms刷新一次屏幕,如果onDraw()方法逻辑比较复杂,没在16ms内执行完毕,那么更新的View在下一个16ms(甚至更多)才会显示出来,也就是更新前的View多停留了16ms(甚至更多),这样就这造成了用户看起来画面停顿。
所以务必将耗时的操作放到子线程!!将耗时的操作放到子线程!!将耗时的操作放到子线程!!重要的事,说3遍!
SurfaceView基本原理
SurfaceView拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面。由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程(子线程)中进行绘制,可以实现比较复杂而高效的UI,而且不会占用主线程资源。
详细请看罗老师的Android视图SurfaceView的实现原理分析
SurfaceView适用场景
从SurfaceView的基本原理可以看出,它和普通的View的区别
View | SurfaceView |
---|---|
适用于主动更新 | 适用于被动刷新 |
在主线程中进行画面更新 | 通常通过一个子线程来进行画面更新 |
绘图中没有使用双缓冲机制 | 在底层实现中就实现了双缓冲机制 |
主动更新和被动更新:
被动更新画面。比如棋类,这种用View就好了。因为画面的更新是依赖于 onTouch 来更新,可以直接使用 invalidate。因为这种情况下,这一次Touch和下一次的Touch需要的时间比较长些,不会产生影响。
主动更新画面。比如一个人在一直跑动。这就需要一个单独的Thread不停的重绘人的状态,避免阻塞主线程。所以显然View,需要SurfaceView来控制。
所以,对于那些资源敏感的操作,或者那些要求快速更新或者高速帧率的地方,例如使用3D图形,创建游戏,或者实时预览摄像头,这一点特别有用。
SurfaceView基本使用方法
- 声明SurfaceView,获取SurfaceView的SurfaceHolder,为SurfaceHolder添加Callback回调,主要实现SurfaceView创建和销毁时的方法。
- 创建子线程,主要用于执行复杂或耗时的操作,并更新SurfaceView。
- SurfaceView创建时,运行子线程。
- 每次绘制,需要调用SurfaceHolder的lockCanvas()获取Canvas对象,进行绘制。
- 每次绘制完成后,调用SurfaceHolder的unlockCanvasAndPost()方法,传入新的Canvas对象进行更新。
- SurfaceView销毁时,停止子线程,并停止绘制。
代码实现:
xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
Activity:
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
private SurfaceView surfaceView;
private DrawThread drawThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 声明SurfaceView
surfaceView = (SurfaceView) findViewById(R.id.surface_view);
// 获取SurfaceView的SurfaceHolder
SurfaceHolder holder = surfaceView.getHolder();
// 为SurfaceHolder添加Callback回调
holder.addCallback(this);
// 创建子线程
drawThread = new DrawThread(holder);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
// 运行子线程
drawThread.startDraw();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 停止子线程,并停止绘制
drawThread.stopDraw();
}
public static class DrawThread extends Thread {
private boolean isDrawing;
private SurfaceHolder holder;
private Paint paint, clearPaint;
public DrawThread(SurfaceHolder holder) {
this.holder = holder;
paint = new Paint();
paint.setTextSize(40);
paint.setColor(Color.WHITE);
clearPaint = new Paint();
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
public void startDraw() {
isDrawing = true;
start();
}
public void stopDraw() {
isDrawing = false;
}
@Override
public void run() {
while (true) {
if (!isDrawing) break;
draw();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void draw() {
Canvas canvas = null;
try {
// 每次绘制,需要调用SurfaceHolder的lockCanvas()获取Canvas对象
canvas = holder.lockCanvas();
// 清空画布
canvas.drawPaint(clearPaint);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat format = new SimpleDateFormat("时间:HH:mm:ss");
String time = format.format(calendar.getTime());
// 进行绘制
canvas.drawText(time, 100, 100, paint);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 每次绘制完成后,调用SurfaceHolder的unlockCanvasAndPost()方法,传入新的Canvas对象进行更新
if (canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
}
}
}