不得不说MIUI是个大坑,在其他系统都能正常实现的时候,唯独MIUI出现了各种奇奇怪怪的状况。
最后上了第三方框架uCrop解决裁剪问题。
所需要的框架
因为LitePal和MobSDK都需要对Application进行修改,所以最好实现自己的Application:
关键代码
运行时权限+对话框选择是拍照还是选择照片
Android6.0后,调用相机以及写入存储文件需要运行时申请权限,这里采用了 Google 官方的 EasyPermissions 框架来简化权限申请步骤。
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
| private var avatarFileName = "avatar.jpg" private var avatarOriginFileName = "avatarOrigin.jpg"
companion object { private const val RC_CAMERA = 200 private const val CHOOSE_PICTURE = 0 private const val TAKE_PICTURE = 1 }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults)
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) }
@AfterPermissionGranted(RC_CAMERA) fun showChoosePicDialog() { val builder = android.app.AlertDialog.Builder(this) builder.setTitle("修改头像") val items = arrayOf("选择本地照片", "拍照") builder.setNegativeButton("取消", null) builder.setItems(items) { _, which -> when (which) { 0 -> { val perms = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (EasyPermissions.hasPermissions(this, *perms)) { val intent = Intent(Intent.ACTION_PICK) intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*") startActivityForResult(intent, CHOOSE_PICTURE) } else { EasyPermissions.requestPermissions(this, "需要文件写入权限", RC_CAMERA, *perms) } } 1 -> { val perms = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA)
if (EasyPermissions.hasPermissions(this, *perms)) { val openCameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
val file = getAvatarFile(avatarOriginFileName)
if(file.exists()) file.delete()
val fileUri = getUriByOsVersion(file)
openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri) openCameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) openCameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) startActivityForResult(openCameraIntent, TAKE_PICTURE) } else{ EasyPermissions.requestPermissions(this, "拍照需要系统摄像头权限授权和文件写入权限", RC_CAMERA, *perms) } } } } builder.show() }
|
对选择、裁剪成功等返回结果的处理
Android7.0 对 APP 内的文件共享做了限制,外部不能直接访问你的内部文件。
这里需要用到FIleProvier,具体的FileProvider的配置在下一小节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (resultCode == Activity.RESULT_OK) { when (requestCode) { TAKE_PICTURE -> { val imgUriSel = getUriByOsVersion(getAvatarFile(avatarOriginFileName)) cutImageByuCrop(imgUriSel) } CHOOSE_PICTURE -> cutImageByuCrop(data?.data) UCrop.REQUEST_CROP -> { data?.let { uploadFile(it) } } UCrop.RESULT_ERROR -> { val cropError = data?.let { UCrop.getError(it) } ToastUtils.showShortToast(cropError.toString()) }
} } }
|
使用uCrop框架对指定的文件进行裁剪
uCrop 是一款很优秀的第三方裁剪框架,Bilibili的客户端也在使用。
一开始调用系统的裁剪的时候,出现过各种坑(比如MIUI用return-data的方式调用会报错,不用return-data有时候又拿不到返回值),于是才换用了这个。
1 2 3 4 5 6 7 8 9 10 11
| private fun cutImageByuCrop(uri: Uri?) { val outputImage = getAvatarFile(avatarFileName) val outputUri = Uri.fromFile(outputImage)
uri?.let { UCrop.of(it, outputUri) .withAspectRatio(1f, 1f) .withMaxResultSize(256, 256) .start(this) } }
|
一个获得指定名字的[File]对象的私有方法
1 2 3 4 5 6 7 8 9 10 11 12
| private fun getAvatarFile(filename:String): File{ val appDir = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES).absolutePath, "Avatar")
if (!appDir.exists()) appDir.mkdir()
return File(appDir, filename) }
|
根据系统api版本决定是用绝对路径还是用FileProvider获得Uri的私有方法
1 2 3 4 5 6 7 8 9
| private fun getUriByOsVersion(file:File):Uri{ val currentApiVersion = android.os.Build.VERSION.SDK_INT
return if(currentApiVersion < 24) { Uri.fromFile(file) } else{ FileProvider.getUriForFile(this, packageName + ".provider", file) } }
|
上传裁剪后的头像
1 2 3 4 5 6 7 8
| @Throws(IOException::class) fun uploadFile(data: Intent) { val file = getAvatarFile(avatarFileName) val file = getAvatarFile(avatarFileName)
LoadingDialogUtils.show(this) userNetwork.updateAvatar(file) }
|
如果要调用系统内置的裁剪,部分代码:
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
| @Deprecated("use ucrop instead") fun cutImage(uri: Uri?) {
if (uri == null) { Log.i("tip", "The uri is not exist.") } tempUri = uri!!
val intent = Intent("com.android.camera.action.CROP") intent.setDataAndType(uri, "image/*") intent.putExtra("crop", "true") intent.putExtra("aspectX", 1) intent.putExtra("aspectY", 1) intent.putExtra("outputX", 256) intent.putExtra("outputY", 256) tempUri = Uri.parse("file://" + "/" + Environment.getExternalStorageDirectory().absolutePath + "/" + "LifeUp" + "/" + avatarFileName) intent.putExtra(MediaStore.EXTRA_OUTPUT, uri) intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) intent.putExtra("noFaceDetection", true)
startActivityForResult(intent, CROP_SMALL_PICTURE) }
|
最好通过将 return-data 设为 false,然后通过传递 uri , 返回时通过 uri 获取 File 的方式来使用。
MIUI 好像在 return-data 设为 true,在 intent 返回 bitmap 的情况下会报错。
另外,即便在 return-data 设为 false 的时候,MIUI 仍然可能会出现保存失败的情况。可能原始文件和裁剪后文件的 uri 不能相同(纯猜测)。
FileProvider 配置
在 AndroidManifest.xml 中加上:
1 2 3 4 5 6 7 8 9 10
| <provider android:name="android.support.v4.content.FileProvider" android:authorities="net.sarasarasa.lifeup.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/> </provider>
|
在 res/xml/file_paths.xml 中加上:
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="utf-8"?> <paths> <files-path path="Android/data/net.sarasarasa.lifeup/files/Pictures/" name="Avatar" /> <external-path path="Android/data/net.sarasarasa.lifeup/files/Pictures/" name="Avatar" /> <external-files-path path="files/Pictures/Avatar" name="images"/> <root-path name="name" path="" /> </paths>
|