性能优化 – 优化SurfaceView的线程调用
目录
SurfaceView的基本用法
SurfaceView是一个适用于频繁的刷新布局的View,可以在非主线程中刷新View,其基本用法如下
创建实例对象
SurfaceView mSurfaceView = new SurfaceView(this);
setContentView(mSurfaceView);
添加SurfaceHolder.Callback
mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
在Surface创建改变和销毁时会调用此回调
一般会在surfaceCreated
方法中启用一个线程,然后借用这个线程来刷新View
mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
private boolean destroy = false;
@Override
public void surfaceCreated(final SurfaceHolder holder) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (!destroy) {
Canvas canvas = holder.lockCanvas();
onDraw(canvas);
holder.unlockCanvasAndPost(canvas);
}
}
});
thread.start();
}
private void onDraw(Canvas canvas) {
canvas.drawColor(Color.GREEN);
//.......................在此做对View的绘制操作
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
destroy = true;
}
});
首先借用new Thread
创建一个线程,然后用一个变量destroy
来标记SurfaceView的状态,借助destroy
来控制线程的启停.
然后在线程中通过holder.lockCanvas()
获得画布对象canvas
,通过holder.unlockCanvasAndPost(canvas)
释放和发送canvas
对象来刷新布局.
值得注意的是用以上方法获得的Canvas对象是之前留下来的,所以如果没有需要保留上次绘制的情况下,需要调用canvas.drawColor()
来执行一次清屏操作.
问题
基本用法介绍完了,那么思考个问题,上面创建了一个线程,几乎是死循环,就算没有新视图的刷新,线程还是一直在运行,这样是对系统资源的一种浪费,那么该如何优化线程的使用呢?
加延时吗?
加延时的化,Android屏幕的刷新时间是16ms,如果超过这个时间,便会有卡顿的感觉,也就是说加延时的话最多在每次循环里加16ms.
每次延时16ms,在一般情况下有点杯水车薪的感觉,然后有时候也可能有频繁刷新的请求,在请求频繁时或者绘制强度比较高的情况下16ms依然是造成视图卡顿的.
那么到底应该如何优化线程的调用呢?
解决方案
其实我们可以使用HandlerThread
来代替普通的Thread
,HandlerThread
相比于普通的Thread
,在没有任务请求时时阻塞挂起的,几乎不会消耗多少系统资源,有任务请求时又可以快速唤醒运行,相比于使用直接使用Thread
明显更合理不少.
我们可以创建一个MySurfaceView
类如下
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.yxf.surfaceviewtest.WeakHandler;
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Handler.Callback {
public static final String TAG = "MySurfaceView";
public static final int MESSAGE_DRAW = 0;
private HandlerThread handlerThread;
private WeakHandler handler;
private volatile Path path = new Path();
public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
getHolder().addCallback(this);
}
public MySurfaceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MySurfaceView(Context context) {
this(context, null);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(event.getX(), event.getY());
refresh();
break;
case MotionEvent.ACTION_MOVE:
path.lineTo(event.getX(), event.getY());
refresh();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
private void refresh() {
Message message = Message.obtain();
message.what = MESSAGE_DRAW;
handler.removeMessages(MESSAGE_DRAW);
handler.sendMessage(message);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
handlerThread = new HandlerThread(TAG);
handlerThread.start();
handler = new WeakHandler(handlerThread.getLooper(), this);
refresh();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
handlerThread.quit();
handlerThread = null;
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_DRAW:
Canvas canvas = getHolder().lockCanvas();
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.GREEN);
paint.setStrokeWidth(10);
canvas.drawColor(Color.WHITE);
canvas.drawPath(path, paint);
getHolder().unlockCanvasAndPost(canvas);
return true;
}
return false;
}
}
如此在surfaceCreated
方法中初始化HandlerThread
和WeakHandler
对象,用WeakHandler
发消息的来刷新视图的方式完全优于直接用Thread
死循环的.
不过其实还可以对特殊情况再做下优化,比如说我们的视图中有多个SurfaceView,而且其刷新强度都不是特别高,则我们可以将多个视图共用同一个HandlerThread,改进后如下
package com.yxf.surfaceviewtest;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Handler.Callback {
public static final String TAG = "MySurfaceView";
public static final int MESSAGE_DRAW = 0;
private boolean isQuitHandlerThreadWhenDestroy = true;
private HandlerThread handlerThread;
private WeakHandler handler;
private volatile Path path = new Path();
public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
getHolder().addCallback(this);
}
public MySurfaceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MySurfaceView(Context context) {
this(context, null);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(event.getX(), event.getY());
refresh();
break;
case MotionEvent.ACTION_MOVE:
path.lineTo(event.getX(), event.getY());
refresh();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
private void refresh() {
Message message = Message.obtain();
message.what = MESSAGE_DRAW;
handler.sendMessage(message);
}
public WeakHandler setHandlerThread(HandlerThread thread) {
return setHandlerThread(thread, null);
}
public WeakHandler setHandlerThread(HandlerThread thread, Handler.Callback callback) {
if (thread == null) {
Log.w(TAG, "the HandlerThread set is null");
return null;
}
return initHandler(thread, callback,null);
}
public WeakHandler setHandlerThread(HandlerThread thread, Handler.Callback callback, WeakHandler handler) {
if (thread == null) {
Log.w(TAG, "the HandlerThread set is null");
return null;
}
return initHandler(thread, callback, handler);
}
private WeakHandler initHandler(HandlerThread thread, Handler.Callback callback, WeakHandler h) {
this.handlerThread = thread;
if (handlerThread.getLooper() == null) {
handlerThread.start();
}
if (callback == null) {
callback = this;
}
if (h == null) {
handler = new WeakHandler(thread.getLooper(), callback);
} else {
handler = h;
}
return handler;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (handlerThread == null) {
handlerThread = new HandlerThread(TAG);
initHandler(handlerThread, null,null);
isQuitHandlerThreadWhenDestroy = true;
}
refresh();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (isQuitHandlerThreadWhenDestroy) {
handlerThread.quit();
handlerThread = null;
}
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_DRAW:
Canvas canvas = getHolder().lockCanvas();
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.GREEN);
paint.setStrokeWidth(10);
canvas.drawColor(Color.WHITE);
canvas.drawPath(path, paint);
getHolder().unlockCanvasAndPost(canvas);
return true;
}
return false;
}
}
这样就可以实现多个SurfaceView共用同一个HandlerThread了.