选择头像、图片时经常使用拍照和相册功能,但是由于 Android 碎片化问题,不同版本 API 有差异,并且在不同页面处理 onActivityResult 感觉太麻烦,所以就想着可以通过链式调用,将这些逻辑封装起来,让整个代码写起来简洁清晰明了,不至于好多类似代码重复,提高开发效率.
选择图片存在的问题
Android 6.0 运行时权限
在 Android 6.0 中严格限制App的权限授予,读取 SD 卡、拍照、录音、定位、短信、通讯录等关键权限需要在代码中申请这些权限,否则会抛出异常导致程序奔溃,当 App 申请权限被拒绝后我们应该给用户弹出提示框,引导用户去设置页开启此权限,然后在 onRequestPermissionsResult 中处理回调结果.
Android 7.0 StrictMode 问题
在 Android 7.0 中强制启用了被称作 StrictMode 的策略,带来的影响就是你的App对外无法暴露 file:// 类型的 URI,否则会出现 FileUriExposedException 异常,进程间应使用 FileProvider 共享文件.
三星机型图片选择问题
在三星机型中,当我们调用相机拍照后,获取的图片发现被旋转90度,所以需要获取图片的 ExifInterface 信息,然后根据 orientation 进行图片的角度旋转,使之方向正确.
解决方式
针对以上问题写一个 library 就显得很有必要.方便在不用页面调用选择图片.在一个透明背景的 Activity 中处理以上问题,让调用方无感知以上问题,只用传入必要的设置条件最后拿到图片文件就可以,然后针对文件进行自己的业务逻辑操作即可.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Avatar.getInstance() .setSelectMode(Avatar.ALL) .setImageFileDir("Tinder") .setHasCrop(true) .imageFile { imageFile -> run { Log.e("file:", imageFile.absolutePath) img.setImageBitmap(BitmapFactory.decodeFile(imageFile.absolutePath)) } } .build(this)
|
在 Avatar 中使用使用静态内部类单例模式,减少对象创建,并方便 Activity 中传回图片文件目录,SelectAvatarActivity 使用透明主题,并在处理权限请求与照片回调处理.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| Avatar.java
private Avatar() { }
public static Avatar getInstance() { return AvatarHolder.INSTANCE; }
public static class AvatarHolder { static final Avatar INSTANCE = new Avatar(); }
SelectAvatarActivity.java
Avatar.getInstance().setImageFile(mAvatarFile);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { mOrigUri = Uri.fromFile(cameraFile); } else { ContentValues contentValues = new ContentValues(1); contentValues.put(MediaStore.Images.Media.DATA, cameraFile.getAbsolutePath()); mOrigUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues); } intent.putExtra(MediaStore.EXTRA_OUTPUT, mOrigUri); startActivityForResult(intent, REQUEST_CODE_GET_IMAGE_CAMERA);
Intent intent; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); } else { intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); } startActivityForResult(intent, REQUEST_CODE_GET_IMAGE_SDCARD);
public static int readPictureDegree(String path) { int degree = 0; try { 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; default: } } catch (IOException e) { e.printStackTrace(); } return degree; }
ImageUtil.java public static Bitmap rotateImageView(int angle, Bitmap bitmap) { Matrix matrix = new Matrix(); matrix.postRotate(angle); if (bitmap != null) { return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); } else { return null; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <style name="TransparentTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">@android:color/transparent</item> <item name="colorPrimaryDark">@android:color/transparent</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item> <item name="android:windowNoTitle">true</item> <item name="android:windowContentOverlay">@null</item> </style>
<provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.fileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> </provider>
<paths>
<root-path name="root_path" path="."/> </paths>
|
在<paths>
中可以添加 <files-path>
分享app内部的存储; <external-path>
分享外部的存储; <cache-path>
分享内部缓存目录.根据不同的需求对文件夹进行授权访问.
当封装好这些功能后,在调用页面获取图片的代码最多短短是几行,而不像原来需要上百行的处理各种问题,并且 API 变动时,只需要改动 library 就可以完成适配.
项目地址