原博客不更新 http://write.blog.csdn.net/postlist ,以后内容都会在这里写
在开始本文之前先看一段Log
“Skipped 47 frames! The application may be doing too much work on its main thread”
这个警告大多数是在自定义View时产生的,而其中在绘制过程中处理逻辑太多、刷新数据量比较大是主要原因
因为逻辑和刷新的数据一般和产品的效果或逻辑有关,优化的空间有限,如果对产品流程性要求很高就需要用新的东东来实现了。Android提供了SurfaceView就是来处理这种情形的。
View通过刷新来重绘视图,Android系统通过发出VSYNC信号来进行屏幕绘制,刷新时间间隔为16ms。如果18ms内View完成了你所须要执行的操作,那么在视觉上就不会参数卡顿;但是当执行逻辑或处理数据较多时,特别是需要频繁刷新的界面上,就会阻塞主线程导致画面卡顿。如:相机取景、视频播放、游戏界面绘制。普通应用最容易遇到的场景,在获取网络数据时加载动画,数据解析完毕隐藏动画显示UI。如果解析的数据量很大,在数据返回到解析完毕情况动画就会卡顿一下。
SurfaceView和View的主要区别:
因为SurfaceView使用子线程更新页面,因此在有交互场景是会带来事件同步问题
SurfaceHolder的setType函数来设置绘制的类型,参数如下:
SURFACE_TYPE_NORMAL:用RAM缓存原生数据的普通Surface
SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access )引擎和硬件加速的Surface
SURFACE_TYPE_GPU:适用于GPU加速的Surface
SURFACE_TYPE_PUSH_BUFFERS:表明该Surface不包含原生数据,Surface用到的数据由其他对象提供,在Camera图像预览中就使用该类型的Surface,有Camera负责提供给预览Surface数据,这样图像预览会比较流畅。如果设置这种类型则就不能调用lockCanvas来获取Canvas对象了
下面是标准的SurfaceView的使用模板
class MyView extends SurfaceView implements SurfaceHolder.Callback,Runnable{
boolean isRunning;
SurfaceHolder holder;
Canvas canvas;
public MyView(Context context) {
super(context);
initView();
}
//初始化
private void initView()
{
// TODO Auto-generated constructor stub
holder = getHolder();
holder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
}
//SurfaceView创建
@Override
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
isRunning = true;
Thread t=new Thread(this);
t.start();
}
//SurfaceView改变
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
//SurfaceView销毁
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
isRunning = false;
}
// Runnable接口,Run方法的实现
@Override
public void run() {
while (isRunning) {
onDraw();
}
}
//绘制内容方法
@Override
protected void onDraw() {
try {
canvas = holder.lockCanvas();
//draw something
} catch (InterruptedException e) {
} finally{//确保每次都能将内容提交
if(canvas != null)
{
holder.unlockCanvasAndPost(canvas);
}
}
}
}
下面来用SurfaceView实现二个具体的例子,一个是根据手势绘制内容,一个是播放视频
public class SurfaceTouchView extends SurfaceView implements SurfaceHolder.Callback,Runnable{
//SurfaceHolder
private SurfaceHolder mHolder;
//用户绘制的Canvas
private Canvas mCanvas;
//子线程的标志位
private boolean mIsRunning;
//保存手指移动路径
private Path mPath;
private Paint mPaint;
public SurfaceTouchView(Context context) {
super(context);
initView();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
mPath.lineTo(x, y);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
private void initView()
{
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
mPath = new Path();
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);//注意这行作用,可以去掉看看
mPaint.setStrokeWidth(40);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsRunning = true;
new Thread(this).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsRunning = false;
}
@Override
public void run() {
long start = System.currentTimeMillis();
while (mIsRunning)
{
draw();
}
long end = System.currentTimeMillis();
//50-100
long dutime = end - start;
if (dutime < 100)
{
SystemClock.sleep(100-dutime);
}
}
private void draw()
{
try {
mCanvas = mHolder.lockCanvas();
mCanvas.drawColor(Color.WHITE);
mCanvas.drawPath(mPath,mPaint);
}catch (Exception e)
{
}finally {
if (mCanvas != null)
{
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
在Activity里面加一行
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SurfaceTouchView(this));
}
}
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.Toast;
import mfg.colormatrix.demo.R;
public class VideoPalyActivity extends AppCompatActivity {
private final String TAG = "VideoPlay";
private EditText et_path;
private SurfaceView sv;
private Button btn_play, btn_pause, btn_replay, btn_stop;
private MediaPlayer mediaPlayer;
private SeekBar seekBar;
private int currentPosition = 0;
private boolean isPlaying;
private View notice;
int index;
String url = "你自己的视频地址" ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_paly);
seekBar = (SeekBar) findViewById(R.id.seekBar);
sv = (SurfaceView) findViewById(R.id.sv);
notice = findViewById(R.id.notice);
et_path = (EditText) findViewById(R.id.et_path);
et_path.setText(url);
btn_play = (Button) findViewById(R.id.btn_play);
btn_pause = (Button) findViewById(R.id.btn_pause);
btn_replay = (Button) findViewById(R.id.btn_replay);
btn_stop = (Button) findViewById(R.id.btn_stop);
btn_play.setOnClickListener(click);
btn_pause.setOnClickListener(click);
btn_replay.setOnClickListener(click);
btn_stop.setOnClickListener(click);
// 为SurfaceHolder添加回调
sv.getHolder().addCallback(callback);
// 4.0版本之下需要设置的属性
// 设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到界面
sv.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// 为进度条添加进度更改事件
seekBar.setOnSeekBarChangeListener(change);
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
SystemClock.sleep(400);
play(0);
}
}).start();
}
private SurfaceHolder.Callback callback = new Callback() {
// SurfaceHolder被修改的时候回调
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i(TAG, "SurfaceHolder 被销毁");
// 销毁SurfaceHolder的时候记录当前的播放位置并停止播放
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
currentPosition = mediaPlayer.getCurrentPosition();
mediaPlayer.stop();
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i(TAG, "SurfaceHolder 被创建");
if (currentPosition > 0) {
// 创建SurfaceHolder的时候,如果存在上次播放的位置,则按照上次播放位置进行播放
play(currentPosition);
currentPosition = 0;
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Log.i(TAG, "SurfaceHolder 大小被改变");
}
};
private SeekBar.OnSeekBarChangeListener change = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 当进度条停止修改的时候触发
// 取得当前进度条的刻度
int progress = seekBar.getProgress();
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
// 设置当前播放的位置
mediaPlayer.seekTo(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
}
};
private View.OnClickListener click = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_play:
play(0);
break;
case R.id.btn_pause:
pause();
break;
case R.id.btn_replay:
replay();
break;
case R.id.btn_stop:
stop();
break;
default:
break;
}
}
};
/* * 停止播放 */
protected void stop() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
btn_play.setEnabled(true);
isPlaying = false;
}
}
/** * 开始播放 * * @param msec 播放初始位置 */
protected void play(final int msec) {
/**注释内容是播放本地视频**/
// 获取视频文件地址
// String path = et_path.getText().toString().trim();
// File file = new File(path);
// if (!file.exists()) {
// Toast.makeText(this, "视频文件路径错误", 0).show();
// return;
// }
try {
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
// 设置播放的视频源
mediaPlayer.setDataSource(url);
// 设置显示视频的SurfaceHolder
mediaPlayer.setDisplay(sv.getHolder());
Log.i(TAG, "开始装载");
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
seekBar.setVisibility(View.VISIBLE);
notice.setVisibility(View.GONE);
Log.i(TAG, "装载完成");
mediaPlayer.start();
// 按照初始位置播放
mediaPlayer.seekTo(msec);
// 设置进度条的最大进度为视频流的最大播放时长
seekBar.setMax(mediaPlayer.getDuration());
// 开始线程,更新进度条的刻度
new Thread() {
@Override
public void run() {
isPlaying = true;
while (isPlaying) {
int current = mediaPlayer
.getCurrentPosition();
seekBar.setProgress(current);
SystemClock.sleep(500);
}
}
}.start();
btn_play.setEnabled(false);
}
});
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
// 在播放完毕被回调
btn_play.setEnabled(true);
}
});
mediaPlayer.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// 发生错误重新播放
play(0);
isPlaying = false;
return false;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
/** * 重新开始播放 */
protected void replay() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.seekTo(0);
Toast.makeText(this, "重新播放", Toast.LENGTH_SHORT).show();
btn_pause.setText("暂停");
return;
}
isPlaying = false;
play(0);
}
/** * 暂停或继续 */
protected void pause() {
if (btn_pause.getText().toString().trim().equals("继续")) {
btn_pause.setText("暂停");
mediaPlayer.start();
Toast.makeText(this, "继续播放", Toast.LENGTH_SHORT).show();
return;
}
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
btn_pause.setText("继续");
Toast.makeText(this, "暂停播放", Toast.LENGTH_SHORT).show();
}
}
}
xml代码如下,我这边为了调试把开始暂停布局隐藏了,需要的自己改一下即可
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MainActivity" >
<EditText
android:id="@+id/et_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="/这里可以写一个默认路径方便调试" />
<RelativeLayout android:layout_width="match_parent"
android:layout_height="300dp">
<SurfaceView
android:id="@+id/sv"
android:layout_width="400dp"
android:layout_height="300dp" />
<TextView android:id="@+id/notice" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="视频正在加载中"
android:textColor="@android:color/white"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"/>
</RelativeLayout>
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone" >
<Button
android:id="@+id/btn_play"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="播放" />
<Button
android:id="@+id/btn_pause"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="暂停" />
<Button
android:id="@+id/btn_replay"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="重播" />
<Button
android:id="@+id/btn_stop"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="停止" />
</LinearLayout>
</LinearLayout>