SurfaceView,TextureView,SurfaceTexture相关

首先我们知道View类如果需要更新视图,必须我们主动的去调用invalidate()或者postInvalidate()方法来再走一次onDraw()完成更新。但是呢,Android系统规定屏幕的刷新间隔为16ms,如果这个View在16ms内更新完毕了,就不会卡顿,但是如果逻辑操作太多,16ms内没有更新完毕,剩下的操作就会丢到下一个16ms里去完成,这样就会造成UI线程的阻塞,造成View的运动过程掉帧,自然就会卡顿了。
所以这些原因也就促使了SurfaceView的存在。比如一个Camera,它有可能相当频繁的有更新画面的需求。

(1)简单介绍

(A)SurfaceView

它继承自View,因此它本质上是一个View。但与普通View不同的是,它有自己的Surface。
一般的Activity包含的多个View会组成View hierachy的树形结构,只有最顶层的DecorView,也就是根结点视图,才是对WMS可见的。这个DecorView在WMS中有一个对应的WindowState。相应地,在SF中有对应的Layer。

这样的好处是对这个Surface的渲染可以放到单独线程去做,它不会影响主线程对事件的响应。但它也有缺点,因为这个Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中,一些View中的特性也无法使用。

  • SurfaceView中采用了双缓冲机制,保证了UI界面的流畅性,同时 SurfaceView 不在主线程中绘制,而是另开辟一个线程去绘制,所以它不妨碍UI线程;
  • View底层没有双缓冲机制,SurfaceView有;
  • view主要适用于主动更新,而SurfaceView适用与被动的更新,如频繁的刷新;
  • view会在主线程中去更新UI,而SurfaceView则在子线程中刷新;
  • SurfaceView的内容不在应用窗口上,所以不能使用变换(平移、缩放、旋转等)。也难以放在ListView或者ScrollView中,不能使用UI控件的一些特性比如View.setAlpha();

(B)TextureView

与SurfaceView相比,TextureView并没有创建一个单独的Surface用来绘制,因此可以和其它普通View一样进行移动,旋转,缩放,动画等变化。另外,Textureview必须在硬件加速开启的窗口中;

从类图中可以看到,TextureView继承自View,它与其它的View一样在View hierachy中管理与绘制。TextureView重载了draw()方法,其中主要SurfaceTexture中收到的图像数据作为纹理更新到对应的HardwareLayer中。

SurfaceTexture.OnFrameAvailableListener用于通知TextureView内容流有新图像到来。TextureView.SurfaceTextureListener接口用于让TextureView的使用者知道SurfaceTexture已准备好,这样就可以把SurfaceTexture交给相应的内容源。

  • 支持移动、旋转、缩放等动画,支持截图;
  • 必须在硬件加速的窗口中使用,占用内存比SurfaceView高,在5.0以前在主线程渲染,5.0以后有单独的渲染线程;

(C)SurfaceTexture

SurfaceTexture用来捕获视频流(stream)中的图像帧(image frame)的,视频流可以是相机预览或者视频解码数据。SurfaceTexture可以作为android.hardware.camera2、MediaCodec、MediaPlayer、和 VideoDecode这些类的目标视频数据输出对象。

SurfaceTexture和SurfaceView不同的是,它对图像流的处理并不直接显示,而是转为OpenGL的外部纹理,因此可用于图像流数据的二次处理(如Camera滤镜,桌面特效等)。比如Camera的预览数据,可以通过SurfaceTexture交给TextureView作为View heirachy中的一个硬件加速层来显示。首先,SurfaceTexture从图像流(来自Camera预览,视频解码,GL绘制场景等)中获得帧数据,当SurfaceTexture中有数据帧更新时,SurfaceTexture.OnFrameAvailableListener这个监听接口将会有回调,此时可以调用updateTexImage()方法从视频流数据中更新当前数据帧。并通过SurfaceTexture所绑定的OpenGL纹理对象来对其进行一些图像处理操作。

(2)API相关

(A)Surface

//android.view.Surface

ROTATION_0
ROTATION_90
ROTATION_180
ROTATION_270

Surface(SurfaceTexture surfaceTexture)

Canvas	lockCanvas(Rect inOutDirty)
Canvas	lockHardwareCanvas()
void	unlockCanvasAndPost(Canvas canvas)

(B)SurfaceView

//android.view.View
// 	   ↳	android.view.SurfaceView

void	draw(Canvas canvas)
SurfaceHolder	getHolder()
void	setZOrderMediaOverlay(boolean isMediaOverlay)
void	setZOrderOnTop(boolean onTop)

onMeasure(int widthMeasureSpec, int heightMeasureSpec)

(C)SurfaceHolder

//android.view.SurfaceHolder

