博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
18、图片 & 多媒体
阅读量:6157 次
发布时间:2019-06-21

本文共 15229 字,大约阅读时间需要 50 分钟。

一、图片处理

1.1、BitMap

1.Bitmap介绍

Bitmap实现在android.graphics包中。但是Bitmap类的构造函数是私有的,外面并不能实例化,只能是通过JNI实例化。

这必然是某个辅助类提供了创建Bitmap的接口,而这个类的实现通过JNI接口来实例化Bitmap的,这个类就是BitmapFactory。

Bitmap是一片连续的内存空间,而我们的应用程序的内存是散乱的,如果当零散的空间下容纳不了bitmap时就导致OOM。

2.BitmapFactory

其中提供的一个Opitions属性可以用来提前获取到图片的宽高信息,下面会介绍。

2.Bitmap优化

当前有一张图片,大小仅为1M,但是其规格为3648*2736,现在需要加载此图片总像素数=3648*2736=9980928

假设现在像素采用ARGB_4444标准,则其占用的总空间为:

图片占用空间=总像素数 *像素的单位 ----> 9980928 * 2bytes=19961856bytes = 19M > 16M  OOM内存溢出

所以我们必须对图片进行缩放才能加载:

假设:

图片的宽和高: 3648 * 2736 屏幕的宽和高: 320 * 480

计算缩放比:

宽度缩放比例: 3648 / 320 = 11 高度缩放比例: 2736 / 480 = 5

比较宽和高的缩放比例, 哪一个大用哪一个进行缩放

缩放后的图片:
3648 / 11 = 331
2736 / 11 = 248
缩放后图片的宽和高: 331* 248
331* 248=882088 * 2bytes=160K

3.解析图片的三种方式

1.使用BitmapFactory解析图片

2.使用BitmapDrawable解析图片

3.使用InputStream和BitmapDrawable绘制

1.2、图片特效

图片的特效包括,图形的缩放、镜面、倒影、旋转、位移等。图片的特效是将原图的图形矩阵乘以一个特效矩阵,形成一个新的图形矩阵来实现的。

Matrix维护了一个3*3的矩阵去更改像素点的坐标。
图形的默认矩阵用数组表示为:
{ 1, 0, 0,     表示向量x = 1x + 0y + 0z
  0, 1, 0,     表示向量y = 0x + 1y + 0z
  0, 0, 1 }    表示向量z = 0x + 0y + 1z
通过更改图形矩阵的值,可以做出缩放/镜面/倒影等图片特效。

1.缩放

2.镜面

唯一不同的地方在矩阵处

3.倒影

4.旋转

5.位移

1.3、颜色过滤器

Android提供了颜色过滤器来进行颜色处理。

a)ColorMatrixColorFilter:通过使用一个4*5的颜色矩阵来创建一个颜色过滤器,改变图片的颜色信息。

b) 图形颜色默认矩阵是一个4x5的矩阵, 数组表现为:

{1, 0, 0, 0, 0, // red  1*R + 0*G + 0*B + 0*A + 0

0, 1, 0, 0, 0, // green  0*R + 1*G + 0*B + 0*A + 0

0, 0, 1, 0, 0, // blue 0*R + 0*G + 1*B + 0*A + 0

0, 0, 0, 1, 0} // alpha 0*R + 0*G + 0*B + 1*A + 0

颜色矩阵的每一行的最后一个值更改时,其对应的颜色值就会发生改变,所以更改颜色只需修改其对应颜色矩阵行的最后一项的值即可,最大值范围为255

a)首先我们声明一个初始化矩阵数组和颜色过滤器

b)重写progressBar的onProgressChanged方法,并修改矩阵数组的值后,重新创建新的颜色过滤器。

1.4、手绘

a)首先在布局文件创建一个ImageView,让其宽高都填充父窗体,并在代码中进行实例化,并设置它的Touch事件。

mIv = (ImageView) findViewById(R.id.iv);mIv.setOnTouchListener(this);

b) 然后重写onTouch(View v, MotionEvent event)方法对手势进行操作

