Android相机权限
开发工具:Android Studio3.3.1 sdk=28.0.0 gradle=gradle-4.10.1-all.zip
时间:2019/2/28
编程语言:Kotlin
功能:拍照和从相册选择相片
测试手机:华为荣耀8,Android version 8.0.0
参考文献:
https://www.cnblogs.com/zhangqie/p/7562959.html
https://www.jianshu.com/p/41b093d213fb
GitHub: https://github.com/SAKURA96/PhotoCameraTest2
简述:三个主要部分是Android动态权限申请,获取相册和拍照功能。未使用第三方库。
个人吐槽:我搜了很多的拍照的代码都是直接闪退,都是因为权限部分没有写好。或者写了但是不太理想,有时候一个权限拒绝后面点击按钮就直接闪退,第三方库也不太适用,总是只有一个成功的回调方法。可是我这里有两个功能啊,我水平有限写不出一个成功回调能独立解决两个事件响应。所以就自己写权限的申请,已经被申请就立即执行相关功能。这个代码不够成熟,在活动请求的回调中,有两处警告,望各位懂的大佬可以指正一下。

Go:
1 Manifest中加入权限,和provider
<!--读写内存块权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--调用相机权限-->
<uses-permission android:name="android.permission.CAMERA"/>
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.photocameratest2.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
2 app/res/xml 中新建file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
3 新建工具类GetPhotoFromAlbum
import android.annotation.SuppressLint import android.content.ContentUris import android.content.Context import android.database.Cursor import android.net.Uri import android.os.Build import android.provider.DocumentsContract import android.provider.MediaStore // Content:从相册内获取照片转化工具类 object GetPhotoFromAlbum { /** * 根据Uri获取图片的绝对路径 * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null */ fun getRealPathFromUri(context: Context, uri: Uri): String? { val sdkVersion = Build.VERSION.SDK_INT return if (sdkVersion >= 19) { // api >= 19 getRealPathFromUriAboveApi19(context, uri) } else { // api < 19 getRealPathFromUriBelowAPI19(context, uri) } } /** * 适配api19以下(不包括api19),根据uri获取图片的绝对路径 * * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null */ private fun getRealPathFromUriBelowAPI19(context: Context, uri: Uri): String? { return getDataColumn(context, uri, null, null) } /** * 适配api19及以上,根据uri获取图片的绝对路径 * * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null */ @SuppressLint("NewApi") private fun getRealPathFromUriAboveApi19(context: Context, uri: Uri): String? { var filePath: String? = null if (DocumentsContract.isDocumentUri(context, uri)) { // 如果是document类型的 uri, 则通过document id来进行处理 val documentId = DocumentsContract.getDocumentId(uri) if (isMediaDocument(uri)) { // MediaProvider // 使用':'分割 val id = documentId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1] val selection = MediaStore.Images.Media._ID + "=?" val selectionArgs = arrayOf(id) filePath = getDataColumn(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs) } else if (isDownloadsDocument(uri)) { // DownloadsProvider val contentUri = ContentUris.withAppendedId( Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(documentId) ) filePath = getDataColumn(context, contentUri, null, null) } } else if ("content".equals(uri.scheme!!, ignoreCase = true)) { // 如果是 content 类型的 Uri filePath = getDataColumn(context, uri, null, null) } else if ("file" == uri.scheme) { // 如果是 file 类型的 Uri,直接获取图片对应的路径 filePath = uri.path } return filePath } /** * 获取数据库表中的 _data 列,即返回Uri对应的文件路径 */ private fun getDataColumn(context: Context, uri: Uri, selection: String?, selectionArgs: Array<String>?): String? { var path: String? = null val projection = arrayOf(MediaStore.Images.Media.DATA) var cursor: Cursor? = null try { cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null) if (cursor != null && cursor.moveToFirst()) { val columnIndex = cursor.getColumnIndexOrThrow(projection[0]) path = cursor.getString(columnIndex) } } catch (e: Exception) { cursor?.close() } finally { cursor?.close() } return path } /** * @param uri the Uri to check * * @return Whether the Uri authority is MediaProvider */ private fun isMediaDocument(uri: Uri): Boolean { return "com.android.providers.media.documents" == uri.authority } /** * @param uri the Uri to check * * @return Whether the Uri authority is DownloadsProvider */ private fun isDownloadsDocument(uri: Uri): Boolean { return "com.android.providers.downloads.documents" == uri.authority } }
4 主布局 activity_main,两个Button,一个ImageView.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/btn_album" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp" android:text="调用相册"/> <Button android:id="@+id/btn_camera" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp" android:text="调用相机"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/iv_image_view" android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="15dp" android:scaleType="fitXY" android:adjustViewBounds="true" tools:src="@mipmap/ic_launcher"/> </LinearLayout> </LinearLayout>
5 在MainActivity中
import android.Manifest import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment import android.provider.MediaStore import android.support.v4.app.ActivityCompat import android.support.v4.content.ContextCompat import android.support.v4.content.FileProvider import android.support.v7.app.AppCompatActivity import android.widget.ImageView import android.widget.Toast import kotlinx.android.synthetic.main.activity_main.* import java.io.File class MainActivity : AppCompatActivity() { //设置权限请求和活动请求的请求码requestCode companion object { private const val PERMISSIONS_REQUEST_ALBUM = 1 private const val PERMISSIONS_REQUEST_CAMERA = 2 private const val ACTIVITY_REQUEST_ALBUM = 3 private const val ACTIVITY_REQUEST_CAMERA = 4 } lateinit var cameraSavePath: File lateinit var uri: Uri lateinit var imageView: ImageView //拍照需要的两个权限 private val permissionList = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA) //存储用户拒绝授权的权限 var permissionTemp: ArrayList<String> = ArrayList() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) imageView = findViewById(R.id.iv_image_view) //拍照的文件存储位置 cameraSavePath = File(Environment.getExternalStorageDirectory().path + "/" + System.currentTimeMillis() + ".jpg") initListener() } //设置两个button的监听 private fun initListener() { //相册监听,检查一个权限 btn_album.setOnClickListener { //检查版本是否大于M if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission( this, Manifest.permission.WRITE_EXTERNAL_STORAGE ) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSIONS_REQUEST_ALBUM ) } else { //权限已经被授权,开启相册 goAlbum() } } } //拍照监听,检查两个权限 btn_camera.setOnClickListener { permissionTemp.clear() for (i in permissionList.indices) { if (ContextCompat.checkSelfPermission( this, permissionList[i] ) != PackageManager.PERMISSION_GRANTED ) { permissionTemp.add(permissionList[i]) } } if (permissionTemp.isEmpty()) { //未授予的权限为空,表示都授予了,开启照相功能 goCamera() } else {//请求权限方法 val permissions = permissionTemp.toTypedArray()//将List转为数组 ActivityCompat.requestPermissions( this, permissions, PERMISSIONS_REQUEST_CAMERA ) } } } //权限结果回调 override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { when (requestCode) { //相册权限请求结果 PERMISSIONS_REQUEST_ALBUM -> { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { goAlbum() } else { Toast.makeText(this, "你拒绝了读取相册权限", Toast.LENGTH_SHORT).show() } } //拍照权限请求结果 PERMISSIONS_REQUEST_CAMERA -> { //用于判断是否有未授权权限,没有则开启照相 var isAgree = true for (i in grantResults.indices) { if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { //检查到有未授予的权限 isAgree = false //判断是否勾选禁止后不再询问 val showRequestPermission = ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i]) if (showRequestPermission) { Toast.makeText(this, "你拒绝了拍照相关权限", Toast.LENGTH_SHORT).show() } } } //isAgree没有被置为false则表示权限都已授予,开启拍照 if (isAgree) { goCamera() } } } super.onRequestPermissionsResult(requestCode, permissions, grantResults) } //相册功能 private fun goAlbum() { val intent = Intent() intent.action = Intent.ACTION_PICK intent.type = "image/*" startActivityForResult(intent, ACTIVITY_REQUEST_ALBUM) } //拍照功能 private fun goCamera() { val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { uri = FileProvider.getUriForFile(this, "com.photocameratest2.fileprovider", cameraSavePath) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } else { uri = Uri.fromFile(cameraSavePath) } intent.putExtra(MediaStore.EXTRA_OUTPUT, uri) this.startActivityForResult(intent, ACTIVITY_REQUEST_CAMERA) } //活动请求的回调,用requestCode来匹配 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { //图片路径 var photoPath: String = "" //相册 if (requestCode == ACTIVITY_REQUEST_ALBUM && resultCode == Activity.RESULT_OK) { photoPath = GetPhotoFromAlbum.getRealPathFromUri(this, data!!.data)!! imageView.setImageURI(Uri.parse(photoPath)) //拍照 } else if (requestCode == ACTIVITY_REQUEST_CAMERA && resultCode == Activity.RESULT_OK) { photoPath = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { cameraSavePath.toString() } else { uri.encodedPath } imageView.setImageURI(Uri.parse(photoPath)) } super.onActivityResult(requestCode, resultCode, data) } }

浙公网安备 33010602011771号