SurfaceHolder.Callback
	abstract void	surfaceChanged(SurfaceHolder holder, int format, int width, int height)
	abstract void	surfaceCreated(SurfaceHolder holder)
	abstract void	surfaceDestroyed(SurfaceHolder holder)

abstract void	addCallback(SurfaceHolder.Callback callback)
abstract Surface	getSurface()
abstract Canvas	lockCanvas()
default Canvas	lockHardwareCanvas()
abstract void	removeCallback(SurfaceHolder.Callback callback)
abstract void	setFixedSize(int width, int height)
abstract void	setFormat(int format)
abstract void	setKeepScreenOn(boolean screenOn)
abstract void	setSizeFromLayout()
abstract void	unlockCanvasAndPost(Canvas canvas)

(D)TextureView

//android.view.View
// 	   ↳	android.view.TextureView

TextureView.SurfaceTextureListener
	abstract void	onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)
	abstract boolean	onSurfaceTextureDestroyed(SurfaceTexture surface)
	abstract void	onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)
	abstract void	onSurfaceTextureUpdated(SurfaceTexture surface)

final void	draw(Canvas canvas)
Bitmap	getBitmap(int width, int height)
Bitmap	getBitmap()
Bitmap	getBitmap(Bitmap bitmap)

SurfaceTexture	getSurfaceTexture()
void	setSurfaceTexture(SurfaceTexture surfaceTexture)
TextureView.SurfaceTextureListener	getSurfaceTextureListener()
void	setSurfaceTextureListener(TextureView.SurfaceTextureListener listener)

Matrix	getTransform(Matrix transform)
void	setTransform(Matrix transform)

Canvas	lockCanvas()
void	unlockCanvasAndPost(Canvas canvas)

final void	onDraw(Canvas canvas)
void	onSizeChanged(int w, int h, int oldw, int oldh)
void	onVisibilityChanged(View changedView, int visibility)

(E)SurfaceTexture

//android.graphics.SurfaceTexture

SurfaceTexture.OnFrameAvailableListener
	abstract void	onFrameAvailable(SurfaceTexture surfaceTexture)

long	getTimestamp()
void	getTransformMatrix(float[] mtx)
void	setDefaultBufferSize(int width, int height)
void	setOnFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener listener)
void	updateTexImage()
void	releaseTexImage()

(3)Demo

(A)自定义PreviewSurfaceView

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.Display;
import android.view.SurfaceView;
import android.view.WindowManager;

public class PreviewSurfaceView extends SurfaceView {
    private static final double ASPECT_TOLERANCE = 0.03;
    private double mAspectRatio = 0.0;
    private int mPreviewWidth = 0;
    private int mPreviewHeight = 0;

    public PreviewSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setAspectRatio(double aspectRatio) {
        if (mAspectRatio != aspectRatio) {
            mAspectRatio = aspectRatio;
            requestLayout();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int previewWidth = MeasureSpec.getSize(widthMeasureSpec);
        int previewHeight = MeasureSpec.getSize(heightMeasureSpec);
        boolean widthLonger = previewWidth > previewHeight;
        int longSide = (widthLonger ? previewWidth : previewHeight);
        int shortSide = (widthLonger ? previewHeight : previewWidth);
        if (mAspectRatio > 0) {
            double fullScreenRatio = findFullscreenRatio(getContext());
            if (Math.abs((mAspectRatio - fullScreenRatio)) <= ASPECT_TOLERANCE) {
                // full screen preview case
                if (longSide < shortSide * mAspectRatio) {
                    longSide = Math.round((float) (shortSide * mAspectRatio) / 2) * 2;
                } else {
                    shortSide = Math.round((float) (longSide / mAspectRatio) / 2) * 2;
                }
            } else {
                // standard (4:3) preview case
                if (longSide > shortSide * mAspectRatio) {
                    longSide = Math.round((float) (shortSide * mAspectRatio) / 2) * 2;
                } else {
                    shortSide = Math.round((float) (longSide / mAspectRatio) / 2) * 2;
                }
            }
        }
        if (widthLonger) {
            previewWidth = longSide;
            previewHeight = shortSide;
        } else {
            previewWidth = shortSide;
            previewHeight = longSide;
        }
        boolean originalPreviewIsLandscape = (mPreviewWidth > mPreviewHeight);
        boolean configurationIsLandscape =
               (getContext().getResources().getConfiguration().orientation ==
               Configuration.ORIENTATION_LANDSCAPE);
       	 // if configuration is changed, swap to view's configuration
		//  if (originalPreviewIsLandscape != configurationIsLandscape) {
		//  int originalPreviewWidth = previewWidth;
		// previewWidth = previewHeight;
		// previewHeight = originalPreviewWidth;
		// }
        setMeasuredDimension(previewWidth, previewHeight);
    }

    protected boolean isFullScreenPreview(double aspectRatio) {
        double fullScreenRatio = findFullscreenRatio(getContext());
        if (Math.abs((aspectRatio - fullScreenRatio)) <= ASPECT_TOLERANCE) {
            return true;
        } else {
            return false;
        }
    }

    private static double findFullscreenRatio(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        Point point = new Point();
        display.getRealSize(point);

        double fullscreen;
        if (point.x > point.y) {
            fullscreen = (double) point.x / point.y;
        } else {
            fullscreen = (double) point.y / point.x;
        }
        return fullscreen;
    }
}

(B)自定义PreviewTextureView

import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.Display;
import android.view.TextureView;
import android.view.WindowManager;

public class PreviewTextureView extends TextureView {

