当前位置:   article > 正文

Android 调用系统相机、获取图片适配方案及常见问题汇总

Android 调用系统相机、获取图片适配方案及常见问题汇总

背景:在维护公司移动办公平台APP 时,由于项目太过老旧,在兼容到最新版本时,出现了拍照、选择图片等问题,在此记录一下遇到的问题及解决的方案。

调用系统相机拍照方案:

Tips:记得申请权限和做运行时权限处理

  1. btn.setOnClickListener(new View.OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. //在应用关联缓存目录中新建 output_image.jpg 图片文件,因为Android6.0之后,读取SDCard被列为危险权限,如将图片存放在
  5. //SD卡的任何其他目录,均需进行运行时权限处理,而使用应用关联目录则可跳过该步骤,同时应用卸载会删除该目录
  6. //具体路劲:/sdcard/Android/data/<pacaage name>/cache
  7. //创建File 对象,用于存储拍摄的照片,存储在手机SD 卡的应用关联缓存目录下
  8. File outputImage = new File(getExternalCacheDir(), "output_image.jpg");
  9. try {
  10. if (outputImage.exists()) {
  11. outputImage.delete();
  12. }
  13. outputImage.createNewFile();
  14. } catch (IOException e) {
  15. e.printStackTrace();
  16. }
  17. //系统版本判断
  18. if (Build.VERSION.SDK_INT >= 24) {
  19. //系统版本高于 Android7.0,者调用 FileProvider.getUriForFile() 方法,将file对象转换为一个封装过的Uri对象,
  20. // 参数一:Context 参数二:任意唯一的字符串 参数三:file对象
  21. imageUri = FileProvider.getUriForFile(MainActivity.this, "com.cloudc.notificationtest.provider", outputImage);
  22. } else {
  23. //系统版本低于 Android7.0,就调用Uri.fromFile()方法,将file对象转换为Uri对象,该Uri对象标志着 output_image.jpg 图片
  24. //的真实路劲,1
  25. imageUri = Uri.fromFile(outputImage);
  26. }
  27. //启动相机程序 隐式调用系统相机
  28. Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
  29. //添加这一句表示对目标应用临时授权该Uri所代表的文件
  30. intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
  31. //指定图片的输出地址
  32. intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
  33. startActivityForResult(intent, TAKE_PHOTO);
  34. }
  35. });

因为从 Android7.0 开始,直接使用本地真实路劲的 Uri 被认为是不安全的,会抛出一个FileUriExposedException 异常,而FileProvider则是一种特殊的内容提供器来对数据进行保护,可以选择行的将封装过的 Uri 共享给外部,从而提高了应用的安全性。

注意:在Android 4.4 之前,访问SD 卡的关联目录也是需要声明权限的,从4.4 系统开始不在需要权限声明

xml / file_path.xml文件:在res/xml 文件夹下
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <paths xmlns:android="http://schemas.android.com/apk/res/android">
  3. <!--external-path 用来指定 Uri 共享,
  4. name 属性可随便填写,
  5. path 属性值表示共享的具体路径,为空表示将整个SD 卡共享
  6. path:你所共享的子目录。虽然name属性是一个URI路径片段,但是path是一个真实的子目录名。
  7. 注意,path是一个子目录,而不是单个文件或者多个文件。
  8. -->
  9. <external-path name="my_images"
  10. path="."/>
  11. </paths>

Manifest.xml文件

  1. ....
  2. <!--name 属性固定
  3. authorities 属性必须和代码中 FileProvider.getUriForFile()中的第二个参数一致
  4. -->
  5. <!--meta-data 中指定了Uri 共享路径,并引用了一个xml文件资源-->
  6. <provider android:name="android.support.v4.content.FileProvider"
  7. android:authorities="com.example.yhadmin.gank.test.Test2Activity.fileprovider"
  8. android:exported="false"
  9. android:grantUriPermissions="true">
  10. <meta-data
  11. android:name="android.support.FILE_PROVIDER_PATHS"
  12. android:resource="@xml/file_paths">
  13. </meta-data>
  14. </provider>
  15. ....

获取图片兼容方案:

前提得获取SDCard 读写权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