@Overridepublic boolean onTouch(View v, MotionEvent event) {    int action = event.getAction();    switch (action) {    case MotionEvent.ACTION_DOWN:        mStartX = (int) event.getX();        mStartY = (int) event.getY();        if(mBitmap == null){            mBitmap = Bitmap.createBitmap(mIv.getWidth(),mIv.getHeight(),Config.ARGB_8888);            // 画布            mCanvas = new Canvas(mBitmap);            // 设置背景色白色            mCanvas.drawColor(Color.WHITE);            mPaint = new Paint();            // 设置画笔颜色为红色,线条粗细为5            mPaint.setColor(Color.RED);            mPaint.setStrokeWidth(5);        }        break;    case MotionEvent.ACTION_MOVE:        // 记录移动到的位置坐标        int moveX = (int) event.getX();        int moveY = (int) event.getY();        // 绘制线条,连接起始位置和当前位置        mCanvas.drawLine(mStartX, mStartY, moveX, moveY, mPaint);        // 设置显示        mIv.setImageBitmap(mBitmap);        // 将起始位置改为为当前移动到的位置        mStartX = moveX;        mStartY = moveY;        break;    case MotionEvent.ACTION_UP:        break;    }    return true;}

c) 上述基本功能已经完成,我们还需要一个清空的方法

// 清除界面public void clear(View view){    mBitmap = null;    mIv.setImageBitmap(null);}

1.5、三级缓存

1. 简介

现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取。

现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,

一个很耗流量的应用,其用户数量级肯定要受到影响。当然,我想,向百度美拍这样的应用,必然也有其内部的图片缓存策略。总之,图片缓存是很重要而且是必须的。

2.图片缓存的原理

实现图片缓存也不难,需要有相应的cache策略。这里我采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),

其实网络不算cache,这里姑且也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,

如果缓存文件中也没有,再从网络上通过http请求拉取图片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,

按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。

关于Java中对象的软引用(SoftReference),如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。

只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高 速缓存。使用软引用能防止内存泄露,增强程序的健壮性。 

当然,这种回收机制只存在于Android 2.3版本之前,Android 2.3之后垃圾回收器会更倾向于回收软引用、弱引用,它们变得不再可靠。

从代码上来说,采用一个ImageManager来负责图片的管理和缓存,函数接口为public void loadBitmap(String url, Handler handler) ;其中url为要下载的图片地址,

handler为图片下载成功后的回调,在handler中处理message,而message中包含了图片的信息以及bitmap对象。ImageManager中使用的ImageMemoryCache(内存缓存)、

ImageFileCache(文件缓存)以及LruCache(最近最久未使用缓存)会在后续文章中介绍。

二、多媒体

1.1、音频播放

1、MediaPlayer

该播放器同时只能播放一个音乐文件,文件大小并没有限制。MediaPlayer必须严格按照状态图操作,否则就会出现错误。

MediaPlayer播放的实例:

// 创建MediaPlayer对象MediaPlayer mediaPlayer = new MediaPlayer();mediaPlayer.setDataSource(Environment.getExternalStorageDirectory() + "/bg.mp3");// 准备mediaPlayer.prepare();// 判断当前音乐是否在播放boolean isPlaying = mediaPlayer.isPlaying();if(!isPlaying){    mediaPlayer.start();}else{    Toast.makeText(this,"音乐正在播放中",0).show();}

2、SoundPool

SoundPool特点是可以自行设置声音的品质、音量、播放比率等参数。并且它可以同时管理多个音频流,每个流都有独自的ID,对某个音频流的管理都是通过ID进行的。

a)SoundPool最大只能申请1M的内存空间,只能用一些很短的声音片段,而不是用它来播放歌曲或者做游戏背景音乐。

b)SoundPool提供了pause和stop方法,但这些方法建议最好不要轻易使用,因为有些时候它们可能会使你的程序莫名其妙的终止。

c)SoundPool的效率问题。其实SoundPool的效率在这些播放类中算是很好的,这可能会影响用户体验。

SoundPool播放的实例:

// 创建SoundPool对象SoundPool soundPool = new SoundPool(1,AudioManager.STREAM_MUSIC,1);// 加载SoundPool音乐文件int soundID = soundPool.load(this,R.raw.shoot1,1);// 播放soundPool音乐soundPool.play(soundID,1,1,0,0,1);

1.2、视频播放

1.第一种

a)第一种方式可以通过surfaceView和MediaPlayer来实现该功能,首先创建一个SufaceView实例

2.第二种

a)设置布局文件,布局文件比较简单,因此这里只给你VideoView标签。

mSurface = (SurfaceView) findViewById(R.id.sfv);     mHolder = mSurface.getHolder();mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

b) 然后通过如下代码播放,注意视频知支持mp4格式

