1.SurfaceView简介
SurfaceView是继承自View的一个特殊视图,它可以在一个独立的线程中绘制图像。SurfaceView通过创建一个叫做Surface的窗口来实现图像的显示,这个Surface可以在一个新的子线程中进行绘制操作,从而避免了主线程被占用而导致的UI卡顿。
SurfaceView的主要特点:
① 可以在独立的线程中进行绘制操作,避免主线程的阻塞;
② 适用于实时更新图像的场景,如使用Camera预览、播放视频等;
③ 可以通过getHolder()方法获取SurfaceHolder对象,进而进行绘制操作。
2.HandlerThread简介
在安卓开发中,如果需要执行耗时操作,则可以开启子线程来完成,然而手动创建销毁线程又麻烦又消耗系统性能,因此可以使用线程池来完成。如果还需要在线程中使用Handler异步消息机制,或者需要实现子线程和子线程之间的通讯(Handler是主线程和子线程之间的通讯),那么就可以用HandlerThreaad。
HandlerThread是Google封装好的一个类,它的内部有自己的Looper对象,可以进行Loop轮询,用于执行多个耗时操作,而不需要多次开启线程,本质是使用Handler和Looper实现的。
HandlerThread的主要特点:
① HandlerThread本质上是一个线程类,它继承了Thread;
② HandlerThread有自己的内部Looper对象,可以进行looper循环;
③ 通过获取HandlerThread的looper对象传递给Handler对象,可以在handleMessage方法中执行异步任务;
④ 创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。
3.SurfaceView+HandlerThread的使用
通过上面的了解,我们知道SurfaceView可用于需要频繁更新ui的场景,其优势就在于可以在子线程中更新ui而达到不影响用户体验的效果,但是假如每一次更新ui都需要创建一个线程去更新ui,那么在频繁的ui更新场景中,是不好的,所以借助HandlerThread的特性来进行更好的处理。
1)首先我们创建一个简单SurfaceTestView,利用HandlerThread实现一个简单的文本绘制。
public class SurfaceTestView extends SurfaceView implements SurfaceHolder.Callback, Handler.Callback {
private final static String TAG = "SurfaceTestView";
private SurfaceHolder mHolder;
private Canvas mCanvas;//绘图的画布
private Paint mPaint;
private Rect mRect;
private HandlerThread mHandlerThread;
private Handler mHandler;
public SurfaceTestView(Context context) {
super(context);
initView();
}
public SurfaceTestView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SurfaceTestView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
public SurfaceTestView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView();
}
private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
mPaint = new Paint();
mRect = new Rect();
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper(), this);
update(0);
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
public void update(int msg) {
Message message = Message.obtain();
message.what = 0;
message.arg1 = msg;
if (mHandler != null) {
mHandler.removeMessages(0);
mHandler.sendMessage(message);
}
}
@Override
public boolean handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 0:
long startTime = System.currentTimeMillis();
mCanvas = mHolder.lockCanvas();
try {
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);// 清除画布
mCanvas.drawColor(0xFF4A8E4A);//背景颜色
mPaint.setAntiAlias(true);
mPaint.setTextSize(20);
mPaint.setColor(Color.BLACK);
String text = "当前:" + String.valueOf(msg.arg1);
mPaint.getTextBounds(text, 0, text.length(), mRect);
Paint.FontMetricsInt fm = mPaint.getFontMetricsInt();
mCanvas.drawText(text, (getWidth() - (float) mRect.width()) / 2, (float) getHeight() / 2 - (float) (fm.bottom + fm.top) / 2, mPaint);
// 确保ui刷新时间一致
long sleepTime = 100 - System.currentTimeMillis() + startTime;
if (sleepTime > 0) {
Thread.sleep(sleepTime);
}
Log.e(TAG, text);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
mHolder.unlockCanvasAndPost(mCanvas);
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
default:
break;
}
return false;
}
}
接下来我们对代码进行逐一分析。
①
@Override
public boolean handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 0:
long startTime = System.currentTimeMillis();
mCanvas = mHolder.lockCanvas();
try {
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);// 清除画布
mCanvas.drawColor(0xFF4A8E4A);//背景颜色
mPaint.setAntiAlias(true);
mPaint.setTextSize(20);
mPaint.setColor(Color.BLACK);
String text = "当前:" + String.valueOf(msg.arg1);
mPaint.getTextBounds(text, 0, text.length(), mRect);
Paint.FontMetricsInt fm = mPaint.getFontMetricsInt();
mCanvas.drawText(text, (getWidth() - (float) mRect.width()) / 2, (float) getHeight() / 2 - (float) (fm.bottom + fm.top) / 2, mPaint);
// 确保ui刷新时间一致
long sleepTime = 100 - System.currentTimeMillis() + startTime;
if (sleepTime > 0) {
Thread.sleep(sleepTime);
}
Log.e(TAG, text);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
mHolder.unlockCanvasAndPost(mCanvas);
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
default:
break;
}
return false;
}
这一部分代码中,通过handleMessage回调实现了一个居中文本的绘制。首先通过lockCanvas方法锁定了画布,在绘制完一个简单的居中文本后,调用unlockCanvasAndPost解锁并将绘制内容呈现出来。值得注意的是,为了保证用户体验,将每次刷新ui的时间过短的控制在了100ms,这是一种优化方式,旨在提高用户体验。
②
public void update(int msg) {
Message message = Message.obtain();
message.what = 0;
message.arg1 = msg;
if (mHandler != null) {
mHandler.removeMessages(0);
mHandler.sendMessage(message);
}
}
这段代码中,利用了Handler发送消息,从而实现ui的更新
③
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
mPaint = new Paint();
mRect = new Rect();
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper(), this);
update(0);
}
在SurfaceTestView 初次加载的时候,先创建 HandlerThread 对象,调用 HandlerThread 的 start() 方法启动线程,再创建一个 Handler 与 HandlerThread 的 Looper 绑定,这样mHandler 发送的所有消息都会交给mHandlerThread 的Looper进行处理,在绑定完成后调用update()方法对ui进行初次绘制,这样就的到的一个“当前:0”的简单文本。
2)接下来,我们可以在Activity中实现一个简单的计数器。
mSufView = (SurfaceTestView) findViewById(R.id.suf_test);
mThread = new Thread(new Runnable() {
@Override
public void run() {
int time = 0;
long startTime = System.currentTimeMillis();
while (mIsFinish) {
if (System.currentTimeMillis() - startTime > 1000) {
startTime = System.currentTimeMillis();
Log.e(TAG, "time:" + time);
mSufView.update(time++);
}
}
}
});
mThread.start();