部分代码

  1. @Override
  2. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  3. switch (requestCode) {
  4. case CHOOSE_PHOTO: {
  5. if (resultCode == RESULT_OK) {
  6. //判断手机系统版本号
  7. if (Build.VERSION.SDK_INT >= 19) {
  8. //4.4及以上系统使用此方法处理图片
  9. handleImageOnKitKat(data);
  10. } else {
  11. //4.4以下系统使用此方法处理图像
  12. handleImageBeforeKitKat(data);
  13. }
  14. }
  15. break;
  16. }
  17. default:
  18. break;
  19. }
  20. }
  21. //4.4以下系统使用此方法处理图像
  22. private void handleImageBeforeKitKat(Intent data) {
  23. Uri uri = data.getData();
  24. String imagePath = getImagePath(uri, null);
  25. displayImage(imagePath);
  26. }
  27. @TargetApi(19)//4.4及以上系统使用此方法处理图片
  28. private void handleImageOnKitKat(Intent data) {
  29. String imagePath = null;
  30. Uri uri = data.getData();
  31. Logger.t("test2Activity").d("URI:"+uri.toString());
  32. if (DocumentsContract.isDocumentUri(this, uri)) {
  33. //如果是 document 类型的 Uri,则通过 document id 处理
  34. String documentId = DocumentsContract.getDocumentId(uri);
  35. Logger.t("test2Activity").d("documentId:"+uri.toString());
  36. if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
  37. //解析出数字格式的 id
  38. String id = documentId.split(":")[1];
  39. String selection = MediaStore.Images.Media._ID + "=" + id;
  40. imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
  41. }
  42. } else if ("content".equalsIgnoreCase(uri.getScheme())) {
  43. //如果是 content 类型的 Uri,则使用普通方式处理
  44. Logger.t("test2Activity").d("content:"+uri.getScheme());
  45. imagePath = getImagePath(uri, null);
  46. } else if ("file".equalsIgnoreCase(uri.getScheme())) {
  47. //如果是 Fiel 类型的 Uri,直接获取图片路径即可
  48. Logger.t("test2Activity").d("uri.getScheme():"+uri.getScheme());
  49. imagePath = uri.getPath();
  50. }
  51. displayImage(imagePath);//加载图片并显示
  52. }
  53.     //加载图片
  54. private void displayImage(String imagePath) {
  55. if (imagePath!=null){
  56. Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
  57. bindingView.picture.setImageBitmap(bitmap);
  58. }else {
  59. ToastHelper.show(Test2Activity.this,"获取图片失败!");
  60. }
  61. }
  62.     //获取图片路径
  63. private String getImagePath(Uri uri, String selection) {
  64. String path = null;
  65. Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
  66. if (cursor != null) {
  67. if (cursor.moveToFirst()) {
  68. path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
  69. }
  70. cursor.close();
  71. }
  72. return path;
  73. }


关于调用系统照相机不执行OnActivityResult的解决方式

1、确认权限是否添加

  1. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  2. <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
2、Activity 的加载模式
这与 Activity 的加载模式(launchMode)有关,该属性可以在 AndroidManifest.xml 中设置。
原先将其设为 singleInstance,经测试,所有需要传递或接收的 Activity 不允许设置该属性,或只能设为标准模式,否则系统将在 startActivityForResult() 后直接调用 onActivityResult()。

3、调用startActivityForResult的参数问题,
即调用时这样: startActivityForResult(intent, 0); 是第二个参数的问题,该参数必须大于等于0才能在返回值,并激活onActivityResult方法。
我最开始是用的一个activity默认的常量:RESULT_OK,跟踪了代码后发现,该常量的值为-1,当然没法激活 onActivityResult方法了,随后随便修改为一个大于0的整数,程序即通跑成功。
startActivityForResult(intent, 1); //这样就行了

API描述:
  1. /**
  2. * Same as calling {@link #startActivityForResult(Intent, int, Bundle)}
  3. * with no options.
  4. *
  5. * @param intent The intent to start.
  6. * @param requestCode If >= 0, this code will be returned in
  7. * onActivityResult() when the activity exits.
  8. *
  9. * @throws android.content.ActivityNotFoundException
  10. *
  11. * @see #startActivity
  12. */
  13. public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
  14. startActivityForResult(intent, requestCode, null);
  15. }
4、调用相机时,未创建保存图片的文件夹



问题:“调用小米4手机拍照,不能点击打勾,就算点击了也没有反应。但是同样的代码也小米2,2S上没有问题,3上偶尔会出现。”
原因:
  1.   Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  2. intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(filePath, TEMP_IMAGE_NAME)));
  3. startActivityForResult(intent, 1);
