SurfaceView 垂直线在屏幕上绘制太慢
SurfaceView Vertical Line Drawing too Slowly across Screen
我一直在尝试尽可能多地发挥 SurfaceView 的性能。目前,我正在对其进行 subclass 处理并在其上实现 运行nable 接口而不是回调。我知道它没有硬件加速。
不过,如果我绘制一条 canvas 原始垂直线在屏幕上滚动或绘制一条位图垂直线,两者 运行 每次通过后都会越来越慢。这对我来说就像内存泄漏,或者只是 Android 本身? OpenGL 或其他库真的是我最后的选择吗?
我之前以不错的速度绘制了大量滚动背景(我认为每个刻度大约 5 个像素,我的目标是每个刻度大约 20-50 个像素,如果有的话,在渲染的过程中会减少停止).
编辑:这是扩展的 SurfaceView、它创建的线程、绘图方法及其初始化。基本上,这是一个稍微大一点的 class,它只保存这个屏幕的数据。 drawXYZ()
方法只是简单地使用canvas图元或位图来绘制,主要是作为背景进行绘制,这是一种纯色背景,上面有一些垂直和水平的线条,就像乐谱一样,几乎没有计算。
drawCursor
是滚动垂直线的原因,当我让它从左到右循环滚动时,它最终比第一个滚动慢得多。
public class MySurfaceView extends SurfaceView implements Runnable
{
Thread renderThread = null;
SurfaceHolder holder;
volatile boolean running = false;
public MySurfaceView() {
super(mainActivity);
this.holder = getHolder();
holder.setFixedSize(screenW, screenH);
}
public void resume() {
running = true;
renderThread = new Thread(this);
renderThread.start();
}
@Override
public void run() {
while (running) {
if (!holder.getSurface().isValid()) {
continue;
}
Canvas canvas = holder.lockCanvas();
if(canvas != null) {
doDraw(canvas);
holder.unlockCanvasAndPost(canvas);
}
}
}
public void pause() {
running = false;
while (true) {
try {
renderThread.join();
break;
} catch (InterruptedException e) {
// retry
}
}
}
protected void doDraw(Canvas canvas)
{
canvas.drawColor(Color.rgb(56, 56, 62));
lastNotePlayed = OptionsContainer.getNotePlaying();
//Draw contours (rows).
paint.setColor(Color.rgb(0, 255, 255));
paint.setStrokeWidth(3);
paint.setTextSize(35);
drawContours(canvas, paint);
//Beats per measure (BPM).
paint.setColor(Color.rgb(233, 232, 232));
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.STROKE);
paint.setPathEffect(bpmLines);
drawBPM(canvas, paint);
paint.setPathEffect(null);
//Draw measures.
paint.setStrokeWidth(5);
drawMeasures(canvas, paint);
//Draw note node inputs.
paint.setColor(Color.rgb(76, 255, 0));
for (int i = 0; i < OptionsContainer.noteList.length; i++) {
if (OptionsContainer.noteList[i].getContour() != 0) {
if (OptionsContainer.noteList[i].getContour() > (OptionsContainer.contour / 2)) {
//Staff on left side, below note.
canvas.drawBitmap(lowerStaffBmp, OptionsContainer.noteList[i].getX(), OptionsContainer.noteList[i].getY(), null);
} else {
canvas.drawBitmap(higherStaffBmp, OptionsContainer.noteList[i].getX(), OptionsContainer.noteList[i].getY() - 40, null);
}
}
}
//Draw cursor.
paint.setStrokeWidth(2);
paint.setColor(Color.WHITE);
drawCursor(canvas, paint);
if (OptionsContainer.isRest)
canvas.drawBitmap(restBmp, (OptionsContainer.screenWidth / 2), (screenHeight - 100) / 2, null);
}
}
@Override
public void init() {
surfaceView = new MySurfaceView();
surfaceView.setLayoutParams(layoutParams);
surfaceView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// Normalize x,y between 0 and 1
float x = event.getX();
float y = event.getY();
if (x < (OptionsContainer.screenWidth) && y < screenH) {
NoteNode note = new NoteNode(x, y, MainActivity.options);
if (note.getContour() == OptionsContainer.noteList[note.getBeat() - 1].getContour()) {
OptionsContainer.noteList[note.getBeat() - 1] = new NoteNode(x, screenHeight + 200, MainActivity.options);
} else {
OptionsContainer.noteList[note.getBeat() - 1] = note;
}
}
}
return true;
}
});
mainActivity.addContentView(surfaceView, layoutParams);
surfaceView.resume();
}
编辑 #2:最终答案
在drawBPM()
中绘制路径后添加Path.reset()
。我想这会阻止该路径的内存泄漏,该路径试图跟踪它一直在写入和覆盖的所有路径,而我们仅查看屏幕上的线条就知之甚少。有一个类似的 Stack Overflow 问题,但 fadden 下面的调试提示 非常 有助于最初尝试找出问题所在和位置。
"Squeezing performance" 和 Canvas-渲染在 SurfaceView 上并没有真正结合在一起,但你可以在许多设备上完成。
Grafika's "multi-surface test" Activity 有一个用软件渲染的弹跳圆圈。我没有注意到它随着时间的推移变慢,所以我怀疑你的代码有问题。注意 Grafika 不子类化 SurfaceView,我通常建议不要这样做——做错事太容易了。将 SurfaceView 子类化的唯一正当理由是,如果您想在 Surface 和 视图上绘制,例如对于某种遮罩效果。
您没有显示任何代码,因此我们无法告诉您更多信息。
我没有看到代码中有任何明显的错误;看起来很简单。我会检查以确保 OptionsContainer.noteList.length
没有无限增长。下一步将是使用 traceview 来确定渲染的哪一部分变慢,或者只是分散 System.nanoTime()
调用来确定哪个部分越来越慢。如果所示方法中的所有内容都以一致的速度执行,但 drawCursor()
除外,请将时间检查调用移到那里,缩小范围,直到您发现是什么在耗尽您的性能。
如果某些东西消耗内存的速度快到足以导致堆问题,您应该会在 logcat
输出中看到大量 GC activity。 DDMS allocation tracker tool 可以提供帮助。
我一直在尝试尽可能多地发挥 SurfaceView 的性能。目前,我正在对其进行 subclass 处理并在其上实现 运行nable 接口而不是回调。我知道它没有硬件加速。
不过,如果我绘制一条 canvas 原始垂直线在屏幕上滚动或绘制一条位图垂直线,两者 运行 每次通过后都会越来越慢。这对我来说就像内存泄漏,或者只是 Android 本身? OpenGL 或其他库真的是我最后的选择吗?
我之前以不错的速度绘制了大量滚动背景(我认为每个刻度大约 5 个像素,我的目标是每个刻度大约 20-50 个像素,如果有的话,在渲染的过程中会减少停止).
编辑:这是扩展的 SurfaceView、它创建的线程、绘图方法及其初始化。基本上,这是一个稍微大一点的 class,它只保存这个屏幕的数据。 drawXYZ()
方法只是简单地使用canvas图元或位图来绘制,主要是作为背景进行绘制,这是一种纯色背景,上面有一些垂直和水平的线条,就像乐谱一样,几乎没有计算。
drawCursor
是滚动垂直线的原因,当我让它从左到右循环滚动时,它最终比第一个滚动慢得多。
public class MySurfaceView extends SurfaceView implements Runnable
{
Thread renderThread = null;
SurfaceHolder holder;
volatile boolean running = false;
public MySurfaceView() {
super(mainActivity);
this.holder = getHolder();
holder.setFixedSize(screenW, screenH);
}
public void resume() {
running = true;
renderThread = new Thread(this);
renderThread.start();
}
@Override
public void run() {
while (running) {
if (!holder.getSurface().isValid()) {
continue;
}
Canvas canvas = holder.lockCanvas();
if(canvas != null) {
doDraw(canvas);
holder.unlockCanvasAndPost(canvas);
}
}
}
public void pause() {
running = false;
while (true) {
try {
renderThread.join();
break;
} catch (InterruptedException e) {
// retry
}
}
}
protected void doDraw(Canvas canvas)
{
canvas.drawColor(Color.rgb(56, 56, 62));
lastNotePlayed = OptionsContainer.getNotePlaying();
//Draw contours (rows).
paint.setColor(Color.rgb(0, 255, 255));
paint.setStrokeWidth(3);
paint.setTextSize(35);
drawContours(canvas, paint);
//Beats per measure (BPM).
paint.setColor(Color.rgb(233, 232, 232));
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.STROKE);
paint.setPathEffect(bpmLines);
drawBPM(canvas, paint);
paint.setPathEffect(null);
//Draw measures.
paint.setStrokeWidth(5);
drawMeasures(canvas, paint);
//Draw note node inputs.
paint.setColor(Color.rgb(76, 255, 0));
for (int i = 0; i < OptionsContainer.noteList.length; i++) {
if (OptionsContainer.noteList[i].getContour() != 0) {
if (OptionsContainer.noteList[i].getContour() > (OptionsContainer.contour / 2)) {
//Staff on left side, below note.
canvas.drawBitmap(lowerStaffBmp, OptionsContainer.noteList[i].getX(), OptionsContainer.noteList[i].getY(), null);
} else {
canvas.drawBitmap(higherStaffBmp, OptionsContainer.noteList[i].getX(), OptionsContainer.noteList[i].getY() - 40, null);
}
}
}
//Draw cursor.
paint.setStrokeWidth(2);
paint.setColor(Color.WHITE);
drawCursor(canvas, paint);
if (OptionsContainer.isRest)
canvas.drawBitmap(restBmp, (OptionsContainer.screenWidth / 2), (screenHeight - 100) / 2, null);
}
}
@Override
public void init() {
surfaceView = new MySurfaceView();
surfaceView.setLayoutParams(layoutParams);
surfaceView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// Normalize x,y between 0 and 1
float x = event.getX();
float y = event.getY();
if (x < (OptionsContainer.screenWidth) && y < screenH) {
NoteNode note = new NoteNode(x, y, MainActivity.options);
if (note.getContour() == OptionsContainer.noteList[note.getBeat() - 1].getContour()) {
OptionsContainer.noteList[note.getBeat() - 1] = new NoteNode(x, screenHeight + 200, MainActivity.options);
} else {
OptionsContainer.noteList[note.getBeat() - 1] = note;
}
}
}
return true;
}
});
mainActivity.addContentView(surfaceView, layoutParams);
surfaceView.resume();
}
编辑 #2:最终答案
在drawBPM()
中绘制路径后添加Path.reset()
。我想这会阻止该路径的内存泄漏,该路径试图跟踪它一直在写入和覆盖的所有路径,而我们仅查看屏幕上的线条就知之甚少。有一个类似的 Stack Overflow 问题,但 fadden 下面的调试提示 非常 有助于最初尝试找出问题所在和位置。
"Squeezing performance" 和 Canvas-渲染在 SurfaceView 上并没有真正结合在一起,但你可以在许多设备上完成。
Grafika's "multi-surface test" Activity 有一个用软件渲染的弹跳圆圈。我没有注意到它随着时间的推移变慢,所以我怀疑你的代码有问题。注意 Grafika 不子类化 SurfaceView,我通常建议不要这样做——做错事太容易了。将 SurfaceView 子类化的唯一正当理由是,如果您想在 Surface 和 视图上绘制,例如对于某种遮罩效果。
您没有显示任何代码,因此我们无法告诉您更多信息。
我没有看到代码中有任何明显的错误;看起来很简单。我会检查以确保 OptionsContainer.noteList.length
没有无限增长。下一步将是使用 traceview 来确定渲染的哪一部分变慢,或者只是分散 System.nanoTime()
调用来确定哪个部分越来越慢。如果所示方法中的所有内容都以一致的速度执行,但 drawCursor()
除外,请将时间检查调用移到那里,缩小范围,直到您发现是什么在耗尽您的性能。
如果某些东西消耗内存的速度快到足以导致堆问题,您应该会在 logcat
输出中看到大量 GC activity。 DDMS allocation tracker tool 可以提供帮助。