大家好,又见面了,我是你们的朋友全栈君。
双缓冲机制
不管是什么操作系统,都有个“图像数据缓冲区”,存放颜色数据,每隔一段时间,把这些颜色数据投射到显示器上,我们就看到了各种各样的画面。
对于应用程序来说,只需要把想要展示的内容存放到“图像数据缓冲区”就可以了,这个操作也基本是系统帮我们做了。这样的模式有个问题就是:如果系统每16ms投射一次图像数据,而我们的UI显示此时还没绘制完成,就只能显示一部分,剩下的一部分就是上一次是图像。
对于这种情况,系统一般会用背景色来填充未完成的部分,保证不会出现图像残影,但是这样又会导致闪屏:一个完整的图像经过多次投射才显示完,每次都有一部分从背景色变为图像,这样的体验就是闪屏。
解决方法就是双缓冲机制,对于绘制复杂的图像,比如游戏、视频等,会创建第二个“图像数据缓冲区”。绘制完成前,图像数据都是放在第二个缓冲区,绘制完成后,才会一次性复制到另一个缓冲区,这样就完美解决了闪屏的问题。
SurfaceView简介
一、Android图层关系
Android中,每个Activity对应一个显示图层,这个Activity里的所有控件都是显示在这个图层中。而SurfaceView跟其它控件不一样,它单独对应一个图层,跟Activity平级。但是在层次排列上,SurfaceView的图层在Activity图层的下面,所以需要在Activity图层上显示一块透明的区域,用于显示下面的SurfaceView图层。
那么,Activity怎么知道这块透明区域位置在哪、宽高多少呢?上面说的是显示图层不一样,但是布局的时候,SurfaceView依然是Activity的RootView的Child,所有的measure、layout等流程都是和其它控件一样的。
二、SurfaceView和普通View的区别
普通的View,onDraw()方法是在主线程中调用的,有两种情况下会被调用。一种是画面有变化(滑动、转场等)时,系统通知调用,另一种是主动调用invalidate()告诉系统,然后再等待系统的通知。总的来说,是一种被动等待刷新,不能控制刷新频率。
而SurfaceView不在Activity那个图层,绘制、刷新完全不受约束,想什么时候绘制、刷新都可以自由控制。
总的来说,SurfaceView主要就是绘制、刷新和显示过程跟普通View不一样而已,其他的都一样。缓存图像数据、显示等等这些事都是系统底层做好的,我们只需要关注绘制、刷新过程就行,SurfaceView开放的几个接口也基本都是绘制、刷新的。
SurfaceView相关接口
一、SurfaceView的接口
public SurfaceHolder getHolder()
SurfaceView只有一个常用接口getHolder(),返回一个SurfaceHolder对象,通过这个对象管理图层的绘制、刷新等操作。
二、SurfaceHolder的接口
void addCallback(SurfaceHolder.Callback callback)
通过这个方法添加一个callback,监听Surface图层的生命周期
void setFixedSize(int width, int height)
通过这个方法,设置Surface图层的大小,设置之后是不可变的。这个方法需要在UI线程调用。
void setSizeFromLayout()
设置Surface图层的大小根据容器(即Activity)的改变而改变,通过callback可以监听Surface的改变。这个方法也需要在UI线程调用。
Canvas lockCanvas() 和 void unlockCanvasAndPost(Canvas canvas)
通过lockCanvas()获取Surface对应的Canvas,进行绘制操作,绘制完成后,通过unlockCanvasAndPost()提交,提交完成后绘制的内容就会显示在屏幕上。注意,下次再次获取Canvas时,上一次绘制的内容可能丢失了,需要重绘。
Rect getSurfaceFrame()
返回Surface对应的Rect,但是坐标left、top的值总是0。注意:不要修改这个Rect。
Surface getSurface()
直接访问Surface,如有有需要的话,可以用这个方法获取Surface,直接进行操作。
SurfaceView自定义绘制demo
public class SurfaceViewDraw extends SurfaceView implements SurfaceHolder.Callback,Runnable{
private static final String TAG = “SurfaceViewDraw”;
private SurfaceHolder mHolder;
private boolean surfaceAvailable;
private Canvas mCanvas;
private Paint mPaint;
private Path mPath;
private Thread mDrawThread;
public SurfaceViewDraw(Context context) {
super(context);
initView();
}
public SurfaceViewDraw(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
mPath = new Path();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(6);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
setFocusable(true);
setFocusableInTouchMode(true);
setKeepScreenOn(true);
}
private void draw() {
try {
//锁定画布并返回画布对象
mCanvas = mHolder.lockCanvas();
mCanvas.drawColor(Color.WHITE);
mCanvas.drawPath(mPath, mPaint);
} catch (Exception e) {
} finally {
//当画布内容不为空时才提交显示
if (mCanvas != null)
mHolder.unlockCanvasAndPost(mCanvas);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 在主线程中接收用户的触摸事件,记录下来。在子线程中,每隔100毫秒执行一次draw()方法,根据主线程中的触摸绘制图像
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, “onTouchEvent – down”);
mPath.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, “onTouchEvent – move”);
mPath.lineTo(x, y);
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, “onTouchEvent – up”);
break;
}
return true;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
surfaceAvailable = true;
// surface创建完成后,就启动子线程,循环进行绘制操作。
if(null == mDrawThread){
mDrawThread = new Thread(this);
}
mDrawThread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
surfaceAvailable = false;
}
@Override
public void run() {
// 每隔100毫秒,执行一次draw()方法,即刷新频率为100毫秒
while (surfaceAvailable) {
draw();
// 严格一点的话应该把绘制时间算进去,这里主要演示用法,就不搞那么复杂了
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
SurfaceView在播放视频中的使用
使用MediaPlayer播放音视频,通过setDisplay()方法设置一个Surface,让视频画面显示出来。这里面涉及到两个过程,一个是player本身的播放操作、控制,一个是Surface的初始化操作,这两个过程是相互独立的,互不影响,唯一的交集就是Surface创建完成后调用player的setDisplay()方法将Surface设置进去而已。
下面使用的player是简单封装过的,下面也会贴出主要代码,了解MediaPlayer详情可以看 – MediaPlaer详解和使用
SurfaceView在播放视频中的使用如下:
public class PlayerActivity extends Activity {
private static final String TAG = “PlayerActivity”;
private static final String VIDEO_DIR_0 = “/storage/sdcard0/zzzccc/videotest/video00.mp4”;
private MyPlayer mPlayer;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_surfaceviewplayer);
// 播放视频。MyPlayer的代码后面贴出
mPlayer = new MyPlayer();
mPlayer.play(this, Uri.parse(VIDEO_DIR_0));
// 初始化Surface,创建成功后,调用player的setDisplay()方法设置Surface
SurfaceView surfaceView = findViewById(R.id.sv_player);
SurfaceHolder holder = surfaceView.getHolder();
holder.setFormat(PixelFormat.RGB_888);
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
mPlayer.setDisplay(holder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 这里可以实时监听视频界面的变化
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 界面不可见时就会销毁,比如Activity的onStop()方法。这里不需要做额外的操作,player会自动处理
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
MyPlayer代码:
public class MyPlayer implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
private MediaPlayer mPlayer;
private boolean hasPrepared;
private void initIfNecessary() {
if (null == mPlayer) {
mPlayer = new MediaPlayer();
mPlayer.setOnErrorListener(this);
mPlayer.setOnCompletionListener(this);
mPlayer.setOnPreparedListener(this);
}
}
public void play(Context context, Uri dataSource) {
hasPrepared = false; // 开始播放前讲Flag置为不可操作
initIfNecessary(); // 如果是第一次播放/player已经释放了,就会重新创建、初始化
try {
mPlayer.reset(); // 如果不是第一次播放,最好调一下reset()
mPlayer.setDataSource(context, dataSource); // 设置曲目资源
mPlayer.prepareAsync(); // 异步的准备方法
} catch (IOException e) {
e.printStackTrace();
}
}
public void start() {
// release()会释放player、将player置空,所以这里需要判断一下
if (null != mPlayer && hasPrepared) {
mPlayer.start();
}
}
public void pause() {
if (null != mPlayer && hasPrepared) {
mPlayer.pause();
}
}
public void seekTo(int position) {
if (null != mPlayer && hasPrepared) {
mPlayer.seekTo(position);
}
}
public void setDisplay(SurfaceHolder holder) {
if (null != mPlayer) {
mPlayer.setDisplay(holder);
}
}
public void release() {
hasPrepared = false;
mPlayer.stop();
mPlayer.release();
mPlayer = null;
}
@Override
public void onPrepared(MediaPlayer mp) {
hasPrepared = true; // 准备完成后回调到这里
start();
}
@Override
public void onCompletion(MediaPlayer mp) {
hasPrepared = false;
// 通知调用处,调用play()方法进行下一个曲目的播放
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
hasPrepared = false;
return false;
}
}
———————
作者:thinkreduce
来源:CSDN
原文:https://blog.csdn.net/u014606081/article/details/79963733
版权声明:本文为博主原创文章,转载请附上博文链接!
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/155058.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...