本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,而且因为涉及到要把拍到的照片显示出来,该样例也会涉及到Android载入大图片时候的处理(避免OOM),还有简要提一下有些人SurfaceView出现黑屏的原因。
Android应用拍照的两种方式,以下为两种形式的Demo展示出来的效果。
知识点:
一、调用系统自带的相机应用二、自己定义我们自己的拍照界面
三、关于计算机解析图片原理(怎样正确载入图片到Android应用中)
所需权限:
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
一、调用系统照相机程序拍照(方式一)
1.定义所须要的权限
2.我们须要定义调用系统相机App的Intent,当然是通过设定IntentFilter中的Action来打开我们想要的activity了。
MediaStore.ACTION_IMAGE_CAPTURE - 这个Action将打开拍照的系统相机。返回一个Image
MediaStore.ACTION_VIDEO_CAPTURE - 这个Action将打开录像的系统相机。返回一个Video
在MediaStore.ACTION_IMAGE_CAPTURE中,我们能够看到这段话:
【The caller may pass an extra EXTRA_OUTPUT to control where this image will be
written. If the EXTRA_OUTPUT is not present, then a small sized image is returned
as a Bitmap object in the extra field. This is useful for applications that only
need a small image. If the EXTRA_OUTPUT is present, then the full-sized image will
be written to the Uri value of EXTRA_OUTPUT.】
3.API规定我们传入拍照得到图片的存储位置的Uri。否则Bimmap将以一个压缩后的形式返回到我们当前Activity.
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name
则会把拍照的图片存储到我们传入的Uri相应的File里面。
4.我们调用startActivityForResult(intent)来启动这样一个系统相机app之后。然后在当前应用Activity的onActivityResult()中接受到返回拍照成功或者失败的消息,做相应处理。
5.“压缩处理”(Android应用中载入大图片),并显示到ImageView中。
二、自己定义照相机
1.检查相机是否存在,并获取相机Camera。
2.创建一个相机图像预览类:extends SurfaceView 并 implements SurfaceHolder (我定义:MySurfaceView)3.把这个预览类放入一个自己定义布局layout里面,而且能够在layout里加入自己的其它button
4.设置相应的拍照button然后听事件
5.捕获照片和保存图片
6.释放掉我们使用的相机Camera,不然之后其它应用将无法使用它。
三、计算机解析图片的方式和Android中大图片Bitmap的压缩显示处理
这个问题有点老生长谈了,平时我们常常遇到一些图片资源。我们把它载入到内存发现抛出内存不够用的异常,即OOM。当然载入图片时出现的OOM情况有非常多种,比方单张图片没有做压缩,导致图片占用内存过大而发生内存溢出。也有多张图片一次性载入进来。导致的内存溢出。
通常单张大图。我们载入进来往往会经过一个图片的压缩处理的过程。而假设多张图片载入,我们可能就须要一些缓存机制。再加上一些算法来保证程序不出现OOM。
我们这里想要讲的知识点跟单张大图比較有关系
首先,我们知道一个图片,它是由非常多像素点来表示的,而像素点的个数仅仅跟图片的分辨率有关。而跟图片所占的内存空间大小无关。比方我们的桌面壁纸:1280 * 768 的分辨率,那么它就有 1280 * 768 = 983040个像素点,这意味着什么呢?我们知道我们要表示一个像素点的颜色。最常常我们须要RGB三种颜色来表示,而R:0~255,相当于两个FF的位置,就是8位。这种话RGB合起来,一个像素点的表示就须要24位(这就是我们平衡听到的24位图),而加上透明度的8位,就是平时说的32位图。那么一张图片,它载入到内存中的话,它会占用多大的空间呢?
计算方法:(像素点 * 一个像素所占用的byte数) / 1024 / 1024 (MB)
以1280 * 768 的分辨率。32位图为例:所占内存大小: ((1280 * 768 * (32 / 8)) / 1024)/1024=3.75(MB)
说了这么多,那么我们再来说下Android系统的规定吧。Android系统严格规定了每一个应用所能分配的最大的内存为多少,我们知道有一个VM值(在我们创建模拟器的时候),这个VM值里面便是我们所说的堆空间(Heap Size),当你的应用占用的空间已经超出我们定义的堆空间大小,那么不好意思,OOM
这种话,我们明确了图片的大小占领原理,还有尽量不要超出这个堆空间,那么OK,如今问题变得简单了。假设我们有一种方式能够在图片载入进来之前,知道图片的大小。然后改变它的长、宽,这种话,分辨率便变小了,这样出来的乘积也就变小了。比方:我们的屏幕仅仅有320 * 240。 这时候你载入大分辨的图片进来最多也仅仅能显示成这样,所以我们常採用的是对图片进行压缩处理。这里有个概念叫压缩比:
长:1024 / 320 = 3.2 约等于 3
宽:768 / 240 = 3.2
那这样我们假设把图片压缩成这样大小的,最后的图片载入进来的大小便是
((320 * 240 * (32 / 8)) / 1024)/1024=0.29(MB)
希望我这样讲完,大家都能听懂了。我这里先把照相机实例中出现的关于假设处理这块图片的代码先粘出来
//-----------------------Android大图的处理方式--------------------------- private void setPicToImageView(ImageView imageView, File imageFile){ int imageViewWidth = imageView.getWidth(); int imageViewHeight = imageView.getHeight(); BitmapFactory.Options opts = new Options(); //设置这个,仅仅得到Bitmap的属性信息放入opts,而不把Bitmap载入到内存中 opts.inJustDecodeBounds = true; BitmapFactory.decodeFile(imageFile.getPath(), opts); int bitmapWidth = opts.outWidth; int bitmapHeight = opts.outHeight; //取最大的比例。保证整个图片的长或者宽必然在该屏幕中能够显示得下 int scale = Math.max(imageViewWidth / bitmapWidth, imageViewHeight / bitmapHeight); //缩放的比例 opts.inSampleSize = scale; //内存不足时可被回收 opts.inPurgeable = true; //设置为false,表示不仅Bitmap的属性,也要载入bitmap opts.inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getPath(), opts); imageView.setImageBitmap(bitmap); }
关于堆空间:
堆(HEAP)是VM中占用内存最多的部分。一般是动态分配的。堆的大小不是一成不变的,通常有一个分配机制来控制它的大小。
比方初始的HEAP是4M大,当4M的空间被占用超过75%的时候,又一次分配堆为8M大。当8M被占用超过75%,分配堆为16M大。倒过来,当16M的堆利用不足30%的时候,缩减它的大小为8M大。
又一次设置堆的大小,尤其是压缩,通常会涉及到内存的拷贝,所以变更堆的大小对效率有不良影响。
废话少说以下就看代码咯~~为了大家看起来方便点,代码的结构可能不是非常规范!
源代码下载地址:http://download.csdn.net/detail/u011133213/7844683
代码部分:
一、用系统的相机
button点击之后开启系统相机Activity
findViewById(R.id.system_camera_btn).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); imageFileUri = getOutFileUri(TYPE_FILE_IMAGE);//得到一个File Uri intent.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri); startActivityForResult(intent, SYSTEM_CAMERA_REQUESTCODE); } });
生成File文件,并得到Uri
//-----------------------生成Uri--------------------------------------- //得到输出文件的URI private Uri getOutFileUri(int fileType) { return Uri.fromFile(getOutFile(fileType)); } //生成输出文件 private File getOutFile(int fileType) { String storageState = Environment.getExternalStorageState(); if (Environment.MEDIA_REMOVED.equals(storageState)){ Toast.makeText(getApplicationContext(), "oh,no, SD卡不存在", Toast.LENGTH_SHORT).show(); return null; } File mediaStorageDir = new File (Environment .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) ,"MyPictures"); if (!mediaStorageDir.exists()){ if (!mediaStorageDir.mkdirs()){ Log.i("MyPictures", "创建图片存储路径文件夹失败"); Log.i("MyPictures", "mediaStorageDir : " + mediaStorageDir.getPath()); return null; } } File file = new File(getFilePath(mediaStorageDir,fileType)); return file; } //生成输出文件路径 private String getFilePath(File mediaStorageDir, int fileType){ String timeStamp =new SimpleDateFormat("yyyyMMdd_HHmmss") .format(new Date()); String filePath = mediaStorageDir.getPath() + File.separator; if (fileType == TYPE_FILE_IMAGE){ filePath += ("IMG_" + timeStamp + ".jpg"); }else if (fileType == TYPE_FILE_VEDIO){ filePath += ("VIDEO_" + timeStamp + ".mp4"); }else{ return null; } return filePath; }
二、用自己定义的相机
检測相机设备是否存在:
/*检測相机是否存在*/ private boolean checkCameraHardWare(Context context){ PackageManager packageManager = context.getPackageManager(); if (packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)){ return true; } return false; }
button按下之后的推断:
findViewById(R.id.myapp_camera_btn).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (checkCameraHardWare(getApplicationContext())){ Intent intent = new Intent(getApplicationContext(), MyCameraActivity.class); startActivity(intent); }else{ Toast.makeText(getApplicationContext(), "没有相机存在", Toast.LENGTH_SHORT).show(); } } });
自己定义的SurfaceView类:
package cn.panghu.camera; import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.hardware.Camera; import android.util.AttributeSet; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback{ private Camera camera = null; private SurfaceHolder surfaceHolder = null; public MySurfaceView(Context context, Camera camera) { super(context); this.camera = camera; surfaceHolder = getHolder(); surfaceHolder.addCallback(this); surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } public MySurfaceView(Context context) { super(context); // TODO Auto-generated constructor stub } @Override public void surfaceCreated(SurfaceHolder holder) { try{ camera.setPreviewDisplay(surfaceHolder); camera.startPreview(); }catch(Exception e){ e.printStackTrace(); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { //根本没有可处理的SurfaceView if (surfaceHolder.getSurface() == null){ return ; } //先停止Camera的预览 try{ camera.stopPreview(); }catch(Exception e){ e.printStackTrace(); } //这里能够做一些我们要做的变换。//又一次开启Camera的预览功能 try{ camera.setPreviewDisplay(surfaceHolder); camera.startPreview(); }catch(Exception e){ e.printStackTrace(); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { } }
自己定义相机Activity类:(为了避免当用户按下Home键,之后再回到我们App中,SurfaceView变黑屏。我们须要将SurfaceView载入到FrameLayout中的代码写在onResume中)
package cn.panghu.camera; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import android.app.Activity; import android.hardware.Camera; import android.hardware.Camera.PictureCallback; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.FrameLayout; import android.widget.Toast; import com.example.camerademoapp.R; public class MyCameraActivity extends Activity { private Button btn_camera_capture = null; private Button btn_camera_cancel = null; private Button btn_camera_ok = null; private Camera camera = null; private MySurfaceView mySurfaceView = null; private byte[] buffer = null; private final int TYPE_FILE_IMAGE = 1; private final int TYPE_FILE_VEDIO = 2; private PictureCallback pictureCallback = new PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { if (data == null){ Log.i("MyPicture", "picture taken data: null"); }else{ Log.i("MyPicture", "picture taken data: " + data.length); } buffer = new byte[data.length]; buffer = data.clone(); } }; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.mycamera_layout); btn_camera_capture = (Button) findViewById(R.id.camera_capture); btn_camera_ok = (Button) findViewById(R.id.camera_ok); btn_camera_cancel = (Button) findViewById(R.id.camera_cancel); btn_camera_capture.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { camera.takePicture(null, null, pictureCallback); btn_camera_capture.setVisibility(View.INVISIBLE); btn_camera_ok.setVisibility(View.VISIBLE); btn_camera_cancel.setVisibility(View.VISIBLE); } }); btn_camera_ok.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //保存图片 saveImageToFile(); camera.startPreview(); btn_camera_capture.setVisibility(View.VISIBLE); btn_camera_ok.setVisibility(View.INVISIBLE); btn_camera_cancel.setVisibility(View.INVISIBLE); } }); btn_camera_cancel.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { camera.startPreview(); btn_camera_capture.setVisibility(View.VISIBLE); btn_camera_ok.setVisibility(View.INVISIBLE); btn_camera_cancel.setVisibility(View.INVISIBLE); } }); } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); camera.release(); camera = null; } @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); if (camera == null){ camera = getCameraInstance(); } //必须放在onResume中,不然会出现Home键之后。再回到该APP。黑屏 mySurfaceView = new MySurfaceView(getApplicationContext(), camera); FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview); preview.addView(mySurfaceView); } /*得到一相机对象*/ private Camera getCameraInstance(){ Camera camera = null; try{ camera = camera.open(); }catch(Exception e){ e.printStackTrace(); } return camera; } //-----------------------保存图片--------------------------------------- private void saveImageToFile(){ File file = getOutFile(TYPE_FILE_IMAGE); if (file == null){ Toast.makeText(getApplicationContext(), "文件创建失败,请检查SD卡读写权限", Toast.LENGTH_SHORT).show(); return ; } Log.i("MyPicture", "自己定义相机图片路径:" + file.getPath()); Toast.makeText(getApplicationContext(), "图片保存路径:" + file.getPath(), Toast.LENGTH_SHORT).show(); if (buffer == null){ Log.i("MyPicture", "自己定义相机Buffer: null"); }else{ try{ FileOutputStream fos = new FileOutputStream(file); fos.write(buffer); fos.close(); }catch(IOException e){ e.printStackTrace(); } } } //-----------------------生成Uri--------------------------------------- //得到输出文件的URI private Uri getOutFileUri(int fileType) { return Uri.fromFile(getOutFile(fileType)); } //生成输出文件 private File getOutFile(int fileType) { String storageState = Environment.getExternalStorageState(); if (Environment.MEDIA_REMOVED.equals(storageState)){ Toast.makeText(getApplicationContext(), "oh,no, SD卡不存在", Toast.LENGTH_SHORT).show(); return null; } File mediaStorageDir = new File (Environment .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) ,"MyPictures"); if (!mediaStorageDir.exists()){ if (!mediaStorageDir.mkdirs()){ Log.i("MyPictures", "创建图片存储路径文件夹失败"); Log.i("MyPictures", "mediaStorageDir : " + mediaStorageDir.getPath()); return null; } } File file = new File(getFilePath(mediaStorageDir,fileType)); return file; } //生成输出文件路径 private String getFilePath(File mediaStorageDir, int fileType){ String timeStamp =new SimpleDateFormat("yyyyMMdd_HHmmss") .format(new Date()); String filePath = mediaStorageDir.getPath() + File.separator; if (fileType == TYPE_FILE_IMAGE){ filePath += ("IMG_" + timeStamp + ".jpg"); }else if (fileType == TYPE_FILE_VEDIO){ filePath += ("VIDEO_" + timeStamp + ".mp4"); }else{ return null; } return filePath; } }