/ 创建播放器对象MediaPlayer player = new MediaPlayer();// 获取音乐路径String path = "/mnt/sdcard/oppo.mp4";// 给播放器设置音乐路径player.setDataSource(path);// 设置音乐格式player.setAudioStreamType(AudioManager.STREAM_MUSIC);// 设置输出画面player.setDisplay(mHolder);// 准备player.prepare();// 播放player.start();

 

2.第二种

a)设置布局文件,布局文件比较简单,因此这里只给你VideoView标签。

b) 设置VideoView的播放文件路径和媒体控制器,调用start方法即可播放媒体文件,当然现在只是让当前类实现了MediaController接口

VideoView video = (VideoView) findViewById(R.id.video);video.setVideoPath("/mnt/sdcard/oppo.mp4");video.setMediaController(new MediaController(this));// 开始播放video.start();// 设置当前播放器窗口为焦点video.requestFocus();

c)这时,视频已经可以播放,如果想实现自己的业务逻辑,可以定义类实现MediaPlayerControl接口,其中提供大量的回调方法供我们使用。

1.3、传感器

Android手机中内置了很多传感器,其主要类型有:方向、加速度(重力)、光线、磁场、距离(临近性)、温度等。

1.传感器的使用

a) 获取传感器管理器SensorManager

SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);

b) 通过传感器管理器对象获得指定类型的传感器:

// 获取指定传感器对象,获取系统默认的重力加速度传感器Sensor sensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

c) 通过传感器管理器对象获得手机中所有的传感器

// 获取手机支持的所有传感器List
sensorList = sm.getSensorList(Sensor.TYPE_ALL);for (int i = 0; i < sensorList.size(); i++) { Sensor sensor = sensorList.get(i); // 获取传感器名称 String name = sensor.getName(); // 获取传感器厂商 String vendor = sensor.getVendor(); // 获取传感器版本号 int version = sensor.getVersion();}

d) 使用传感器管理器对象注册传感器来使一个传感器工作:

sm.registerListener(new SensorEventListener() {    // 当传感器改变时触发该函数    @Override    public void onSensorChanged(SensorEvent event) {            }    // 当传感器精度更改时触发该函数    @Override    public void onAccuracyChanged(Sensor sensor, int accuracy) {            }},sensor,SensorManager.SENSOR_DELAY_NORMAL);

(1) listener :传感器事件监听器

(2) sensor :要被注册的传感器对象

(3) rate  :采样率,分为最快、游戏、普通、用户界面几种当应用程序请求特定的采样率时,其实只是对传感器子系统的一个建议,不保证特定的采样率可用。

采样率的四种类型详解:

最快: SensorManager.SENSOR_DELAY_FASTEST,最低延迟,一般不是特别敏感的处理不推荐使用,该种模式可能造成耗电,由于传递的为原始数据,

       算法不处理好会影响游戏逻辑和UI的性能

游戏: SensorManager.SENSOR_DELAY_GAME,游戏延迟,一般绝大多数的实时性较高的游戏都使用该级别
普通: SensorManager.SENSOR_DELAY_NORMAL,标准延迟,对于一般的益智类或EASY级别的游戏可以使用,但过低的采样率可能对一些赛车类游戏有跳帧现象

用户界面: SensorManager.SENSOR_DELAY_UI,一般对于屏幕方向自动旋转使用,相对节省电能和逻辑处理,一般游戏开发中我们不使用

总结:由于传感器比较多,这里只是简单的概述,后续会花大量的时间整理传感器的使用。

1.4、摄像头

调用系统摄像头进行拍照和摄像无需添加权限,直接调用即可。只需知道系统摄像头的action和category就可以调用系统摄像头。

a)打开Android源码,查看”\packages\apps\”文件文件目录下的Camera应用,即系统摄像头的应用程序。打开其清单文件文件,查看其Activity的action和category信息。

b)Camera类的action和category

VideoCamera类的action和category

c) 已知调用系统摄像头拍照和摄像功能对应的action和category信息,采用隐式调用的方式调用Activity。

由于希望在调用拍照或摄像功能后回到当前应用的界面,且得知拍照或摄像的结果如何是否成功使用startActivityForResult方法开启Activity,并重写onActivityResult方法处理回传的数据。

1.拍照功能

Intent intent = new Intent();// 设置Actionintent.setAction("android.media.action.IMAGE_CAPTURE");// 创建一个文件File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),"my.jsp");// 创建Uri对象Uri uri = Uri.fromFile(file);// 设置图片输出路径intent.putExtra(MediaStore.EXTRA_OUTPUT,uri);// 开启ActivitystartActivityForResult(intent,100);

