随着现代信息量的疯狂增长、信息的快速交流,单纯的文字信息已经难以满足日常、工作的沟通,一张图片往往能达到一图胜过千万言的效果,前段时间不是盛行“有图有真相的”说法;还有一些场景则需要通过上传照片来验证身份的合法性,比如手机银行要求上传身份证正反照,手持身份证照 ……废话多了点,总之,作为移动端开发者的你,肯定会遇到这样那样拍照上传的需求,接下来我们来试试 Intent 调用 Android 系统相机的2种方式,说说其中的猫腻。
Intent 作为 Android 四大组件之间的纽带,可以在其间进行相互调用唤醒、传输数据。因此我们可以通过 Intent 调用系统的相机,系统已经为我们预留好了相机的 intent-action,最简单的直接通过 Intent 隐式调用即可。
最简单的做法就是只设置action,别的任何信息都不指定:
// 不指定拍照存储路径的方式调起相机
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, REQUEST_CODE);
拍照完成后的返回处理:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (REQUEST_CAMERA == requestCode
&& RESULT_OK == resultCode) {
if (data != null) {
if (data.getExtras() != null) {
Log.e("WangJ", "result-Extras: " + data.getExtras());
imgThumbnail.setImageBitmap((Bitmap) data.getParcelableExtra("data"));
} else {
Log.e("WangJ", "无intent extra返回");
}
Uri uri;
if ((uri = data.getData()) != null) {
Log.e("WangJ", "result-Uri: " + uri.toString());
tvFileUri.setText(uri.toString());
imgOriginal.setImageBitmap(getBitmapByUri(uri));
Log.e("WangJ", "按返回Uri刷新图库");
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(uri);
sendBroadcast(intent);
} else {
Log.e("WangJ", "无Uri返回");
}
} else {
Log.e("WangJ", "result为空!");
imgOriginal.setImageBitmap(getBitmapByUri(imageUri));
Log.e("WangJ", "按自定义Uri刷新图库");
/* 以下代码的作用是通知系统相册刷新目标位置,这样即可在相册中显示该图片,
* 如果不想在相册中显示,可以不发送次广播即可 */
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(imageUri);
sendBroadcast(intent);
}
}
}
private Bitmap getBitmapByUri(Uri picuUi) {
Bitmap bitmap = null;
try {
InputStream in = getContentResolver().openInputStream(picuUi);
bitmap = BitmapFactory.decodeStream(in);
Log.e("WangJ", "从流中获取的原始大小: " + bitmap.getWidth() * bitmap.getHeight());
bitmap = ThumbnailUtils.extractThumbnail(bitmap, 800, 1280);
Log.e("WangJ", "压缩后大小: " + bitmap.getWidth() * bitmap.getHeight());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return bitmap;
}
我们开看看效果(别问我这里的结果处理怎么想到是怎么写的,结合前任的经验一次次试过来的,如有不足,敬请指导)。众所周知,Android 系统被各大厂商进行了深浅不一的定制,可能表现出不一样的结果,这里就看到很多前人的说法,现以手中现有的测试机型加以验证:
环境 | 版本 |
---|---|
AndroidVersion | 4.4.4 KTU84P |
MIUIVersion | 6.2.1.0(KXDCNBK)稳定版 |
有没有发现日志中显示Bundle中有个大家伙,size约90k,这就是我们上边取出来的缩略图bitmap。
由此可见:在此设备上,这种调用方式只返回一个缩略图,没有别的信息返回。并且我在手机中也未找到刚才拍摄的照片。
环境 | 版本 |
---|---|
AndroidVersion | 6.0.1 |
型号 | SM-G9008W |
输出日志(不要纠结上边显示编号86,怎么日志是90,因为录屏的时候日式给跑过了,只好重新跑一个截个日志):
由此可见:SAMSUNG Galaxy S5按此方式调用既返回了缩略图,也返回了所拍照片的存储Uri,然后根据这个Uri能查到原图,并且在相册中也能找到这个图。
环境 | 版本 |
---|---|
AndroidVersion | 6.0 |
型号 | HUAWEI MT7-TL10 |
EMUI | EMUI 4.0 |
运行结果和 MI 4C 结果一模一样,不再浪费篇章。
采用这种单纯action调用相机而不指定其他信息的方法在一些主流机器上并不可靠,即使能够返回缩略图,但拿不到原图,甚至在手机中也没有记录。
但是,Intent调用相机时还能指定目标图像的存储路径,看接下来的方法。
直接上代码:
String filePath = Environment.getExternalStorageDirectory() + File.separator
+ Environment.DIRECTORY_PICTURES + File.separator;
String fileName = "IMG_"
+ DateFormat.format("yyyyMMdd_hhmmss", Calendar.getInstance(Locale.CHINA))
+ ".jpg";
imageUri = Uri.fromFile(new File(filePath + fileName));
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, REQUEST_CAMERA);
其余代码不变。再看结果:
由此可见:使用 Intent 调用相机时通过为Intent设定intent.putExtra(MediaStore.EXTRA_OUTPUT, ”路径”);可以指定将拍照结果存至指定位置,拍照完成后 Intent 中不再返回任何信息,但是 resultCode 明确显示操作成功。此时我们可以根据之前指定的存储位置刷新图库即可在相册中显示拍照结果,通过该Uri获取原图也没有问题。
有图有真相
由此可见:指定图像存储位置时,SAMSUNG Galaxy S5和小米4C的表现一致。
和以上一致,不再赘述,截图为证:
综合以上的验证并且查验大量前人的经验,在使用 Intent 调用系统相机进行拍照时,推荐为目标图像指定存储位置,这样在主流机型上表现一致,并且这个 Uri 自己指定的,增加了更大的自定义性,变过过程也更加灵活。
另外,在拍照完成后,图像已存至指定位置,想不想在系统相册中看到这张图片,还不是你说了算?
这个坑没啥歧义,但是要是不注意可能你连Demo都跑不起来:
如果你在跑Demo时打印那些用到的 Uri,你肯定会发现类似这样的问题:
其实他们只是表现形式不一样而已,就像王老五的小名叫小五,王老五、小五都是指的同一个人。file:/// 开头的Uri使用的是绝对路径,而 content:// 开头的是系统从内容提供器 ContentProvider 中拿到的标记。这2种形式不同的Uri都能被系统 顺藤摸瓜 找出源数据。
可能出现拍照完成后点击“确认”或 对号 没有响应,这是因为你指定的存储 Uri 的问题。
这个是自己在尝试将照片存入不同文件夹下发现的一个问题,如果你指定的存储路径Uri指向的文件夹不存在,那么系统不会自动创建,除非你在代码中加以处理(比如判断文件夹是否已经存在,否则新建)。这里我建议存入系统常用文件夹下,比如Pictures、Camera、DCIM等目录每个手机都是有的,99.99%不会出错。
(声明:这个问题我没有遇到,但是作为可能的问题借鉴前人的经验先记下来)
Samsung的系统相机,版式是横板的,如果你的activity恰巧是竖版的,那么获取这个回调uri的时候,很可能为空!原因在于,如果你没有设置版式改变的时候,activity不要调用onCreate方法!这就是要命的地方!
1.解决方法其实很简单:
在Manfest.xml中,给activity添加一个属性:
android:configChanges="orientation|keyboardHidden"
2.在activity中添加:
@Override
public void onConfigurationChanged(Configuration config) {
super.onConfigurationChanged(config);
}
如上图,某些相机本身带有重力感应,拍照时会随着手机姿态进行调整,导致照片会有对应的旋转,此时拿到的照片可能和期望值之间有一个旋转角度(90度、180度、270度)。此时可以
ExifInterface exifInterface = new ExifInterface(图片路径);
// 获取图片的旋转信息
int orientation = exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
...(省略)
// orientation的值如下类似如下
public static final int ORIENTATION_ROTATE_180 = 3;
public static final int ORIENTATION_ROTATE_270 = 8;
public static final int ORIENTATION_ROTATE_90 = 6;
... 还有更多
获取到图片的旋转信息后,根据需要再利用旋转矩阵将图还原即可。
Activity 的加载模式会影响 Activity 之间 Intent 传递数据的通路。举个例子:
以下为验证结果:
Activity A加载模式 | Activity B加载模式 | 现象 |
---|---|---|
singleInstance | 任意 | 失败 |
任意 | singleInstance | 失败 |
任意 | singleTask | 失败 |
总之: 被请求的 Activity 不能使用 singleInstance、singleTask;发起请求的Activity不能使用 singleInstance,要慎用singleTask。
(提示:如果你运行Demo中的Intent传递数据部分,不仅注意观察日志输出内容,也要关注日志输出的时机,因为 Activity 的 launchMode 不同,触发 onActivityResult() 方法的时机也不同!)
目前先这些,Demo源码已上传GitHub,欢迎指导,假如有所收获,欢迎star。下一篇说说在 WebView 中调用相机的问题。
知识有限,如有谬误,敬请指正