day14 Android多媒体
1. Android中图片的处理
1.1 加载分辨率过大的图片以及OOM异常
1.1.1 OOM产生的原因 – 内存溢出:当我们加载的图片的分辨率过大,就会出现OOM内存溢出错误。 这是因为安卓系统vm的最大内存申请极限是16M。而我们在处理图片的时候是根据图片的分辨率大小来创建内存的byte数组,如果数组过大,超过了内存的极限,就会报OOM异常。
1.1.2 获取bitmap需要BitmapFactory,调用里面的Bitmap.decodeFile()方法使用decodeFile这种方式是把图片文件所有的像素点都加载入内存。如果图片分辨率过大 虚拟机承受不了负担就会报OOM异常
Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/hh.jpg")
解决的方法:只要图片的分辨率高于手机分辨率或者相同 就不会看出有任何不同的地方 所以首先要获取手机的分辨率 再跟图片的分辨率进行比例压缩就可以完美解决.
具体步骤:
- 获取手机屏幕api – getSystemService(WINDOW_SERVICE) 返回 WindowManager 对象 使用manager对象获取手机分辨率
- 获取图片分辨率api – BitmapFactory。decodeFile(“file”,opts) Option对象是安卓定义好的bitmap选项类 这个类中我们可以设置图片只读不解析加载到内存– bitmap.InJustDecodeBounds=true 这个属性设置为True 之后Decoder就会返回null 而不会返回位图 这样就可以不去真实的解析 bitmap 但可以查询宽跟高
设置完这个参数之后decodeFile()文件后返回值就为null了 - 利用获取到的图片与屏幕宽和高 得出要缩放的比例 将Option选项中的opts.inSampleSize设置为缩放比例 就可以缩放文件了
最后还要真正解析图片 将前面设置好的opts传入decodeFile()方法中
//加载分辨率过大图片方法 public void load(View v){ BitmapFactory.Options opts = new BitmapFactory.Options(); //设置只获取图片的大小 而不需要直接加载图片进入内存 opts.inJustDecodeBounds = true; //获取原图片宽高 BitmapFactory.decodeFile("/mnt/sdcard/dog.jpg",opts); int imgWidth = opts.outWidth; int imgHeight = opts.outHeight; Log.i("MainActivity","imgWidth"+imgWidth+"~~~imgHeight"+imgHeight); //获取模拟器窗口信息 WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); Point outSize = new Point(); wm.getDefaultDisplay().getSize(outSize); int screenWidth = outSize.x; int screenHeight = outSize.y; Log.i("MainActivity","screenWidth"+screenWidth+"~~~screenHeight"+screenHeight); //获取图片的宽 高缩放比例 通过比较比例 选取大的作为缩放比 int scaleX = imgWidth/screenWidth; int scaleY = imgHeight/screenHeight; int scale = 1; if(scaleX>scaleY && scaleX>1){ scale = scaleX; }else if(scaleX<scaleY && scaleY>1){ scale = scaleY; } //设置图片缩放比 opts.inSampleSize = scale; //重要:设置完成后 要将opts.inJustDecodeBounds属性改为false就可以加载图片了 opts.inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/dog.jpg", opts); iv.setImageBitmap(bitmap); }
2. 创建图片的副本
先熟悉一下Android里面关于Canvas API的描述:To draw something, you need 4 basic components: A Bitmap to hold the pixels. a Canvas to host the draw calls(Writing into the bitmap).
简单可以理解为:Bitmap是用来存储像素的, Canvas是用来接收draw的调用(Draw的结果就是即将像素给画到Bitmap中)。 由此Bitmap其实就是用来直接展示在窗口上的一个显示对象,他是一个最终的产品。
那么怎么理解Canvas呢?Canvas有画布的意思,刚开始接触的时候 容易理解为画图应该直接在画布上完成即可。但是在Android中Canvas其实是一个媒介,是通过Canvas来调用drawLine,drawCircle方法,最终展示的都是像素,而只有Bitmap可以保存像素,canvas并不能。所以创建Canvas的时候一定要传递一个Bitmap对象,用来承载在Canvas上画的内容。 可以更好的理解为 Bitmap是一张不可以直接画图的图像,只有在这张图像上放Canvas这个画布,然后就可以在画布上开始作图,最后掀开画布,这个图对应的像素点就存储到Bitmap中了。 而不是很多人说的白纸 这样会有很多人误解为 作图应该直接在纸上化成即可。
重点步骤
- 获取要拷贝的原图 获取原图的代码如下。
Bitmap bitmap = BitmapFactory.decodeResource(Resource res, int id)
:这行代码作用是获取到资源文件下的图片。 创建一个Bitmap副本,这里要用到一个Bitmap类中的静态方法createBitmap() : 根据原图的尺寸配置创建副本。 注意: 这里获取到的newBitmap只是一个位图的配置对象,并不是拷贝好的图片。
Bitmap newBitmap = Bitmap.createBitmap(int width,int height,Config config)
width = bitmap.getWidth()
height = bitmap.getHeight()
config = bitmap.getConfig()
- 创建画板跟画笔对象。 Google提供了一个Canvas类 目的就是为了作图
Canvas canvas = new Canvas(newBitmap)
Paint paint = new Paint()
paint.setColor(Color.BLACK)
- 作图用的是canvas中的drawBitmap()方法
canvas.drawBitmap(bitmap,Matrix matrix,Paint paint)
Matrix是Google定义的矩阵类,他的作用主要是来放大或者缩小图片
Matrix matrix = new Matrix()
matrix.setScale(2,2)
就是将图片的放大两倍
Matrix介绍:这里不得不提到Matrix矩阵的概念。相信大家很多搞程序的都不知道或者学过也也早已经还给老师了。矩阵就是由方程组的系数以及常数所构成的方阵,用在解线性方程组上既方便又直观。 其实就是解线性方程的组的一系列理论。高数很重要,还在上大学的朋友们一定且行且珍惜。 Android中Google已经帮我们定义好了矩阵这个类处理图像,所以直接可以使用。其实原理就是处理每一个像素点的坐标。所以Goolge用Matrix矩阵封装了关于图片处理的方法:
matrix.setRotate(degrees,px,py) – 设置旋转(旋转度数,x轴中心点,y轴中心点)
matrix.setScale() – 设置放大缩小
matrix.setTranslate(dx,dy) – 设置位移
//将图片放大
public void turnBig(View v) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.tomcat);
Matrix matrix = new Matrix();
matrix.setScale(2,2);
Paint paint = new Paint();
paint.setColor(Color.BLUE);
Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth() * 2, bitmap.getHeight() * 2, bitmap.getConfig());
Canvas canvas = new Canvas(newBitmap);
canvas.drawBitmap(bitmap,matrix,paint);
iv.setImageBitmap(newBitmap);
}
//图片的左右移动
public void turnRight(View v){
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.tomcat);
Bitmap newBitmap = bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
dx++;
Canvas canvas = new Canvas(newBitmap);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
Matrix matrix = new Matrix();
matrix.setTranslate(dx,0);
canvas.drawBitmap(bitmap,matrix,paint);
iv.setImageBitmap(newBitmap);
}
//图片的旋转
int degrees = 1;
public void rotate(View v){
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.tomcat);
Bitmap newBitmap = bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
Canvas canvas = new Canvas(newBitmap);
Matrix matrix = new Matrix();
Paint paint = new Paint();
degrees--;
matrix.setRotate(degrees,bitmap.getWidth()/2,bitmap.getHeight()/2);
canvas.drawBitmap(bitmap,matrix,paint);
iv.setImageBitmap(newBitmap);
}
小知识点:
1. 当我们要获取到res目录下的文件的时候,我们就可以使用decodeResource(Resource res, int id)方法了 参数res我们直接使用API getResource()方法.谷歌封装好的调用系统资源方法,可以获取到所有资源文件 eg 图片 layout string… id就是要找的资源R.id….
2. 由于我们要修改图片时,原图是无法修改的。,所以必须要创建一个原图的copy副本。在副本上面才可以修改 使用bitmap.createBitmap来创建 API: createBitmap(int width,int height, Config config) 这个方法config参数指的是文件的配置文件 – eg 是多少色图/位图… 由于是创建副本 使用bitmap.getConfig();
3.综合案例练习
3.1 画画板
要求:画图 将图片保存 设置画笔颜色 public class MainActivity extends AppCompatActivity implements View.OnClickListener { ImageView iv;
float startX;
float startY;
float endX;
float endY;
private Bitmap bitmap;
private Paint paint;
private Canvas canvas;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv);
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
int width = wm.getDefaultDisplay().getWidth();
int height = wm.getDefaultDisplay().getHeight();
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
paint = new Paint();
paint.setColor(Color.BLACK);
TextView tv_red = (TextView) findViewById(R.id.tv_red);
TextView tv_green = (TextView) findViewById(R.id.tv_green);
TextView tv_blue = (TextView) findViewById(R.id.tv_blue);
tv_red.setOnClickListener(this);
tv_blue.setOnClickListener(this);
tv_green.setOnClickListener(this);
iv.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getRawX();
startY = event.getRawY();
Log.i("MainActivity", "Touched");
break;
case MotionEvent.ACTION_MOVE:
Log.i("MainActivity", "Moving");
endX = event.getRawX();
endY = event.getRawY();
canvas.drawLine(startX, startY, endX, endY, paint);
startX = endX;
startY = endY;
iv.setImageBitmap(bitmap);
break;
case MotionEvent.ACTION_UP:
Log.i("MainActivity", "Released");
}
return true;
}
});
}
public void save(View v){
try {
File file = new File(Environment.getExternalStorageDirectory().getPath(),System.currentTimeMillis()+".jpg");
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos);
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.tv_blue:
paint.setColor(Color.BLUE);
Toast.makeText(getApplicationContext(),"Blue Selected",Toast.LENGTH_SHORT).show();
break;
case R.id.tv_green:
paint.setColor(Color.GREEN);
Toast.makeText(getApplicationContext(),"Green Selected",Toast.LENGTH_SHORT).show();
break;
case R.id.tv_red:
paint.setColor(Color.RED);
Toast.makeText(getApplicationContext(),"Red Selected",Toast.LENGTH_SHORT).show();
break;
}
}
}
2 Android 音乐播放 - MediaPlayer
核心:MediaPlayer的生命周期
- Idle :空闲状态 setDataSource()
Initialized:有两个方法 prepare()同步(播放本地音乐) prepareAsync异步准备(播放网络音乐)!重点 在异步准备中有一个preparing生命周期 播放网络音乐要设置setOnpreparedLinstener监听.
mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { mediaPlayer.start(); } });
Prepared:seekTo() start()
- Started:pause() stop()单向模式 OnCompletionListener
- Paused
Stoped
MediaPlayer的使用
public class MainActivity extends AppCompatActivity { private static SeekBar seekBar; private Iservice iservice; public static Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { Bundle data = msg.getData(); int duration = data.getInt("duration"); int currentPosition = data.getInt("currentPosition"); seekBar.setMax(duration); seekBar.setProgress(currentPosition); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent(this,MusicPlayerService.class); startService(intent); MyConn conn = new MyConn(); bindService(intent,conn,BIND_AUTO_CREATE); seekBar = (SeekBar) findViewById(R.id.seekBar); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { iservice.callPlayMusicPosition(seekBar.getProgress()); } }); } public void play(View v){ iservice.callPlay(); } public void pause(View v){ iservice.callPause(); } public void restart(View v){ iservice.callRestart(); } public class MyConn implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service) { iservice = (Iservice) service; } @Override public void onServiceDisconnected(ComponentName name) { } } }
Service服务中播放音乐
public class MusicPlayerService extends Service { MediaPlayer mediaPlayer; @Override public IBinder onBind(Intent intent) { return new MyBinder(); } @Override public void onCreate() { mediaPlayer = new MediaPlayer(); try { mediaPlayer.setDataSource("/mnt/sdcard/Bruno Mars - Talking to the Moon.mp3"); mediaPlayer.prepare(); } catch (IOException e) { e.printStackTrace(); } super.onCreate(); } public void play() { mediaPlayer.start(); updateSeekBar(); } public void pause() { mediaPlayer.pause(); } public void restart() { mediaPlayer.start(); } public void playMusicPosition(int position){ mediaPlayer.seekTo(position); } public class MyBinder extends Binder implements Iservice { @Override public void callPlay() { play(); } @Override public void callPause() { pause(); } @Override public void callRestart() { restart(); } @Override public void callPlayMusicPosition(int position) { playMusicPosition(position); } } }
SeekBar进度条更新
private void updateSeekBar() { //获取当前歌曲的总进度 final int duration = mediaPlayer.getDuration(); final Timer timer = new Timer(); final TimerTask task = new TimerTask() { @Override public void run() { int currentPosition = mediaPlayer.getCurrentPosition(); Message msg = Message.obtain(); Bundle bundle = new Bundle(); bundle.putInt("duration",duration); bundle.putInt("currentPosition",currentPosition); msg.setData(bundle); MainActivity.handler.sendMessage(msg); } }; timer.schedule(task,1000,1000); mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { Toast.makeText(getApplicationContext(),"Music Play Finished",Toast.LENGTH_SHORT).show(); timer.cancel(); task.cancel(); } }); }
3 Android 视频播放 – MediaPlayer
3.1 surfaceView – 重量级控件 跟普通TextView Button等空间不同 当我们使用surfaceView的时候 无法按正常获取空间的方式直接播放视频。 这是因为surfaceView是重量级控件,加载需要时间 否则无法拿到这个类的的实例,SurfaceView没有被初始化就无法获取到Holder。 这个时候可以通过surfaceView的生命周期方法来播放视频。
- 使用SurfaceView必须要拿到surfaceHolder–在控件中获取holder。
通过使用回调函数holder.addCallback()中surfaceCreated方法就可以确定surfaceView已经初始化成功
holder.addCallback(new SurfaceHolder.Callback() { //当该方法调用的时候意味着SurfaceView已经初始化好 可以使用了 @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } //当播放界面被切换出去就会执行destroy方法 @Override public void surfaceDestroyed(SurfaceHolder holder) { if(mediaPlayer.isPlaying()&&mediaPlayer!=null) { currentPosition = mediaPlayer.getCurrentPosition(); mediaPlayer.stop(); mediaPlayer.reset(); mediaPlayer.release(); } });
对比播放音乐 一定要切记使用SurfaceView播放video时要使用mediaPlayer.setDisplay(holder) 并且将holder传入 因为视频播放时依赖于载体的 而Holder就是用来维护视频播放器的 重点
3.2 videoView 继承自SurfaceView 相当于是Surface的自定义控件 适合用于简单地播放视频广告
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//[1]找到控件 测量 排版 绘制
VideoView vv = (VideoView) findViewById(R.id.vv);
//[2]设置视频播放的路径
vv.setVideoPath("http://192.168.77.83:8080/cc.mp4");
//[3]开始播放
vv.start();
}
}
3.3 Vitamo 视频播放框架 可以直接引入整个类库 – 就是一个完整的应用
由于Google提供的MediaPlayer只支持MP4 3GP类型的文件格式 所以这个时候三方类库就比较好用了 Vitamo可以支持基本市面上所有的视频播放
主要步骤
1 引入vitamio框架 以library、
2 在布局中定义VideoView
<io.vov.vitamio.widget.VideoView
android:id="@+id/vv"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
3 mainactivity代码
//插件vitamio框架检查是否可用
if (!LibsChecker.checkVitamioLibs(this)) {
return;
}
final VideoView vv = (VideoView) findViewById(R.id.vv);
vv.setVideoPath("http://192.168.1.2:8080/haha.avi");
vv.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
vv.start();
}
});
//设置video的控制器
vv.setMediaController(new MediaController(this));
4 一定要在清单文件初始化InitActivity
<activity android:name="io.vov.vitamio.activity.InitActivity"></activity>