如果此处Uri.fromFile(new File(filePath, TEMP_IMAGE_NAME),图片的地址有问题就不能点击确认。
这个问题是小米系统的原因,貌似DCIM和Picture这两个文件的权限不一样。
具体参看这两个文件路径:
  1. 1、Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath();
  2. 2、 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
5. 屏幕横向纵向切换的问题。 切换后。上一页 destory了。然后重新oncreate。然后又进入拍照了, 在activity的xml里面加上下面这句话即可:

  1. android:configChanges="orientation|keyboardHidden"
  2. android:screenOrientation="portrait"
6.输出的文件名无特殊字符
输出了之后才发现是特殊字符造成的,为了省事我直接取系统当前时间 yyyy-MM-dd HH:mm:ss z作为图片名称,初看似乎没什么影响,但是保存之后转化为uri就会出现%之类的特殊字符。然后悲剧就发生了,其实只要避免名称中出现  "-" ":" 诸如此类 的字符一般上都是可以正常保存返回的。

7.手机系统因内存不足而kill 了前 Activity或 拍照完成后应用闪退

这个现象相对少见,目前在廉价的低端手机上出现过。启动拍照时正常,当在拍照界面点击确认按钮,拍照界面消失返回我们的应用时,直接闪退。更让人崩溃的是,Logcat里面没有相应的日志信息。无奈只能借助搜索,发现原来是手机厂商对系统做了修改(为了在低端硬件上能够运行Android系统...),当我们的应用程序的Activity启动拍照,进入系统相机时,我们的Activity被销毁了。这个情况在测试时也不是必现。测试机在使用一段时间后很容易出现,但如果将设备重启后开启我们的应用来拍照又没有问题(这是我们遇到的情况,可能不是必现规则)。

Activity被回收时保存数据,可以在onSaveInstanceState()生命周期方法中处理,将图片路径保存到Bundle中。
  1. @Override
  2. protected void onSaveInstanceState(Bundle outState) {
  3. outState.putString("file_path", mFilePath);
  4. super.onSaveInstanceState(outState);
  5. }
然后在onCreate()方法里,判断savedInstanceState不为空时,取出图片路径的值。
  1. @Override
  2. protected void onCreate(@Nullable Bundle savedInstanceState) {
  3. if (savedInstanceState != null) {
  4. mFilePath = savedInstanceState.getString("file_path");
  5. // your code here
  6. }
  7. }
网上看到另外一种恢复的方法。Activity没有重新创建,而是成员变量被回收了,当拍照返回时,在onActivityResult()方法中mFilePath为空。解决方法是从onRestoreInstanceState()方法恢复数据。这种情况暂时没遇到,做个记录。
  1. @Override
  2. protected void onRestoreInstanceState(Bundle savedInstanceState) {
  3. if (TextUtils.isEmpty(mFilePath)) {
  4. mFilePath = savedInstanceState.getString("file_path");
  5. }
  6. super.onRestoreInstanceState(savedInstanceState);
  7. }
8.拍摄的照片被旋转了

明明在拍摄时照片是正的,进入裁剪页面发现照片竟然是被旋转的。这个现象在不同的设备上,表现会不一样,可能正常也可能被旋转,而且旋转的角度也不同。要解决这个问题,需要首选获取照片的旋转角度,再反转回去就可以了。

ExifInterface接口提供了多媒体文件比如JPG格式图片的一些附加信息,比如文件的旋转,gps,拍摄时间等。如下代码展示了使用ExifInterface获取图片的旋转角度。
  1. /**
  2. * 获取图片的旋转角度
  3. * @param path 图片的绝对路径
  4. */
  5. private int getBitmapDegree(String imagePath) {
  6. int degree = 0;
  7. try {
  8. // 从指定路径下读取图片,并获取其EXIF信息
  9. ExifInterface exifInterface = new ExifInterface(imagePath);
  10. // 获取图片的旋转信息
  11. int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
  12. ExifInterface.ORIENTATION_NORMAL);
  13. switch (orientation) {
  14. case ExifInterface.ORIENTATION_ROTATE_90:
  15. degree = 90;
  16. break;
  17. case ExifInterface.ORIENTATION_ROTATE_180:
  18. degree = 180;
  19. break;
  20. case ExifInterface.ORIENTATION_ROTATE_270:
  21. degree = 270;
  22. break;
  23. }
  24. } catch (IOException e) {
  25. e.printStackTrace();
  26. }
  27. return degree;
  28. }

进行图形变换如旋转、缩放、移动的操作,可以使用Matrix类来完成。如下代码展示了使用Matrix对图片旋转,生成新的Bitmap。

  1. /**
  2. * 将图片按照某个角度进行旋转
  3. * @param bitmap 需要旋转的图片
  4. * @param degree 旋转角度
  5. */
  6. public static Bitmap rotateBitmapByDegree(Bitmap bitmap, int degree) {
  7. Bitmap result = null;
  8. // 根据旋转角度,生成旋转矩阵
  9. Matrix matrix = new Matrix();
  10. matrix.postRotate(degree);
  11. try {
  12. // 将原始图片按照旋转矩阵进行旋转,并得到新的图片
  13. result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
  14. } catch (Exception e) {
  15. }
  16. if (bitmap != null && !bitmap.isRecycled()) {
  17. bitmap.recycle();
  18. bitmap = null;
  19. }
  20. return result;
  21. }




声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/175391
推荐阅读
相关标签
  

闽ICP备14008679号