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"/>
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" >
<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");
}
}
在活动里,我分别把生命周期函数全部写了打印测试语句,看Surface对象生命周期与Activity生命周期的联系,方便对下面的操作更进一步了解
启动播放器之后的生命周期函数图:
因为我没有在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);
}
}
解决办法是我们需要在Surface生命周期函数SurfaceCreate()里对上一次最后播放的帧进行判断和处理,也就是Surface生命周期函数SurfaceDestory()执行之后,当我们点击HOME键,我们在 SurfaceDestory()函数调用getCurrentPosition()方法取到Surface在被销毁之前的最后一帧,将其保存,直到我们下一次执行SurfaceCreate()函数的时候,对帧进行一次判断就OK了,然后调用seekTo()方法进行跳帧播放,这样我们就可以解决当活动或者Surface被销毁之后再次进入程序无法看到动画的原因了
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生命周期函数、播放器逻辑
好了,内容就这么多,好好消化,试试自己动手写写,有助于加深印象