Android图像渲染:SurfaceView与GLSurfaceView多格式图片加载实现方案

摘要: 关于Android开发渲染图片,详细介绍SurfaceView传统方案和GLSurfaceView硬件加载两种方案的具体实现,适配灰度图、BGR、YUV420、YUYV等多种格式图片

关键词:Android 图像渲染 SurfaceView GLSurfaceView

一、背景:
项目开发中需连续绘制多种格式图片(如BGR、YUV420、灰度图等),初始采用SurfaceView实现渲染,暴露以下关键问题:

  1. 帧率不稳定: SurfaceView虽支持子线程绘制,但在高频更新场景(如>30fps)下,帧率波动显著,单帧绘制耗时50-80ms,难以满足流畅性要求。
  2. CPU占用过高: 处理非RGB格式时(如YUV420→RGB转换),需依赖CPU格式转换,单帧图像消耗15ms以上,同时Bitmap内存复制进一步加剧负载,总CPU占用率达50%-70%。

GLSurfaceView通过OpenGL ES API将计算负载卸载至GPU,大大减少CPU占用,提高刷新率,现总结SurfaceView和GLSurfaceView两种渲染方案实现方案

二、SurfaceView
结合SurfaceView与OpenCV实现多格式图片加载的方案,主要通过SurfaceView的子线程绘制能力OpenCV的图片格式转换能力协同工作,实现步骤:

  1. OpenCV 环境初始化,支持跨格式转换

     // 加载图片之前完成初始化
     val initOpenCV = OpenCVLoader.initLocal()
     if (initOpenCV){
       // 初始化成功
     }
    
  2. SurfaceView 基础设置,监听Surface的生命周期

       mSurfaceView.holder.addCallback(object : SurfaceHolder.Callback {
              override fun surfaceCreated(p0: SurfaceHolder) {
      			//当Surface首次创建完成后调用
      			mHolder = p0  //保存SurfaceHolder,供后续使用
      			isDraw = true //设置变量控制绘制线程
      			startDrawThread()  // 启动绘制线程
              }
     
              override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
      			//Surface的尺寸或格式发生变化时调用
      			mHolder = p0  // 更新全局变量mHolder
              }
     
              override fun surfaceDestroyed(p0: SurfaceHolder) {
      			//当Surface被销毁前调用
      			isDraw = false
      			clearQueue()
              }
      })
    
  3. 创建绘制线程,通过独立线程避免 UI 阻塞
    3.1. 新建阻塞队列,使用LinkedBlockingQueue存储待绘制的Bitmap,控制内存占用

     var bitmapQueue = LinkedBlockingQueue<Bitmap>(1) // 大小为1的阻塞队列
     // 添加Bitmap到队列(队列满时自动丢弃最旧的帧)
     fun addBitmap(bitmap: Bitmap?) {
      	//剩余容量为0,丢弃队首的旧帧
          if (bitmapQueue.remainingCapacity() == 0) {
              val oldBitmap: Bitmap = bitmapQueue.poll()
              if (oldBitmap != null && !oldBitmap.isRecycled) {
                  oldBitmap.recycle() // 回收Bitmap资源
              }
          }
          bitmapQueue.offer(bitmap) // 非阻塞添加新帧
     }
     // 清理队列中的Bitmap资源
     fun clearQueue() {
          for (bitmap in bitmapQueue) {
              if (bitmap != null && !bitmap.isRecycled) {
                  bitmap.recycle()
              }
          }
          bitmapQueue.clear()
     }
    

    3.2. 绘制线程,不断获取Bitmap并绘制到SurfaceView上

      fun startDrawThread(){
          lifecycleScope.launch(Dispatchers.IO) {
              while (isDraw){
      			// 从队列获取Bitmap(阻塞直到有可用帧)
                  var drawBitmap  = bitmapQueue.take()
                  drawBitmap?.let {
                      if (!it.isRecycled){
                          var canvas:Canvas?=null
                          try {
                              canvas = mHolder?.lockCanvas() // 获取画布
                              canvas?.let {it1->
                                  val viewRect = Rect(0, 0, viewW, viewH)
                                  it1.drawBitmap(it, null, viewRect, imgPaint)
                              }
                          }catch (e:Exception){
                              e.printStackTrace()
                          }finally {
                              mHolder?.unlockCanvasAndPost(canvas) // 提交绘制内容
                              it.recycle() // 绘制完成后回收资源
                          }
                      }
                  }
              }
          }
      }
    

    关键说明

    • 线程安全
      1. surfaceCreated()中启动绘制线程
      2. surfaceDestroyed()中停止线程并清理资源
      3. 使用isDraw控制线程生命周期
    • 队列管理
      1. 队列大小设为1(根据需求可调整),避免内存堆积
      2. addBitmap()方法实现队列满时丢弃最旧帧并回收资源
      3. 绘制线程通过take()阻塞获取新帧,队列空时自动等待
    • 资源回收
      1. 绘制完成后立即recycle() Bitmap,减少内存占用
      2. surfaceDestroyed()中清理队列残留资源
      3. 添加前检查Bitmap有效性,避免操作已回收资源
  4. OpenCV格式转换,实现多种图像格式字节流(ByteArray)到Android Bitmap的转换
    4.1. 灰度图->Bitmap

      val grayMat = Mat(height, width, CvType.CV_8UC1).apply {
          put(0, 0, gray)
      }
      Imgproc.cvtColor(grayMat, rgbMat, Imgproc.COLOR_GRAY2RGB)
      val bitmap = Bitmap.createBitmap(rgbMat.cols(), rgbMat.rows(), Bitmap.Config.RGB_565)
      Utils.matToBitmap(rgbMat, bitmap)
    

    4.2. BGR->Bitmap

     val bgrMat = Mat(height, width, CvType.CV_8UC3).apply {
              put(0, 0, bgr)
      }
      Imgproc.cvtColor(bgrMat, rgbMat, Imgproc.COLOR_BG
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菠萝加点糖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值