2.摄像功能

Intent intent = new Intent();intent.setAction("android.media.action.VIDEO_CAPTURE");File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),"myVedio.mp4");Uri uri = Uri.fromFile(file);intent.putExtra(MediaStore.EXTRA_OUTPUT,uri);startActivityForResult(intent,101);

3.自定义拍照

a)首先定义一个SurfaceView用作相机的预览

SurfaceView mSurfaceView = (SurfaceView) this.findViewById(R.id.surfaceView);SurfaceHolder holder = mSurfaceView.getHolder();holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);holder.setKeepScreenOn(true);holder.addCallback(new SurfaceCallback());

  b )创建SurfaceCallback来开启预览,其中提供了三个回调方法,方便我们对相机预览进行管理。

/**sufaceView的回调*/private final class SurfaceCallback implements Callback {    @Override    public void surfaceCreated(SurfaceHolder holder) {        try {            mCamera = Camera.open(0);// 0:打开后置    1:打开前置             if(mCamera != null){                mCamera.setPreviewDisplay(holder);                mCamera.setDisplayOrientation(getPreviewDegree(NewCameraCheck.this));                mCamera.startPreview();            }else{                Toast.makeText(NewCameraCheck.this, "获取不到相机实例", Toast.LENGTH_SHORT).show();            }                  } catch (Exception e) {            e.printStackTrace();        }    }    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {        if(mCamera != null){            parameters = mCamera.getParameters();             parameters.setPictureFormat(PixelFormat.JPEG);              parameters.setPreviewSize(width, height);            parameters.setPreviewFrameRate(5);            parameters.setPictureSize(width, height);            parameters.setJpegQuality(80);        }else{            Toast.makeText(NewCameraCheck.this, "获取不到相机实例,无法设置参数。", Toast.LENGTH_SHORT).show();        }    }    // 在Surface销毁时释放资源    @Override    public void surfaceDestroyed(SurfaceHolder holder) {        if(mCamera != null){            //mCamera.stopPreview();            mCamera.release();             mCamera = null;        }    }}

c) 提供一个静态方法用来对相机预览的旋转角度进行自动调整

// 提供一个静态方法,用于根据手机方向获得相机预览画面旋转的角度public  int getPreviewDegree(Activity activity) {    // 获得手机的方向    int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();    mDegree = 0;    // 根据手机的方向计算相机预览画面应该选择的角度    switch (rotation) {    case Surface.ROTATION_0:        mDegree = 90;        break;    case Surface.ROTATION_90:        mDegree = 0;        break;    case Surface.ROTATION_180:        mDegree = 270;        break;    case Surface.ROTATION_270:        mDegree = 180;        break;    }    return mDegree;}

d) 我们可以通过调用Camera的tackPicture()方法进行拍照,如果我们需要对焦拍照的话,使用下面这段代码拍照

public void tackImage(){    if(mCamera == null){        Toast.makeText(NewCameraCheck.this, "获取不到相机实例,拍照失败!", Toast.LENGTH_SHORT).show();        return;    }    mCamera.autoFocus(new AutoFocusCallback() {        @Override        public void onAutoFocus(boolean success, Camera camera) {            mCamera.takePicture(null, null, new MyPictureCallback());            //mCamera.startPreview();        }    });}

e) 除此之外,我们相机适配需要注意清单文件中的配置,当然还要记得添加权限

清单文件配置:
权限配置:

4.自定义摄像

使用Camera+MediaRecoder + SurfaceView控件可实现录制视频的功能。

a)首先定义一个SurfaceView用作相机的预览

setContentView(R.layout.activity_main);SurfaceView mSurfaceView  = (SurfaceView) findViewById(R.id.surface);SurfaceHolder holder = mSurfaceView .getHolder();holder.addCallback(new MyVideoCallback());

b) 创建SurfaceCallback来开启预览,其中提供了三个回调方法,方便我们对相机预览进行管理。

class MyVideoCallback implements Callback{    @Override    public void surfaceCreated(SurfaceHolder holder) {        mCamera.open();        try {            mCamera.setPreviewDisplay(holder);            mCamera.startPreview();        } catch (IOException e) {            e.printStackTrace();        }    }    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width,            int height) {            }    @Override    public void surfaceDestroyed(SurfaceHolder holder) {        mCamera.stopPreview();        mCamera.release();    }}

c)开始录像需要设置大量的参数,代码中异常未处理,具体操作如下

