最近在看 nanChen 写的图片选择器 ImagePicker,感受写得很不错,也打算把从中学到的东西写下来。不少时候,遇到一个好的框架可以下降开发成本这是好事。可是也要去了解其内部具体实现逻辑,说不定哪天你须要完成一个相似的小功能,你知道原理就能快速写出来,而不是引入整个框架。html
本文讲其中的第一个功能:如何调起手机的相机拍照?android
对于如何调用系统现有应用,这里简单再说一下。在开发的应用中调用系统现有应用,须要使用 Intent 指定开启的应用的 Action 和 Category,而后经过 startActivity(Intent) 或者 startActivityForResult(Intent, int) 开启指定的 Activity,若是使用 startActivityForResult() 方法开启并须要返回值,再重写 onActivityResult(int, int, Intent) 便可。app
先来看看系统现有相机应用的 AndroidManifest.xml 清单文件定义的 Activity:框架
<activity android:name="com.android.camera.Camera" android:clearTaskOnLaunch="true" android:configChanges="orientation|keyboardHidden" android:screenOrientation="landscape" android:taskAffinity="android.task.camera" android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <categroy android:name="android.intent.category.DEFAULT" /> <categroy android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter> <action android:name="android.media.action.IMAGE_CAPTURE" /> <categroy android:name="android.intent.category.DEFAULT" /> </intent-filter> <intent-filter> <action android:name="android.media.action.STILL_IMAGE_CAMERA" /> <categroy android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> <activity android:name="com.android.camera.VideoCamera" android:clearTaskOnLaunch="true" android:configChanges="origientation|keyboardHidden" android:label="@string/video_camera_label" android:screenOrientation="landscape" android:taskAffinity="android.task.camcorder" android:theme="@android:style/theme.Black.NoTitleBar.Fullscreen" > <intent-filter> <action android:name="android.media.action.VIDEO_CAMERA" /> <categroy android:name="android.intent.category.DEFAULT" /> </intent-filter> <intent-filter> <action android:name="android.media.action.VIDEO_CAPTURE" /> <categroy android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
它定义了两个 Activity,com.android.camera.Camera 表示照相机,com.android.camera.VideoCamera 表示摄像机。从字面意思能够看出,为了捕获系统相机返回的数据,通常须要使用一下两个 Action 便可开启照相机与摄像机:ide
android.media.action.IMAGE_CAPTURE:Intent 的 Action 类型,从现有的相机应用中请求一张图片。函数
android.media.action.VIDEO_CAPTURE:Intent 的 Action 类型,从现有的相机应用中请求一段视频。this
上面两个参数,均在 MediaStore 类中以静态常量的形式定义好了,分别是:MediaStore.ACTION_IMAGE_CAPTURE (相机) 和 MediaStore.ACTION_VIDEO_CAPTURE (摄像机)。spa
在新开启的 Activity 中,若是须要获取它的返回值,则须要使用 startActivityForResult(Intent,int) 方法打开 Activity,并重写 onActivityResult(int, int, Intent) 获取系统相机的返回数据,那么咱们只须要在 onActivityResult() 中获取到返回值便可。code
系统相机拍摄的照片,若是不指定路径,会保存在系统默认文件夹下,可使用 Intent.getExtra() 方法获得,获得的是一个 Uri 地址,表示了一个内容提供者的地址。若是经过MediaStore.EXTRA_OUTPUT 指定了保存路径,那么经过 Intent.getExtra() 获得的将是一个空地址,可是既然是咱们指定的地址,那么也不愁找不到它了。视频
可是若是是7.0以上,须要使用 FileProvider 来获得这个 uri 地址。
说清楚流程以后,下面就是具体代码实现:
首先是在 AndroidManiFest.xml 下声明拍照权限:
<uses-permission android:name="android.permission.CAMERA" />
声明权限后,在开始拍照前,仍是须要判断用户是否给了咱们拍照的权限:
if (( mActivity).checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(mActivity, new String[]{Manifest.permission.CAMERA}, GalleryActivity.REQUEST_PERMISSION_CAMERA); } else { imagePicker.takePicture(mActivity, GalleryActivity.REQUEST_CODE_TAKE); }
若是用户没有给权限,那么须要申请权限,权限申请之后,会有一个回调通知开发者是否容许了,具体见下发的代码:
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_PERMISSION_CAMERA) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { imagePicker.takePicture(this, REQUEST_CODE_TAKE); } else { showToast("权限被禁止,没法打开相机"); } } }
权限容许以后,经过 imagePicker.takePicture 去拍照。下面看下拍照的具体代码逻辑:
/** * 拍照的方法 */ public void takePicture(Activity activity, int requestCode) { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); takePictureIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); if (takePictureIntent.resolveActivity(activity.getPackageManager()) != null) { if (Utils.existSDCard()) takeImageFile = new File(Environment.getExternalStorageDirectory(), "/DCIM/camera/"); else takeImageFile = Environment.getDataDirectory(); takeImageFile = createFile(takeImageFile, "IMG_", ".jpg"); if (takeImageFile != null) { // 默认状况下,即不须要指定intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); // 照相机有本身默认的存储路径,拍摄的照片将返回一个缩略图。若是想访问原始图片, // 能够经过dat extra可以获得原始图片位置。即,若是指定了目标uri,data就没有数据, // 若是没有指定uri,则data就返回有数据! Uri uri; if (VERSION.SDK_INT <= VERSION_CODES.M) { uri = Uri.fromFile(takeImageFile); } else { /** * 7.0 调用系统相机拍照再也不容许使用Uri方式,应该替换为FileProvider * 而且这样能够解决MIUI系统上拍照返回size为0的状况 */ uri = FileProvider.getUriForFile(activity, "com.example.myapplication.provider", takeImageFile); //加入uri权限 要不三星手机不能拍照 List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; activity.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); } } // Log.e("nanchen", ProviderUtil.getFileProviderName(activity)); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri); } } activity.startActivityForResult(takePictureIntent, requestCode); }
关于 Intent.resolveActivity 做用,简单来讲就是当你在调用第三方软件或者系统 Activity,相似打开相机,发送图片等隐式 Intent,是并不必定可以在全部的 Android 设备上都正常运行。经过该方法判断这个 intent 对应的 activity 是否存在,确保不会出现崩溃。
takeImageFile 是图片存储地址,在7.0之前,须要用 Uri.fromFile 进行处理。7.0以后的采用 FileProvider。下面介绍下 FileProvider 的使用方法:
使用 FileProvider 须要在 AndroidManiFest.xml 里声明:
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.myapplication.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_path" /> </provider>
这里是直接使用的 v4 包中的 FileProvider,咱们也能够直接继承 FileProvider 类,适当重写重载函数,但不建议如此作。下面来介绍上面的几个设置:
name: provider 的类名,若使用默认的 v4 的 FileProvider 可以使用 "android.support.v4.content.FileProvider",也能够设置为自定义的继承 FileProvider 的 provider 类;
authorities: 一个签名认证,能够自定义,但在获取 uri 的时候须要保持一致;
grantUriPermissions: 使用 FileProvider 的使用须要咱们给流出的URI 赋予临时访问权限(READ 和 WRITE),该设置是容许咱们行使该项权力;
meta-data: meta-data 配置的是咱们能够访问的文件的路径配置信息,须要使用 xml 文件进行配置,FileProvider 会经过解析 xml 文件获取配置项,其中 name 名字不可改变为: android.support.FILE_PROVIDER_PATHS,resource 为配置路径信息的配置项目。
可访问的路径配置能够在 res 中创建一个 xml 文件下面创建一个配置文件,格式以下:
<?xml version="1.0" encoding="utf-8"?> <paths> <!--path:须要临时受权访问的路径(.表明全部路径)--> <!--name:就是你给这个访问路径起个名字--> <external-path name="cam" path="." /> </paths>
下面解释下:
<files-path/> 表明的根目录: Context.getFilesDir()
最后,将 uri 放到 MediaStore.EXTRA_OUTPUT 中。
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
拍照完成后,会回调 onActivityResult,在这里咱们能够根据先前传的值将图片展现到 ImageView 中:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.i(TAG, "系统相机拍照完成,resultCode=" + resultCode + " " + requestCode); if (requestCode == REQUEST_CODE_TAKE) { Uri uri = Uri.fromFile(takeImageFile); iv_CameraImg.setImageURI(uri); } }
到此,调用系统相机拍照的过程到此就结束了。
原文出处:https://www.cnblogs.com/huansky/p/12104662.html