surfaceview生命周期(surface 寿命)
Android surfaceview实现显示画面但不立即播放功能!
package com.zsch.forestinventory.activity.left_activity;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.MediaController;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.zsch.androidlib.activity.BaseActivity;
import com.zsch.androidlib.ui.ZoomableImageView;
import com.zsch.androidlib.view.LVCircularRing;
import com.zsch.forestinventory.R;
import java.io.IOException;
/**
* 查看单张照片
*/
public class ShowVideoActivity extends BaseActivity implements View.OnClickListener, MediaController.MediaPlayerControl, MediaPlayer.OnBufferingUpdateListener, SurfaceHolder.Callback {
private ImageButton ibBack;
private Uri uri;
private MediaPlayer mediaPlayer;
private MediaController controller; //视频播放控制器
private int bufferPercentage = 0;
@Override
protected void initVariables() {
uri = getIntent().getData();
if (uri == null)
finish();
}
@Override
protected void initView(Bundle savedInstanceState) {
setContentView(R.layout.activity_show_video);
mediaPlayer = new MediaPlayer();
controller = new MediaController(this);
//媒体控制器将创建一个具有默认设置的控件,并把它们放到一个窗口里漂浮在你的应用程序上。具体来说,这些控件会漂浮在通过setAnchorView()指定的视图上
//设置这个控制器绑定(anchor/锚)到一个视图上。例如可以是一个VideoView对象,或者是你的activity的主视图。
controller.setAnchorView(findViewById(R.id.root_ll));
if (savedInstanceState == null) {
// Bundle类型的数据与Map类型的数据相似,都是以key-value的形式存储数据的。实际上,savedInstanceState也就是保存Activity的状态的
//onsaveInstanceState方法是用来保存Activity的状态的。当一个Activity在生命周期结束前,会调用该方法保存状态
// 用来保存状态信息的Bundle会同时传给两个method,即onRestoreInstanceState() and onCreate().
uri = getIntent().getData();
} else {
uri = savedInstanceState.getParcelable("uri");
}
if (uri == null) {
Toast.makeText(this, "无视频展示", Toast.LENGTH_SHORT).show();
finish();
return;
}
SurfaceView videoSuf = (SurfaceView) findViewById(R.id.controll_surfaceView);
ibBack=(ImageButton) findViewById(R.id.ibBack);
ibBack.setOnClickListener(this);
videoSuf.setZOrderOnTop(false);//true时 会置顶到window的最上层,那么会遮挡其它view ...
videoSuf.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
videoSuf.getHolder().addCallback(this); //只要继承SurfaceView类并实现SurfaceHolder.Callback接口就可以实现一个自定义的SurfaceView了, 然后有surfaceCreated,surfaceChanged,surfaceDestroyed 等三种方法
// videoSuf.getHolder().setKeepScreenOn(true); //屏幕总是打开
//videoSuf.setFixedSize(320, 220);//显示的分辨率,不设置为视频默认
}
@Override
protected void loadData() {
}
@Override
protected void onResume() {
super.onResume();
// try {
// mediaPlayer.setDataSource(uri.getPath()); //播放存储设备的资源文件
// mediaPlayer.setOnBufferingUpdateListener(this); //监听事件,网络流媒体的缓冲监听。这个方法与上个接口中的方法int getBufferPercentage()进行结合使用
// //mediaPlayer.prepare();
// controller.setMediaPlayer(this);
// controller.setEnabled(true); //设置按钮可点击 false 设置为不可点击
// }catch (IOException e){
// e.printStackTrace();
// }
}
@Override
protected void onPause() {
super.onPause();
if (mediaPlayer.isPlaying()){
mediaPlayer.stop();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (null != mediaPlayer){
mediaPlayer.release();
mediaPlayer = null;
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.ibBack:
onBackPressed();
break;
}
}
@Override
public void onBackPressed() {
// if (mediaPlayer.isPlaying()){
// mediaPlayer.stop();
// }
// if (null != mediaPlayer){
// mediaPlayer.release();
// mediaPlayer = null;
// }
finish(); // finish();//在Activity中执行this.finish()方法之后,执行如下过程: onPause(),onStop(),onDestory(),
}
@Override
public boolean onTouchEvent(MotionEvent event) { //实现这个方法来处理触摸屏幕引发的事件。
controller.show();
return super.onTouchEvent(event);
}
@Override
public void onPointerCaptureChanged(boolean hasCapture) {
}
//MediaPlayerControl
@Override
public void onBufferingUpdate(MediaPlayer mediaPlayer, int i) {
bufferPercentage = i;
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) { //这步很重要 surfaceCreated创建时使用 //必须在surface创建后才能初始化MediaPlayer,否则不会显示图像
try {
mediaPlayer.setDataSource(uri.getPath()); //播放存储设备的资源文件
mediaPlayer.setOnBufferingUpdateListener(this); //监听事件,网络流媒体的缓冲监听。这个方法与上个接口中的方法int getBufferPercentage()进行结合使用
//mediaPlayer.prepare();
controller.setMediaPlayer(this);
controller.setEnabled(true); //设置按钮可点击 false 设置为不可点击
}catch (IOException e){
e.printStackTrace();
}
//设置显示视频显示在SurfaceView上
mediaPlayer.setDisplay(surfaceHolder);
// prepare方法是将资源同步缓存到内存中,一般加载本地较小的资源可以用这个,如果是较大的资源或者网络资源建议使用prepareAsync方法,异步加载.
// 但如果想让资源启动,即start()起来,因为在异步中,如果不设置监听直接start的话,是拿不到这个资源,如果让线程睡眠一段时间,则可以取得资源,
mediaPlayer.prepareAsync(); //准备 为异步加载
controller.show(); //显示控制器
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
//这里的 start()和pause() 指的是 media的拍摄过程中的 步骤
@Override
public void start() {
if (null != mediaPlayer){
mediaPlayer.start();
}
}
@Override
public void pause() {
if (null != mediaPlayer){
mediaPlayer.pause();
}
}
@Override
public int getDuration() {
return mediaPlayer.getDuration();
}
@Override
public int getCurrentPosition() { //获得 视频播放的位置 为了暂停后 继续播放
return mediaPlayer.getCurrentPosition();
}
@Override
public void seekTo(int i) {
mediaPlayer.seekTo(i);
}
@Override
public boolean isPlaying() {
if (mediaPlayer.isPlaying()){
return true;
}
return false;
}
@Override
public int getBufferPercentage() {
return bufferPercentage;
}
@Override
public boolean canPause() {
return true;
}
@Override
public boolean canSeekBackward() {
return true;
}
@Override
public boolean canSeekForward() {
return true;
}
@Override
public int getAudioSessionId() {
return 0;
}
}
android surfaceview 怎么用
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
上面代码我们在SurfaceView的构造方法中执行了init初始化方法,在这个方法里,我们先获取SurfaceView里的 SurfaceHolder对象,然后通过它设置Surface的生命周期回调方法,使用DemoSurfaceView类本身作为回调方法代理类。 surfaceCreated方法,是当SurfaceView被显示时会调用的方法,所以你需要再这边开启绘制的线 程,surfaceDestroyed方法是当SurfaceView被隐藏会销毁时调用的方法,在这里你可以关闭绘制的线程。上面的例子运行后什么也不 显示,因为还没定义一个执行绘制的线程。下面我们修改下代码,使用一个线程绘制一个逐渐变大的圆圈:
[java] view plain copy print?
package com.android777.demo.uicontroller.graphics;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
public class DemoSurfaceView extends SurfaceView implements Callback{
LoopThread thread;
public DemoSurfaceView(Context context) {
super(context);
init(); //初始化,设置生命周期回调方法
}
private void init(){
SurfaceHolder holder = getHolder();
holder.addCallback(this); //设置Surface生命周期回调
thread = new LoopThread(holder, getContext());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
如何正确的在fragment中显示surfaceview
由于在ViewPage中PageAdapter来管理所有的Fragment。在加载一个Fragment的时候,会自动缓存左右几个(默认是一个)页面,此时也会调用到正常的生命周期函数,onCreate,onCrateView,onResume.可是这样就干扰了我们统计页面打开次数。例如:一个ViewPager中存在三个页面的时候,当默认是第一个页面,并且被打开。那么在我们的后台就会收集到用户打开两个页面的信息。这个对我们的统计就是一个很大的干扰。所以就不能再将统计代码放在onResume中,要放在一个其他的函数内,但问题的关键是如何确认当前的Fragment可见?这样我才能把统计函数移植过去。同时Fragment虽然有onResume和onPause的,但是这两个方法是Activity的方法,调用时机也是与Activity相同,和ViewPager搭配使用这个方法就很鸡肋了,根本不是你想要的效果,这里介绍一种方法。
在问这个问题的时候把焦点都放在生命周期的触发上了。发现Fragment下专门有一个setUserVisibleHint函数来处理这个事情:
android surfaceview 会不会阻塞ui线程
系统不会为每个组件单独创建线程,在同一个进程里的UI组件都会在UI线程里实例化,系统对每一个组件的调用都从UI线程分发出去。
结果就是,响应系统回调的方法(比如响应用户动作的onKeyDown()和各种生命周期回调)永远都是在UI线程里运行。
当App做一些比较重(intensive)的工作的时候,除非你合理地实现,否则单线程模型的performance会很poor。
特别的是,如果所有的工作都在UI线程,做一些比较耗时的工作比如访问网络或者数据库查询,都会阻塞UI线程,导致事件停止分发(包括绘制事件)。对于用户来说,应用看起来像是卡住了,更坏的情况是,如果UI线程blocked的时间太长(大约超过5秒),用户就会看到ANR(application not responding)的对话框。
另外,Andoid UI toolkit并不是线程安全的,所以你不能从非UI线程来操纵UI组件。你必须把所有的UI操作放在UI线程里,所以Android的单线程模型有两条原则:
1.不要阻塞UI线程。
2.不要在UI线程之外访问Android UI toolkit(主要是这两个包中的组件:android.widget andandroid.view)。
SurfaceView/VideoView
我们经常用SurfaceView和VideoView来播放视频,但是这两个东东,经常都会出问题。
①SurfaceView不会添加到View树上,并且显示在所有View之上
②在按Home键的时候,会让Surface销毁,并且在重新进入APP的时候,让Surface重建,在Surface重建的时候,SurfaceView那一块是透明的,显示的会是Activity的背景
③如果是列表中的子view中播放视频,在上下滑动的时候,会导致Surface绘制不及时,会有残留
④多个VideoView同时播放的时候,在SurfaceFlinger支持不好的手机上,会出现下一个SurfaceView的某一帧会显示在上一个SurfaceView上
①VideoView是直接继承SurfaceView
②VideoView中的openVideo可能会ANR
③VideoView中的release,stopPlayBack都会导致ANR,因为这些方法都是同步执行的,并且通过IPC服务交给MediaServer去释放资源
解决方法:
使用TextureView替换SurfaceView实现VideoView,因为TextureView是直接继承View的,并且在ListView中滑动的时候,也不会在滑动的时候,有帧残留
当我们有一个列表,每个子项就是一个视频,并且自动播放。surfaceView使用的就是Android自带的状态机来控制播放,所以就会一段一段的将视频先读到缓冲区,再播放。由于MediaPlayer中的release,reset,stopPlayBack都是同步的。而且当视频卡片在滑出屏幕之后,需要把视频暂停,在不可见的时候不进行播放。节省系统资源,并且节省用户流量。而如果同时出现多个视频的时候,会频繁调用到上述生命周期方法,导致很容易出现ANR。
解决方法:
问题1.在视频划出ListView的时候,停止播放视频。
解决方案:在ListView中调用setRecycleListener,设置View回收的监听,因为ListView的重用性,会在View回收到scrap区的时候,通过这个Listener进行一些处理,所以在这里根据View.getTag,找到视频View的引用,调用stopPlayBack停止
问题2.频繁调用release等方法导致ANR
解决方案:在视频调用的时候,建立一个释放视频资源的守护线程。在Android中,直接可以用HandlerThread,因为这样可以尽可能的让资源的消耗达到最少,HandlerThread在没有新事件到来的时候,都是处于wait状态,直到有新事件的到来,才会被notify,处理新事件。它里面也是通过一个Thread,在这个Thread中新建一个Looper,在Looper中没有事件的话,则wait,一旦通过Handler发送新事件的话,则会被notify。所以会在子线程中加入一个队列,当需要release的MediaPlayer,直接丢到子线程去进行资源释放。
但是这样会导致一个问题,就是Android维护的MediaPlayer的状态机中的状态可能会乱,这时候就会抛出IllegalStateException,目前对于这种异常,我们选择了捕获它。
另外,由于MediaPlayer.setSurface需要传递一个Surface,然后再在这个Surface上进行绘制,如果频繁new Surface传入的话,就会导致GrafficBuffer分配Surface失败,从而MediaPlayer会回调onError中,显示视频不能播放。所以尽可能一个视频View用一个Surface。
SurfaceView的简单使用
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
protected Button mBufferBtn;
protected Button mVideoBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_main);
initView();
}
@Override
public void onClick(View view) {
Intent intent = new Intent();
if (view.getId() == R.id.button) {
intent.setClass(this, BufferActivity.class);
} else if (view.getId() == R.id.button) {
intent.setClass(this, VideoActivity.class);
}
startActivity(intent);
}
private void initView() {
mBufferBtn = (Button) findViewById(R.id.button2);
mBufferBtn.setOnClickListener(MainActivity.this);
mVideoBtn = (Button) findViewById(R.id.button2);
mVideoBtn.setOnClickListener(MainActivity.this);
}
}
public class BufferActivity extends AppCompatActivity implements SurfaceHolder.Callback {
protected SurfaceView mSurfaceView;
private SurfaceHolder mHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_buffer);
initView();
initSurfaceHolder();
}
// 初始化Surface的管理者
private void initSurfaceHolder() {
mHolder = mSurfaceView.getHolder();
// 添加管理生命周期的接口回调
mHolder.addCallback(this);
}
private void initView() {
mSurfaceView = (SurfaceView) findViewById(R.id.surface_view);
}
// 缓冲区创建
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d("1507", "surfaceCreated");
new DrawThread().start();
}
// 缓冲区内容改变(子线程渲染UI的过程)
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d("1507", "surfaceChanged");
}
// 缓冲区销毁
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d("1507", "surfaceDestroyed");
}
// 绘制UI的子线程
private class DrawThread extends Thread {
@Override
public void run() {
super.run();
// 创建画笔
Paint paint = new Paint();
paint.setColor(Color.GREEN);// 画笔颜色
paint.setStrokeWidth(10);// 画笔粗细。注意:Java中设置的尺寸单位都是px
paint.setStyle(Paint.Style.FILL_AND_STROKE);// 设置实心
paint.setAntiAlias(true);// 设置是否抗锯齿
// 获取SurfaceView的盖度
int height = mSurfaceView.getHeight();
Canvas canvas = null;
for (int i = 0; i height; i+= 5) {
// 获取Surface中的画布
canvas = mHolder.lockCanvas();// 锁定画布
// 使用画笔在画布上绘制指定形状
canvas.drawCircle(100, i + 50, 50, paint);// 圆心x坐标,圆心y坐标,半径,画笔
// 缓冲区的画布绘制完毕,需要解锁并提交给窗口展示
mHolder.unlockCanvasAndPost(canvas);
//? ? ? ? ? ? ? ? try {
//? ? ? ? ? ? ? ? ? ? Thread.sleep(100);
//? ? ? ? ? ? ? ? } catch (InterruptedException e) {
//? ? ? ? ? ? ? ? ? ? e.printStackTrace();
//? ? ? ? ? ? ? ? }
}
}
}
}
public class VideoActivity extends AppCompatActivity implements View.OnClickListener {
protected MyVideoSurfaceView mSurfaceView;
protected Button mPlayBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_video);
initView();
}
// 运行、可见
@Override
protected void onStart() {
super.onStart();
}
// 可交互
@Override
protected void onResume() {
super.onResume();
}
private void play() {
String videoPath = Environment.getExternalStorageDirectory().getPath() +
"/VID_20171117_144736.3gp";// 外部存储根路径
mSurfaceView.playVideo(videoPath);
}
private void initView() {
mSurfaceView = (MyVideoSurfaceView) findViewById(R.id.surface_view);
mPlayBtn = (Button) findViewById(R.id.play_btn);
mPlayBtn.setOnClickListener(VideoActivity.this);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.play_btn) {
play();
}
}
}
public class MyVideoSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private MediaPlayer mMediaPlayer;
public MyVideoSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// 获取Surface换朝哪个区的持有者
mHolder = getHolder();
mHolder.addCallback(this);
}
// 设置播放源
public void playVideo(String path) {
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
}
try {
// 设置播放源
mMediaPlayer.setDataSource(path);
// 设置多媒体的显示部分:使用SurfaceHolder渲染画面
mMediaPlayer.setDisplay(mHolder);
mMediaPlayer.prepare();
mMediaPlayer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mMediaPlayer.release();
mMediaPlayer = null;
}
}