Android SurfaceView 日常应用中应用开发用到的比较少,通常在一些视频播放器和游戏中用到,SurfaceView比起View来说
开发类似播放器的东西还是比较方便的,虽然说是View的子类,都是在onDraw( )里进行绘画和处理,View的流程是通过
Paint,绘画之后通过Canvas通过UI线程直接渲染到屏幕上,而SurfaceView则是双缓冲,这里简单说说所谓的双缓冲,意为两
个子线程分开处理,因为比如我们的动画在屏幕播放都是一帧一帧的,播放一帧,解析一帧,在播放,在解析,这样的做法明
显是不科学的,很容易早成UI主线程的阻塞,双缓冲就可以很好的解决这个问题,一个线程负责在后台把所有帧解析,之后再
由另一个帧执行整个解析之后的数据进行屏幕的渲染,这样就避免了线程的阻塞问题,当数据处理且执行完之后,在关闭线程
避免不必要的内存资源浪费
我写了如下2种情况处理的视频播放器对比:
因为我是在模拟器测试的,会涉及到SD卡得读写权限,所以我们需要添加如下权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/LinearLayout01" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/login_bg" android:orientation="vertical" > <RelativeLayout android:id="@+id/login_div" android:layout_width="300dp" android:layout_height="wrap_content" android:layout_margin="15dip" android:layout_weight="0.90" android:background="@drawable/login_background" android:padding="15dip" > <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> <Button android:id="@+id/btn_play" android:layout_width="fill_parent" android:layout_height="25dp" android:layout_marginTop="10dp" android:background="@drawable/button_style" android:gravity="center" android:text="Play" /> <Button android:id="@+id/btn_pause" android:layout_width="fill_parent" android:layout_height="25dp" android:layout_marginTop="10dp" android:background="@drawable/button_style" android:gravity="center" android:text="Pause" /> <Button android:id="@+id/btn_stop" android:layout_width="fill_parent" android:layout_height="25dp" android:layout_marginTop="10dp" android:background="@drawable/button_style" android:gravity="center" android:text="Stop" /> </LinearLayout>
package com.example.engineerjspsurfaceview; /** * SurfaceView * @author Engineer-Jsp * @date 2014.11.11 * */ import android.media.AudioManager; import android.media.MediaPlayer; import android.os.Bundle; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.app.Activity; public class MainActivity extends Activity implements SurfaceHolder.Callback{ private static final String TAG = "MainActivity"; SurfaceHolder surfaceholder; MediaPlayer player; SurfaceView surfaceview; Button play,pause,stop; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView();//初始化 surfaceholder = surfaceview.getHolder();//SurfaceHolder是SurfaceView的控制接口 surfaceholder.addCallback(this);//因为这个类实现了SurfaceHolder.Callback接口,所以回调参数直接this //设置surfaceview不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到用户面前,由MediaPlayer对象来完成 surfaceholder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//高版本此方法已过时 //播放 play.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { player.start(); } }); //暂停 pause.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { player.pause(); } }); //结束播放 stop.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { player.stop(); } }); } // surface对象改变方法 @Override public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { Log.d(TAG, "surfaceChanged"); } // surface对象生成 @Override public void surfaceCreated(SurfaceHolder arg0) { Log.d(TAG, "surfaceCreated"); //必须在surface创建后才能初始化MediaPlayer,否则不会显示图像 player = new MediaPlayer(); player.setAudioStreamType(AudioManager.STREAM_MUSIC);//流媒体类型 player.setDisplay(surfaceholder);//显示 //设置显示视频显示在SurfaceView上 try { player.setDataSource("/sdcard/video.avi");//媒体文件地址 player.prepare();//缓冲 } catch (Exception e) { e.printStackTrace(); } } // surface对象销毁 @Override public void surfaceDestroyed(SurfaceHolder arg0) { Log.d(TAG, "surfaceDestroyed"); } //初始化方法 public void initView(){ surfaceview = (SurfaceView)findViewById(R.id.surfaceView); pause = (Button)findViewById(R.id.btn_pause); play = (Button)findViewById(R.id.btn_play); stop = (Button)findViewById(R.id.btn_stop); } //Activity销毁 @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy"); if(player.isPlaying()){ player.stop(); } //Activity销毁时停止播放,释放资源。不做这个操作,即使退出还是能听到视频播放的声音 player.release(); } @Override protected void onStart() { super.onStart(); Log.d(TAG, "onStart"); } @Override protected void onStop() { super.onStop(); Log.d(TAG, "onStop"); } @Override protected void onPause() { super.onPause(); Log.d(TAG, "onPause"); } @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume"); } @Override protected void onRestart() { super.onRestart(); Log.d(TAG, "onRestart"); } }
启动播放器之后的生命周期函数图:
因为我没有在onCreate()写打印语句,所以没有onCreate打印结果,可以看到Activity优先于Surface创建,还有就是第66行代码,为什么要在这里进行媒体播放器创建呢,因为在SurfaceView对象再未渲染之前,创建播放器的话,视频播放器将不会播放任何视频数据,所以应该放在Surface对象被创建渲染函数里才是正确的
执行效果:
布局不是很美观,因为是供测试用的,暂且不做美化,点击play视频开始播放,Pause暂停,Stop结束,可以看上图我专门用红框标记了那个房子,也就是Home键,当我们点击播放器的暂停时,在执行Home键,那会产生什么结果呢?
下面我给大家继续演示,当我们点击HOME键之后,生命周期如下:
当我们点击HOME键之后,活动被暂停,Surface对象被销毁,活动结束,当我们在次打开应用程序,发现屏幕是黑的,什么也没有,这不是BUG,这跟Surface的生命周期有关,后面将会详细介绍产生这种结果的原因和解决办法,下面我们再来看看重新进入应用程序的生命周期函数图
可以清楚的看到,当再次进入程序,活动被重启了,活动开始,活动继续,Surface对象重新被创建,但是应用程序没有任何动画,这是为什么呢?
原因:主要是由于Android的回收机制,为了防止程序的内存问题,Android会把比较耗内存的且在当前活动不可见得操作回收掉,因为SurfaceView是特别耗内存的,且我们按下HOME键之后,活动被暂停结束,SurfaceView也被销毁了,变为不可见,Android就将其回收掉了,因为之前的Surface对象播放的时候呗暂停了,之后又被销毁,所以在此进入程序,我们是不会看到动画的,要怎么解决这个BUG呢?这里我们需要用到Surface的生命周期函数来处理
SurfaceView更进版代码:
package com.example.engineerjspsurfaceview; /** * SurfaceView更进版 * @author Engineer-Jsp * @date 2014.11.11 * */ import java.io.File; import java.io.FileInputStream; import android.app.Activity; import android.media.AudioManager; import android.media.MediaPlayer; import android.os.Bundle; import android.os.Environment; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.Button; import android.widget.EditText; public class EngineerJspSurfaceView extends Activity{ private static final String TAG = "EngineerJspSurfaceView";//Logcat 过滤标签 SurfaceHolder surfaceholder;//SurfaceHolder对象 MediaPlayer player;//播放器对象 EditText filepath;//路径输入框 SurfaceView surfaceview;//SurfaceView对象 Button play,pause,stop,replay;//按钮组件 String filename;//绝对路径 int lastnode;//Video最后保存帧 @Override //Activity创建 protected void onCreate(Bundle savedInstanceState) { //Activity的当前状态 super.onCreate(savedInstanceState); //实例化xml setContentView(R.layout.main); //需要初始化的组件,方法调用 initView(); } /** * 自定义按钮事件 * */ class EngineerJspSurfaceListenner implements View.OnClickListener{ @Override public void onClick(View view) { //Environment.MEDIA_MOUNTED sd卡在手机上正常使用状态 //Environment.MEDIA_UNMOUNTED 用户手工到手机设置中卸载sd卡之后的状态 //Environment.MEDIA_REMOVED 用户手动卸载,然后将sd卡从手机取出之后的状态 //Environment.MEDIA_BAD_REMOVAL 用户未到手机设置中手动卸载sd卡,直接拨出之后的状态 //Environment.MEDIA_SHARED 手机直接连接到电脑作为u盘使用之后的状态 //Environment.MEDIA_CHECKINGS 手机正在扫描sd卡过程中的状态 //在做android开发对sd操作时,最好是sd卡处于Environment.MEDIA_MOUNTED状态时,对sd卡上的文件进行操作,其他状态不宜进行操作 if(Environment.getExternalStorageState()==Environment.MEDIA_UNMOUNTED){ Log.d(TAG, "SD卡异常..."); } //视频文件路径,EditText中获取 filename = filepath.getText().toString(); Log.d(TAG, "Video路径:"+filename); switch (view.getId()) { //开始播放 case R.id.play: play(); break; //结束播放 case R.id.stop: if(player.isPlaying()){ player.stop(); } break; //暂停/继续播放 case R.id.pause: if(player.isPlaying()){ player.pause(); }else{ player.start(); } break; //重头播放 case R.id.replay: if(player.isPlaying()){ player.seekTo(0); }else{ play(); } break; } } } /** * 自定义接口 * 启动自定义接口类对象(被监听端) * */ class EngineerJspSurface implements SurfaceHolder.Callback{ //Surface对象改变 @Override public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { Log.d(TAG, "surfaceChanged"); } //Surface对象创建 @Override public void surfaceCreated(SurfaceHolder arg0) { Log.d(TAG, "surfaceCreated"); if(lastnode>0&&null!=filename){//判断二次Surface渲染对象最后帧的值,及文件路径,(帧值保存过,文件存在) play();//启动,渲染 player.seekTo(lastnode);//跳至lastnode保存的帧为起始的帧 lastnode=0;//重新初始化,方便下次Surface渲染时存getCurrentPosition的帧值 } } //Surface对象销毁 @Override public void surfaceDestroyed(SurfaceHolder arg0) { Log.d(TAG, "surfaceDestroyed"); if(player.isPlaying()){//判断当前播放器状态,播放中 lastnode = player.getCurrentPosition();//取当前帧 player.stop();//kill掉 } } } //Activity销毁 @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy"); } //Activity开始 @Override protected void onStart() { super.onStart(); Log.d(TAG, "onStart"); } //Activity停止 @Override protected void onStop() { super.onStop(); Log.d(TAG, "onStop"); } //Activity暂停 @Override protected void onPause() { super.onPause(); Log.d(TAG, "onPause"); } //Activity继续 @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume"); } //Activity重启 @Override protected void onRestart() { super.onRestart(); Log.d(TAG, "onRestart"); } //播放方法 public void play(){ try { File file = new File(Environment.getExternalStorageDirectory(),filename);//取文件目录 player.reset();//重置初始化状态 player.setAudioStreamType(AudioManager.STREAM_MUSIC);//流媒体类型 player.setDisplay(surfaceview.getHolder());//影片以SurfceView播放 Log.d(TAG, "Video绝对路径:"+file.getAbsolutePath()); FileInputStream fis = new FileInputStream(filename); player.setDataSource(fis.getFD());//媒体路径 player.prepare();//缓冲 player.start();//播放 } catch (Exception e) { Log.d(TAG, e.toString());//打印捕获异常 e.printStackTrace(); } } //初始化/实例化组件 public void initView(){ filepath = (EditText)findViewById(R.id.filepath); surfaceview = (SurfaceView)findViewById(R.id.surfaceView); play = (Button)findViewById(R.id.play); pause= (Button)findViewById(R.id.pause); stop = (Button)findViewById(R.id.stop); replay=(Button)findViewById(R.id.replay); surfaceview.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//设置surfaceview不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到用户面前,MediaPlayer来完成 surfaceview.getHolder().addCallback(new EngineerJspSurface());//对Surface对象实时监听 player = new MediaPlayer();//创建播放器 EngineerJspSurfaceListenner listenner = new EngineerJspSurfaceListenner();//监听器对象 play.setOnClickListener(listenner); pause.setOnClickListener(listenner); stop.setOnClickListener(listenner); replay.setOnClickListener(listenner); } }
XML布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/LinearLayout01" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/login_bg" android:orientation="vertical" > <EditText android:id="@+id/filepath" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="Input your video Filepath" /> <RelativeLayout android:id="@+id/login_div" android:layout_width="300dp" android:layout_height="wrap_content" android:layout_margin="15dip" android:layout_weight="0.90" android:background="@drawable/login_background" android:padding="15dip" > <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> <Button android:id="@+id/play" android:layout_width="fill_parent" android:layout_height="25dp" android:layout_marginTop="10dp" android:background="@drawable/button_style" android:gravity="center" android:text="Play" /> <Button android:id="@+id/pause" android:layout_width="fill_parent" android:layout_height="25dp" android:layout_marginTop="10dp" android:background="@drawable/button_style" android:gravity="center" android:text="Pause" /> <Button android:id="@+id/stop" android:layout_width="fill_parent" android:layout_height="25dp" android:layout_marginTop="10dp" android:background="@drawable/button_style" android:gravity="center" android:text="Stop" /> <Button android:id="@+id/replay" android:layout_width="fill_parent" android:layout_height="25dp" android:layout_marginTop="10dp" android:background="@drawable/button_style" android:gravity="center" android:text="Replay" /> </LinearLayout>
Surface、SurfaceView、SurfaceHolder、SurfaceHolder.Callback接口、Surface生命周期函数、播放器逻辑
好了,内容就这么多,好好消化,试试自己动手写写,有助于加深印象