拍照版本之间的重要区别
6.0以下的版本:只要在AndroidManifest中注明相机的使用权限,SDCard的读写2权限,就能在正常的写功能了
6.0:6.0以后Google不再允许开发者直接或许应用的权限,需要在用户知情的情况下授予权限,这便是运行时权限。相机,读,写3个权限除了要在AndroidManifest先声明,在写功能代码的时候还要做动态申请的操作。
7.0+: 众所周知Android四大组件之一内容提供者的作用,便是在不同的应用程序之间实现数据共享, Android 从 N 版本开始禁止通过 file://Uri 的方式在不同 App 之间共享文件,如此以来7.0版本以下直接获取图片文件路径的方法行不通了,所以我们写的应用,就需要通过内容提供器的方式,来获取系统文件管理器中目标文件的路径。
6.0 以下版本调用相机拍照
权限
<uses-feature android:name="android.hardware.camera" />
<!--相机权限-->
<uses-permission android:name="android.permission.CAMERA" />
<!--写入SD卡的权限:如果你希望保存相机拍照后的照片-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--读取SD卡的权限:打开相册选取图片所必须的权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
拍照代码
/**
* 拍照
*
* @param view
*/
@Event(R.id.btn_photo)
private void takePhoto(View view) {
//启动照相机拍照
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
photoPath = Environment.getExternalStorageDirectory() + "/xyh.jpg";
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(photoPath)));
startActivityForResult(intent, PHOTO_REQUEST);
}
6.0 版本调用相机拍照
和6.0 以下版本相比多了一个动态申请权限,拍照代码一样
/**
* 拍照
*
* @param view
*/
@Event(R.id.btn_photo)
private void takePhoto(View view) {
//启动照相机拍照
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
photoPath = Environment.getExternalStorageDirectory() + "/xyh.jpg";
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(photoPath)));
startActivityForResult(intent, PHOTO_REQUEST);
}
7.0 版本调用相机拍照
在用Android 7.0版本时,出现了异常 FileUriExposedException,就是在传递uri时出错
百度了一下,说是Android 7.0后又修改了文件权限,可以使用FileProvider解决
1.在 res 目录下新建文件夹 xml 然后创建资源文件 filepaths(随意名字)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--external-path:代表的根目录: Environment.getExternalStorageDirectory()-->
<external-path
name="images"
path="拍照/" /> //存放照片文件夹
</resources>
<files-path/> //代表的根目录: Context.getFilesDir()
<external-path/> //代表的根目录: Environment.getExternalStorageDirectory()
<cache-path/> //代表的根目录: getCacheDir()
2.在manifest中添加provider
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.xiaoyehai.takephoto.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
exported:要求必须为false,为true则会报安全异常。grantUriPermissions:true,表示授予 URI 临时访问权限。
3.调用相机拍照,图片得存储吧,存储图片又需要权限,因此动态申请权限
AndroidManifest.xml文件中:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
4.java代码
/**
* android7.0拍照
*/
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_PERMISSION_CODE = 559;
private static final int REQUEST_CAMERA = 648;
private ImageView mImageView;
private String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
private File mFile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.iv);
}
public void doclick(View view) {
if (Build.VERSION.SDK_INT >= 23) {
if (ContextCompat.checkSelfPermission(this, permissions[0]) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, REQUEST_PERMISSION_CODE);
} else {
takePhoto();
}
} else {
takePhoto();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_PERMISSION_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
takePhoto();
} else {
// 没有获取 到权限,从新请求,或者关闭app
Toast.makeText(this, "需要存储权限", Toast.LENGTH_SHORT).show();
}
}
private void takePhoto() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
mFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+ "/拍照/" + System.currentTimeMillis() + ".jpg");
if (!mFile.getParentFile().exists()) {
mFile.getParentFile().mkdirs();
}
//改变Uri com.xiaoyehai.takephoto.fileprovider注意和清单文件中一致中的一致
Uri uri = FileProvider.getUriForFile(this, "com.xiaoyehai.takephoto.fileprovider", mFile);
//添加权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, REQUEST_CAMERA);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CAMERA && resultCode == RESULT_OK) {
Uri uri = FileProvider.getUriForFile(this, "com.xiaoyehai.takephoto.fileprovider", mFile);
//content://com.xiaoyehai.takephoto.fileprovider/images/1533796434054.jpg
Log.e("xyh", "onActivityResult: " + uri);
mImageView.setImageBitmap(BitmapFactory.decodeFile(mFile.getAbsolutePath()));
//在手机相册中显示刚拍摄的图片
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.fromFile(mFile);
mediaScanIntent.setData(contentUri);
sendBroadcast(mediaScanIntent);
}
}
}
如果图片太大,会造成OOM,可以对图片进行压缩
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CAMERA && resultCode == RESULT_OK) {
Uri uri = FileProvider.getUriForFile(this, "com.xiaoyehai.takephoto.fileprovider", mFile);
//content://com.xiaoyehai.takephoto.fileprovider/images/1533796434054.jpg
Log.e("xyh", "onActivityResult: " + uri);
//如果图片太大,可以对图片进行压缩
Bitmap bitmap = BitmapUtils.compressBitmap(mFile.getAbsolutePath(), mImageView.getMeasuredWidth(), mImageView.getMeasuredHeight());
mImageView.setImageBitmap(bitmap);
//在手机相册中显示刚拍摄的图片
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.fromFile(mFile);
mediaScanIntent.setData(contentUri);
sendBroadcast(mediaScanIntent);
}
}
BitmapUtils
package com.xiaoyehai.takephoto;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;
import java.io.ByteArrayOutputStream;
/**
* Created by xiaoyehai on 2018/3/2 0002.
*/
public class BitmapUtils {
/**
* Base64字符串转换成图片
*
* @param string
* @return
*/
public static Bitmap stringtoBitmap(String string) {
Bitmap bitmap = null;
try {
byte[] bitmapArray;
bitmapArray = Base64.decode(string, Base64.DEFAULT);
bitmap = BitmapFactory.decodeByteArray(bitmapArray, 0, bitmapArray.length);
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
/**
* 图片转换成Base64字符串
*
* @param bitmap
* @return
*/
public static String bitmaptoString(Bitmap bitmap) {
//将Bitmap转换成字符串
String string = null;
ByteArrayOutputStream bStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bStream);
byte[] bytes = bStream.toByteArray();
string = Base64.encodeToString(bytes, Base64.DEFAULT);
return string;
}
/**
* 对资源文件中的图片进行压缩
*
* @param context 上下文对象
* @param imageId 压缩图片的id
* @param destWidth 显示图片的控件宽度
* @param destHeight 显示图片的控件高度
* @return
*/
public static Bitmap compressBitmap(Context context, int imageId, int destWidth, int destHeight) {
//创建一个可选项对象,该对象用于配置图片的处理参数
BitmapFactory.Options options = new BitmapFactory.Options();
//第一次采样,该属性设置为true只会加载图片的边框进来,并不会加载图片具体的像素点,也就是说不会把图片加载到内存中
options.inJustDecodeBounds = true;
//加载图片,该方法只是从图片文件中读取图片的宽和高信息,而没有真正的加载到内存中
BitmapFactory.decodeResource(context.getResources(), imageId, options);
//获取图片的宽和高
int imageWidth = options.outWidth;
int imageHeight = options.outHeight;
//定义缩放比例
int sampleSize = 1;
while (imageWidth / sampleSize > destHeight || imageHeight / sampleSize > destWidth) {
//如果宽高的任意一方的缩放比例没有达到要求,都继续增大缩放比例
//sampleSize应该为2的n次幂,如果给sampleSize设置的数字不是2的n次幂,那么系统会就近取值
sampleSize *= 2;
}
//第二次采样,使用缩放比例进行缩放加载图,加载器就会返回图片了
options.inJustDecodeBounds = false;
//设置压缩比
options.inSampleSize = sampleSize;
/**
* inPreferredConfig设置图片的质量:
* ARGB_8888:默认的图片质量,也是最好的质量,32位,4个字节
* ARGB_4444:是ARGB_8888占用内存的一半,但质量比较低,16位,不推荐使用
* RGB_565:不带透明度,是ARGB_8888占用内存的一半,质量不错,16位,推荐使用
* ALPHA_8:
*/
//设置图片质量
options.inPreferredConfig = Bitmap.Config.RGB_565;
//返回压缩后的图片
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), imageId, options);
//Log.i("info", "压缩后图片的大小:" + bitmap.getByteCount());
return bitmap;
}
/**
* 对内存卡中的图片进行压缩
*
* @param path 图片路径
* @param destWidth 显示图片的控件宽度
* @param destHeight 显示图片的控件高度
* @return
*/
public static Bitmap compressBitmap(String path, int destWidth, int destHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
int imageWidth = options.outWidth;
int imageHeight = options.outHeight;
int sampleSize = 1;
while (imageWidth / sampleSize > destHeight || imageHeight / sampleSize > destWidth) {
sampleSize *= 2;
}
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
//Log.i("info", "压缩后图片的大小:" + bitmap.getByteCount());
return bitmap;
}
/**
* 通过网络请求返回的字节数据进行二次采样并返回bitmap
*
* @param bytes
* @param destWidth 显示图片的控件宽度
* @param destHeight 显示图片的控件高度
* @return
*/
public static Bitmap compressBitmap(byte[] bytes, int destWidth, int destHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
int imageWidth = options.outWidth;
int imageHeight = options.outHeight;
int sampleSize = 1;
while (imageWidth / sampleSize > destHeight || imageHeight / sampleSize > destWidth) {
sampleSize *= 2;
}
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
// Log.i("info", "压缩后图片的大小:" + bitmap.getByteCount());
return bitmap;
}
}
最终版本:兼容代码
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_PERMISSION_CODE = 559;
private static final int REQUEST_CAMERA = 648;
//注意和清单文件中的一致
private String authority = "com.xiaoyehai.demo.fileprovider";
private ImageView mImageView;
private String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
private File mImageFile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.iv);
//申请权限
if (ContextCompat.checkSelfPermission(this, permissions[0]) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, REQUEST_PERMISSION_CODE);
}
}
/**
* 拍照
*
* @param view
*/
public void takePhoto(View view) {
//创建存储照片的文件
mImageFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+ "/拍照/" + System.currentTimeMillis() + ".jpg");
// /storage/emulated/0/拍照/1571752870386.jpg
Log.e("xyh", "takePhoto: " + mImageFile);
if (!mImageFile.getParentFile().exists()) {
mImageFile.getParentFile().mkdirs();
}
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//Android 7.0及以上获取文件 Uri
uri = FileProvider.getUriForFile(this, authority, mImageFile);
} else {
//Android 7.0以前获取文件 Uri
uri = Uri.fromFile(mImageFile);
}
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, REQUEST_CAMERA);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CAMERA && resultCode == RESULT_OK) {
Uri uri = FileProvider.getUriForFile(this, authority, mImageFile);
//content://com.xiaoyehai.demo.fileprovider/images/1571752746254.jpg
Log.e("xyh", "onActivityResult: " + uri);
Bitmap bitmap = BitmapFactory.decodeFile(mImageFile.getAbsolutePath());
Log.e("xyh", "压缩前图片大小:" + bitmap.getByteCount());
//如果图片太大,会造成OOM,可以对图片进行压缩
//使用Luban图片压缩框架对图片进行压缩
Luban.with(this)
.load(mImageFile)
.ignoreBy(100)
.setTargetDir(getPath())
.filter(new CompressionPredicate() {
@Override
public boolean apply(String path) {
return !(TextUtils.isEmpty(path) || path.toLowerCase().endsWith(".gif"));
}
})
.setCompressListener(new OnCompressListener() {
@Override
public void onStart() {
// TODO 压缩开始前调用,可以在方法内启动 loading UI
}
@Override
public void onSuccess(File file) {
// TODO 压缩成功后调用,返回压缩后的图片文件
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
Log.e("xyh", "压缩后图片大小:" + bitmap.getByteCount());
mImageView.setImageBitmap(bitmap);
}
@Override
public void onError(Throwable e) {
// TODO 当压缩过程出现问题时调用
Log.e("xyh", "onError: " + e.getMessage());
}
}).launch();
//在手机相册中显示刚拍摄的图片
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.fromFile(mImageFile);
mediaScanIntent.setData(contentUri);
sendBroadcast(mediaScanIntent);
}
}
private String getPath() {
// /storage/emulated/0/拍照
String path = mImageFile.getParentFile().getAbsolutePath();
Log.e("xyh", "getPath: " + path);
return path;
}
}