// 停止预览mCamera.stopPreview();// 解锁摄像头mCamera.unlock();// 初始化MediaRecorder对象MediaRecorder recorder = new MediaRecorder();// 给recorder设置摄像头recorder.setCamera(mCamera);// 设置音频源recorder.setAudioSource(AudioSource.CAMCORDER);// 设置视频源recorder.setVideoSource(VideoSource.CAMERA);// 设置录像质量参数CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);recorder.setProfile(profile);// 设置录像输出路径recorder.setOutputFile(Environment.getExternalStorageDirectory().getAbsolutePath()+"/test.mp4");// 设置预览显示对象recorder.setPreviewDisplay(mHolder.getSurface());// 准备recorder.prepare();recorder.start();

d) 如果想停止录像的话,只需要调用如下代码:

mRecorder.stop();mRecorder.reset();mRecorder.release();mCamera.lock();mCamera.startPreview();

e) 当然别忘记添加权限

后续进阶可以参看:Android多媒体开发高级编程

三、拍照和相册获取图片

首先调用系统的相机或打开相册来获取图片

// 拍照btnCamera.setOnClickListener(new OnClickListener() {    @Override    public void onClick(View v) {        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(                Environment.getExternalStorageDirectory(), "temp.jpg")));        startActivityForResult(intent, PHOTOHRAPH);    }});// 从相册选择btnPhoto.setOnClickListener(new OnClickListener() {    @Override    public void onClick(View v) {        Intent intent = new Intent();        intent.setAction(Intent.ACTION_PICK);        intent.setType("image/*");        startActivityForResult(intent, PHOTOZOOM);    }});

在activity中的onActivityResult中进行回调获取到打开的图片

public static final int NONE = 0;public static final int PHOTOHRAPH = 1;// 拍照public static final int PHOTOZOOM = 2; // 缩放public static final int PHOTORESULE = 3;// 结果  @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {    switch (requestCode) {    case NONE:        return;    case PHOTOHRAPH:    // 拍照        // 设置文件保存路径这里放在根目录下        File picture = new File(Environment.getExternalStorageDirectory() + "/temp.jpg");        startPhotoZoom(Uri.fromFile(picture));        break;    case PHOTOZOOM:    // 从相册选择        startPhotoZoom(data.getData());        break;    case PHOTORESULE:        Bundle extras = data.getExtras();        if (extras != null) {            Bitmap photo = extras.getParcelable("data");            ByteArrayOutputStream stream = new ByteArrayOutputStream();            photo.compress(Bitmap.CompressFormat.JPEG, 75, stream);// (0 - 100)压缩文件                        // 设置头像显示            mIvPhoto.setImageBitmap(photo);            dialog.dismiss();        }        break;    }}

其中封装的方法startPhotoZoom,是对图片进行宽高和压缩等处理:

public void startPhotoZoom(Uri uri) {    Intent intent = new Intent("com.android.camera.action.CROP");    intent.setDataAndType(uri, "image/*");    intent.putExtra("crop", "true");    // aspectX aspectY 是宽高的比例    intent.putExtra("aspectX", 1);    intent.putExtra("aspectY", 1);    // outputX outputY 是裁剪图片宽高    intent.putExtra("outputX", 64);    intent.putExtra("outputY", 64);    intent.putExtra("return-data", true);    startActivityForResult(intent, PHOTORESULE);}

转载于:https://www.cnblogs.com/pengjingya/p/5509055.html

你可能感兴趣的文章
[SCOI2005][BZOJ 1084]最大子矩阵
查看>>
学习笔记之Data Visualization
查看>>
Leetcode 3. Longest Substring Without Repeating Characters
查看>>
416. Partition Equal Subset Sum
查看>>
app内部H5测试点总结
查看>>
[TC13761]Mutalisk
查看>>
while()
查看>>
常用限制input的方法
查看>>
IIS7下使用urlrewriter.dll配置
查看>>
并行程序设计学习心得1——并行计算机存储
查看>>
bulk
查看>>
C++ 迭代器运算
查看>>
【支持iOS11】UITableView左滑删除自定义 - 实现多选项并使用自定义图片
查看>>
【算法笔记】多线程斐波那契数列
查看>>
java8函数式编程实例
查看>>
jqgrid滚动条宽度/列显示不全问题
查看>>
在mac OS10.10下安装 cocoapods遇到的一些问题
查看>>
css技巧
查看>>
Tyvj 1728 普通平衡树
查看>>
javascript性能优化
查看>>