我们知道View是通过刷新来重绘视图,系统通过发出VSSYNC
信号来进行屏幕的重绘,刷新的时间间隔是16ms
,如果我们可以在16ms以内将绘制工作完成,则没有任何问题,如果我们绘制过程逻辑很复杂,并且我们的界面更新还非常频繁,这时候就会造成界面的卡顿,影响用户体验,为此Android提供了SurfaceView
来解决这一问题
View
和SurfaceView
的区别:
1 . View适用于主动更新的情况,而SurfaceView则适用于被动更新的情况,比如频繁刷新界面。
2 . View在主线程中对页面进行刷新,而SurfaceView则开启一个子线程来对页面进行刷新。
3 . View在绘图时没有实现双缓冲机制,SurfaceView在底层机制中就实现了双缓冲机制。
这摘录了一段网上对于双缓冲技术的介绍
双缓冲技术是游戏开发中的一个重要的技术。当一个动画争先显示时,程序又在改变它,前面还没有显示完,程序又请求重新绘制,这样屏幕就会不停地闪烁。而双缓冲技术是把要处理的图片在内存中处理好之后,再将其显示在屏幕上。双缓冲主要是为了解决 反复局部刷屏带来的闪烁。把要画的东西先画到一个内存区域里,然后整体的一次性画出来。
注意:SurfaceView的内容不在应用窗口上,所以不能使用变换(平移、缩放、旋转等)。也难以放在ListView或者ScrollView中,不能使用UI控件的一些特性比如View.setAlpha()
下面我们通过两个小案例来展示SurfaceView的使用。先放上效果图
示例代码:
public class SurfaceViewSinFun extends SurfaceView implements SurfaceHolder.Callback, Runnable {
? ? private SurfaceHolder mSurfaceHolder;
? ? //绘图的Canvas
? ? private Canvas mCanvas;
? ? //子线程标志位
? ? private boolean mIsDrawing;
? ? private int x = 0, y = 0;
? ? private Paint mPaint;
? ? private Path mPath;
? ? public SurfaceViewSinFun(Context context) {
? ? ? ? this(context, null);
? ? }
?
? ? public SurfaceViewSinFun(Context context, AttributeSet attrs) {
? ? ? ? this(context, attrs, 0);
? ? }
?
? ? public SurfaceViewSinFun(Context context, AttributeSet attrs, int defStyleAttr) {
? ? ? ? super(context, attrs, defStyleAttr);
? ? ? ? mPaint = new Paint();
? ? ? ? mPaint.setColor(Color.BLACK);
? ? ? ? mPaint.setStyle(Paint.Style.STROKE);
? ? ? ? mPaint.setAntiAlias(true);
? ? ? ? mPaint.setStrokeWidth(5);
? ? ? ? mPath = new Path();
? ? ? ? //路径起始点(0, 100)
? ? ? ? mPath.moveTo(0, 100);
? ? ? ? initView();
? ? }
?
? ? @Override
? ? public void surfaceCreated(SurfaceHolder holder) {
? ? ? ? mIsDrawing = true;
? ? ? ? new Thread(this).start();
? ? }
?
? ? @Override
? ? public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
?
? ? }
?
? ? @Override
? ? public void surfaceDestroyed(SurfaceHolder holder) {
? ? ? ? mIsDrawing = false;
? ? }
?
? ? @Override
? ? public void run() {
? ? ? ? while (mIsDrawing){
? ? ? ? ? ? drawSomething();
? ? ? ? ? ? x += 1;
? ? ? ? ? ? y = (int)(100 * Math.sin(2 * x * Math.PI / 180) + 400);
? ? ? ? ? ? //加入新的坐标点
? ? ? ? ? ? mPath.lineTo(x, y);
? ? ? ? }
? ? }
?
? ? private void drawSomething() {
? ? ? ? try {
? ? ? ? ? ? //获得canvas对象
? ? ? ? ? ? mCanvas = mSurfaceHolder.lockCanvas();
? ? ? ? ? ? //绘制背景
? ? ? ? ? ? mCanvas.drawColor(Color.WHITE);
? ? ? ? ? ? //绘制路径
? ? ? ? ? ? mCanvas.drawPath(mPath, mPaint);
? ? ? ? }catch (Exception e){
?
? ? ? ? }finally {
? ? ? ? ? ? if (mCanvas != null){
? ? ? ? ? ? ? ? //释放canvas对象并提交画布
? ? ? ? ? ? ? ? mSurfaceHolder.unlockCanvasAndPost(mCanvas);
? ? ? ? ? ? }
? ? ? ? }
? ? }
?
? ? /**
? ? ?* 初始化View
? ? ?*/
? ? private void initView(){
? ? ? ? mSurfaceHolder = getHolder();
? ? ? ? mSurfaceHolder.addCallback(this);
? ? ? ? setFocusable(true);
? ? ? ? setKeepScreenOn(true);
? ? ? ? setFocusableInTouchMode(true);
? ? }
}
View:必须在UI的主线程中更新画面,用于被动更新画面。
SurfaceView:UI线程和子线程中都可以。在一个新启动的线程中重新绘制画面,主动更新画面。
通常View更新的时候都会调用ViewRootImpl中的performXXX()方法,在该方法中会首先使用checkThread()检查是否当前更新位于主线线程;
SurfaceView提供了专门用于绘制的Surface,可以通过SurfaceView来控制Surface的格式和尺寸,SurfaceView更新就不需要考虑线程的问题,它既可以在子线程更新,也可以在主线程更新。
?
TextureView 适用于 Android 4.0 和之后的版本,在很多的情况下可以顺便作为 SurfaceView 的替代品来使用。TextureView 的行为更像传统的 View,可以对绘制在它上面的内容实现动画和变换。但要求运行它的环境是硬件加速的,这可能会导致某些应用程序的兼容性问题。应用程序在 SDK 为 11或以上的版本时,默认启动了硬件加速。(如果需要禁用硬件加速可在 AndroidManifest.xml 文件中的 <activity> 或整个 <application> 标签中添加 android:hardwareAccelerated="false",即可。)
public class TextureViewActivity extends Activity implements TextureView.SurfaceTextureListener {
? ? private MediaPlayer mediaPlayer;
? ? @Bind(R.id.texture)
? ? TextureView textureView;
? ? @Bind(R.id.video_image)
? ? ImageView video_image;
? ? private Surface surface;
? ? @Override
? ? protected void onCreate(@Nullable Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? setContentView(R.layout.activity_surface);
? ? ? ? ButterKnife.bind(this);
? ? ? ? textureView.setSurfaceTextureListener(this);//设置监听,实现四个方法
? ? }
? ? @Override
? ? public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
? ? ? ? System.out.println("onSurfaceTextureAvailable被执行");
? ? ? ? surface = new Surface(surfaceTexture);
? ? ? ? new PlayerVideo().start();
? ? }
? ? @Override
? ? public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
? ? ? ? System.out.println("onSurfaceTextureSizeChanged被执行");
? ? }
? ? @Override
? ? public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
? ? ? ? System.out.println("onSurfaceTextureDestroyed被执行");
? ? ? ? surfaceTexture=null;
? ? ? ? surface=null;
? ? ? ? mediaPlayer.stop();
? ? ? ? mediaPlayer.release();
? ? ? ? return true;
? ? }
? ? @Override
? ? public void onSurfaceTextureUpdated(SurfaceTexture surface) {
? ? }
? ? class PlayerVideo extends Thread{
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? MPermissionUtils.requestPermissionsResult(TextureViewActivity.this, 1, new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, new MPermissionUtils.OnPermissionListener() {
? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? public void onPermissionGranted() {
? ? ? ? ? ? ? ? ? ? File file=new File(Environment.getExternalStorageDirectory()+"/tv.mp4");
? ? ? ? ? ? ? ? ? ? if (!file.exists()){
? ? ? ? ? ? ? ? ? ? ? ? copyFile();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? mediaPlayer=new MediaPlayer();
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? mediaPlayer.setDataSource(file.getAbsolutePath());
? ? ? ? ? ? ? ? ? ? ? ? mediaPlayer.setSurface(surface);
? ? ? ? ? ? ? ? ? ? ? ? mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
? ? ? ? ? ? ? ? ? ? ? ? mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
? ? ? ? ? ? ? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? ? ? ? ? ? ? public void onPrepared(MediaPlayer mp) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? video_image.setVisibility(View.GONE);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? mediaPlayer.start();
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? });
? ? ? ? ? ? ? ? ? ? ? ? mediaPlayer.prepareAsync();
? ? ? ? ? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? public void onPermissionDenied() {
? ? ? ? ? ? ? ? ? ? MPermissionUtils.showTipsDialog(TextureViewActivity.this);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? ? ? }
? ? }
?
? ? ? ?TextureView和SurfaceView都是继承自View类的,但是TextureView在Andriod4.0之后的API中才能使用。SurfaceView可以通过SurfaceHolder.addCallback方法在子线程中更新UI,TextureView则可以通过TextureView.setSurfaceTextureListener在子线程中更新UI,个人认为能够在子线程中更新UI是上述两种View相比于View的最大优势。
? ? ? ?但是,两者更新画面的方式也有些不同,由于SurfaceView的双缓冲功能,可以是画面更加流畅的运行,但是由于其holder的存在导致画面更新会存在间隔,并且,由于holder的存在,SurfaceView也不能进行像View一样的setAlpha和setRotation方法,但是对于一些类似于坦克大战等需要不断告诉更新画布的游戏来说,SurfaceView绝对是极好的选择。但是比如视频播放器或相机应用的开发,TextureView则更加适合。
?