安卓SurfaceView+HandlerThread的使用

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();
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值