Android调用系统 拍照 相册 适配所有版本 7.0 恢复自动旋转
来源:互联网 发布:比特精灵 for mac 编辑:程序博客网 时间:2024/06/06 14:09
源代码
项目中经常会用到拍照和选取相册图片的功能,其中的坑很多,所以总结了一下,方便以后使用。该博客优点为:
1.适配到Android7.1;
2.有些手机拍照后自动旋转照片,该博客将照片恢复到未旋转状态;
3.修复有些手机“Bitmap too large to be uploaded into a texture”问题;
首先新建一个工程,编写布局文件
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/bt_take_photo" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:text="拍照" /> <Button android:id="@+id/bt_album" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/bt_take_photo" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:text="相册" /> <ImageView android:id="@+id/iv_picture" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@id/bt_album" android:layout_margin="10dp" android:src="@mipmap/ic_launcher" /></RelativeLayout>
布局文件很简单,两个按钮和一个ImageView,ImageView用于显示拍照后或者在相册中选取的图片。
编写MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final int TAKE_PHOTO = 1; private static final int CHOOSE_PHOTO = 2; private static final int APPLY_PERMISSION = 3; private Button btTakePhoto; private Button btAlbum; private ImageView ivPicture; //拍照后相片的Uri private Uri imageUri; //拍照后相片path private String imagePath; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btTakePhoto = (Button) findViewById(R.id.bt_take_photo); btAlbum = (Button) findViewById(R.id.bt_album); ivPicture = (ImageView) findViewById(R.id.iv_picture); btAlbum.setOnClickListener(this); btTakePhoto.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.bt_take_photo: /** * 创建File对象,用于存储拍照后的图片 * 调用getExternalCacheDir方法可以得到应用关联目录 /sdcard/Android/data/<package name>/cache * 为何调用getExternalCacheDir? * 因为Android从6.0开始。读写SD卡被认为是危险权限。如果将图片放在其他目录,需要动态申请权限 * 而使用关联目录可以跳过这一步 * */ imagePath = getExternalCacheDir() + "/output_image.jpg"; File outputImage = new File(imagePath); try { if (outputImage.exists()) outputImage.delete(); outputImage.createNewFile(); } catch (IOException e) { e.printStackTrace(); } if (Build.VERSION.SDK_INT >= 24) { /** * 从Android7.0开始,直接使用本地的真是路径的URI被认为是不安全的,会抛出FileUriExposeException * 而FileProvider是一种特殊的ContentProvider,可以给外部选择性的分享Uri,提高安全性*/ imageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.testing.picturetest", outputImage); } else { imageUri = Uri.fromFile(outputImage); } //启动相机 Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); startActivityForResult(intent, TAKE_PHOTO); break; case R.id.bt_album: //Android6.0开始,WRITE_EXTERNAL_STORAGE被认为是危险权限,需要动态申请 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE}, APPLY_PERMISSION); } else openAlbum(); break; } } private void openAlbum() { Intent intent = new Intent("android.intent.action.GET_CONTENT"); intent.setType("image/*"); startActivityForResult(intent, CHOOSE_PHOTO); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case TAKE_PHOTO: if (resultCode == RESULT_OK) { try { Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri)); //某些手机拍完照之后会自动旋转照片,以下代码把图片还原为旋转前状态 int degree = PhotoRotateUtil.getBitmapDegree(imagePath); bitmap = rotateBitmapByDegree(bitmap, degree); ivPicture.setImageBitmap(bitmap); } catch (FileNotFoundException e) { e.printStackTrace(); } } break; case CHOOSE_PHOTO: if (resultCode == RESULT_OK) { String path = ""; if (Build.VERSION.SDK_INT >= 19) { //4.4及以上系统使用这个方法处理图片 path = handleImageOnKitKat(data); } else { //4.4以下使用这个方法处理图片 path = handleImageBeforeKitKat(data); } Bitmap bitmap = BitmapFactory.decodeFile(path); int degree = PhotoRotateUtil.getBitmapDegree(path); bitmap = rotateBitmapByDegree(bitmap, degree); ivPicture.setImageBitmap(bitmap); } break; default: break; } } private String handleImageBeforeKitKat(Intent data) { Uri uri = data.getData(); String imagePath = getImagePath(uri, null); return imagePath; } @RequiresApi(api = Build.VERSION_CODES.KITKAT) private String handleImageOnKitKat(Intent data) { String imgPath = null; Uri uri = data.getData(); if (DocumentsContract.isDocumentUri(this, uri)) { //如果是document类型的Uri,则通过document id处理 String docId = DocumentsContract.getDocumentId(uri); if ("com.android.providers.media.documents".equals(uri.getAuthority())) { String id = docId.split(":")[1];//解析出数字格式的id String selection = MediaStore.Images.Media._ID + "=" + id; imgPath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection); } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) { Uri contenturi = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId)); imgPath = getImagePath(contenturi, null); } } else if ("content".equalsIgnoreCase(uri.getScheme())) { //如果是content类型的uri,使用普通方法处理 imgPath = getImagePath(uri, null); } else if ("file".equalsIgnoreCase(uri.getScheme())) { //如果是File类型的Uri,直接获取图片路径即可 imgPath = uri.getPath(); } return imgPath; } //通过Uri和selection来获取真实的图片路径 private String getImagePath(Uri uri, String selection) { String path = null; Cursor cursor = getContentResolver().query(uri, null, selection, null, null); if (cursor != null) { if (cursor.moveToFirst()) { path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); } cursor.close(); } return path; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { case APPLY_PERMISSION: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { openAlbum(); } break; } }}
大多数需要注意的问题已经在代码中解释过了,大家看一下就能明白。其中还需要注意的是:
1.从Android7.0开始,直接使用本地真实路径的URI被认为是不安全的,会抛出FileUriExposeException而FileProvider是一种特殊的ContentProvider,可以给外部选择性的分享Uri,提高安全性。
使用FileProvider还需要其他配置。首先在AndroidManifest中进行注册:
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" android:hardwareAccelerated="false"> ··· <provider android:authorities="com.example.testing.picturetest" android:name="android.support.v4.content.FileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data> </provider> </application>
其中name属性是固定的,authorities必须要和刚才FileProvider.getUriForFile方法的第二个参数一致。另外还要在provider标签内使用meta-data来制定Uri的共享路径,并引用一个@xml/file_paths资源。该资源的代码为:
<?xml version="1.0" encoding="utf-8"?><paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="my_images" path=""/></paths>
2. 在Android4.4之前,访问SD卡的应用关联目录也要声明权限,从4.4开始不再需要声明权限,所以在AndroidMinifest中添加:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
3.部分手机(例如三星galaxys7)拍照之后,或者在相册中选取的照片旋转过的,所以要恢复到旋转之前的状态,代码如下:
public class PhotoRotateUtil { /** * 读取图片的旋转的角度 * * @param path 图片绝对路径 * @return 图片的旋转角度 */ public static int getBitmapDegree(String path) { int degree = 0; try { // 从指定路径下读取图片,并获取其EXIF信息 ExifInterface exifInterface = new ExifInterface(path); // 获取图片的旋转信息 int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_90: degree = 90; break; case ExifInterface.ORIENTATION_ROTATE_180: degree = 180; break; case ExifInterface.ORIENTATION_ROTATE_270: degree = 270; break; } } catch (IOException e) { e.printStackTrace(); } return degree; } /** * 将图片按照某个角度进行旋转 * * @param bm * 需要旋转的图片 * @param degree * 旋转角度 * @return 旋转后的图片 */ public static Bitmap rotateBitmapByDegree(Bitmap bm, int degree) { Bitmap returnBm = null; // 根据旋转角度,生成旋转矩阵 Matrix matrix = new Matrix(); matrix.postRotate(degree); try { // 将原始图片按照旋转矩阵进行旋转,并得到新的图片 returnBm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true); } catch (OutOfMemoryError e) { } if (returnBm == null) { returnBm = bm; } if (bm != returnBm) { bm.recycle(); } return returnBm; }}
4. Android从4.4开始,选取相册中的图片不再返回图片真实的Uri,而是一个封装过后的Uri,因此4.4以上的版本,需要对Uri解析才行(handleImageOnKitKat);
5. 硬件加速的时候,对bitmap的大小有限制,而且每个手机限制的都不一样,超过限制的大小就报错:Bitmap too large to be uploaded into a texture,其中解决办法也不止一种,经过总结后,发现最简单的也是最有效的就是关闭硬件加速(Android对硬件加速的支持并非完美,有些绘制操作在开启硬件加速的情况下不能正常工作,具体的列表可以参考Android开发者文档):
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" android:hardwareAccelerated="false"> ··· </application>
大功告成!
如果有疑问,欢迎评论,谢谢大家!
源代码
- Android调用系统 拍照 相册 适配所有版本 7.0 恢复自动旋转
- MVP模式的Android 调用系统拍照,相册,剪裁,适配到7.0,修复拍照图片旋转问题
- Android 7.0 调用系统拍照,相册,剪裁,修复拍照图片旋转问题
- Android调用系统相册和相机拍照
- Android开发之调用相机拍照和调用系统相册
- Android调用系统相册和系统相机拍照
- android 调用系统相册和系统拍照功能的Demo
- android 调用系统照相机拍照后保存到系统相册
- Android 调用相册 拍照 实现系统控件缩放 切割图片
- Android 调用相册 拍照 实现系统控件缩放 切割图片
- Android 调用相册 拍照 实现系统控件缩放 切割图片
- Android 调用相册 拍照 实现系统控件缩放 切割图片
- Android 调用相册 拍照 实现系统控件缩放 切割图片
- Android 调用相册 拍照 实现系统控件缩放 切割图片 .
- Android 调用相册 拍照 实现系统控件缩放 切割图片 .
- Android 调用相册 拍照 实现系统控件缩放 切割图片
- Android 调用相册 拍照 实现系统控件缩放 切割图片
- Android 调用相册 拍照 实现系统控件缩放 切割图片
- Java 修饰符
- 如何获取SD卡内存
- 用C语言实现通讯录
- 如何基于TI AM437x芯片快速设计工业通信产品
- API的四种类型
- Android调用系统 拍照 相册 适配所有版本 7.0 恢复自动旋转
- WORD2013 如何编辑章节标题以及修改样式
- Error:Execution failed for task ':demo_project:transformNative_libsWithStripDebugSymbolForDebug'. >
- 免安装版MySQL的优化与配置
- Ajax 接收服务器返回的json响应
- Lua游戏内存泄漏检查
- Zookeeper的Watcher
- 创建手机页面
- 梯度下降的一阶泰勒公式展开证明