    private static final double ASPECT_TOLERANCE = 0.03;
    private double mAspectRatio = 0.0;

    public PreviewTextureView(Context context) {
        this(context, null);
    }

    public PreviewTextureView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PreviewTextureView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setAspectRatio(double aspectRatio) {
        if (mAspectRatio != aspectRatio) {
            mAspectRatio = aspectRatio;
            requestLayout();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int previewWidth = MeasureSpec.getSize(widthMeasureSpec);
        int previewHeight = MeasureSpec.getSize(heightMeasureSpec);
        boolean widthLonger = previewWidth > previewHeight;
        int longSide = (widthLonger ? previewWidth : previewHeight);
        int shortSide = (widthLonger ? previewHeight : previewWidth);
        if (mAspectRatio > 0) {
            double fullScreenRatio = findFullscreenRatio(getContext());
            if (Math.abs((mAspectRatio - fullScreenRatio)) <= ASPECT_TOLERANCE) {
                // full screen preview case
                if (longSide < shortSide * mAspectRatio) {
                    longSide = Math.round((float) (shortSide * mAspectRatio) / 2) * 2;
                } else {
                    shortSide = Math.round((float) (longSide / mAspectRatio) / 2) * 2;
                }
            } else {
                // standard (4:3) preview case
                if (longSide > shortSide * mAspectRatio) {
                    longSide = Math.round((float) (shortSide * mAspectRatio) / 2) * 2;
                } else {
                    shortSide = Math.round((float) (longSide / mAspectRatio) / 2) * 2;
                }
            }
        }
        if (widthLonger) {
            previewWidth = longSide;
            previewHeight = shortSide;
        } else {
            previewWidth = shortSide;
            previewHeight = longSide;
        }
        setMeasuredDimension(previewWidth, previewHeight);
    }

    private static double findFullscreenRatio(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        Point point = new Point();
        display.getRealSize(point);

        double fullscreen;
        if (point.x > point.y) {
            fullscreen = (double) point.x / point.y;
        } else {
            fullscreen = (double) point.y / point.x;
        }
        return fullscreen;
    }
}

(C)SurfaceView Demo

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>
private void getScreenParams() {
        DisplayMetrics dm = new DisplayMetrics();
        // 获取屏幕信息
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        screenWidth = dm.widthPixels;
        screenHeight = dm.heightPixels;
    }

protected void initView() {
        surfaceHolder = surfaceview.getHolder();
        surfaceHolder.setKeepScreenOn(true);
        surfaceHolder.addCallback(new SurfaceViewCallback());
        // 调整surfaceView的大小
        ViewGroup.LayoutParams params = surfaceview.getLayoutParams();
        params.width = screenWidth;
        params.height = screenHeight;
        surfaceview.setLayoutParams(params);
    }

private class SurfaceViewCallback implements SurfaceHolder.Callback {

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
			mCamera.setPreviewDisplay(holder);
             mCamera.startPreview();
        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
        
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {

        }
    }

(D)TextureView Demo

public class LiveCameraActivity extends Activity implements TextureView.SurfaceTextureListener {
      private Camera mCamera;
      private TextureView mTextureView;

      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);

          mTextureView = new TextureView(this);
          mTextureView.setSurfaceTextureListener(this);

          setContentView(mTextureView);
      }

      public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
          mCamera = Camera.open();

          try {
              mCamera.setPreviewTexture(surface);
              mCamera.startPreview();
          } catch (IOException ioe) {
              // Something bad happened
          }
      }

      public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

      }

      public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
          mCamera.stopPreview();
          mCamera.release();
          return true;
      }

      public void onSurfaceTextureUpdated(SurfaceTexture surface) {

      }
  }

(4)总结

  • SurfaceView是一个有自己独立Surface的View, 它的渲染可以放在单独线程而不是主线程中, 其缺点是不能做变形和动画;
  • SurfaceTexture可以用作非直接输出的内容流,这样就提供二次处理的机会。与SurfaceView直接输出相比,这样会有若干帧的延迟;
  • TextureView是一个可以把内容流作为外部纹理输出在上面的View, 它本身需要是一个硬件加速层;
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

雪舞飞影

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

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

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

打赏作者

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

抵扣说明:

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

余额充值