最近公司项目有频繁使用相机拍照的需求,然后频繁的实际使用过程中遇到很多大大小小的坑,在此记录下。
(ps.关于一些拍照后图片旋转、裁剪设置返回数据为true造成崩溃等等问题网上相关链接很多在此不做赘述)。
拍照、裁剪操作中出现的错误可参考这篇文章:
——http://blog.csdn.net/lang791534167/article/details/39647263
由于是在主界面的Activity中的一个Fragment模块下包含Viewpager,ViewPager的两个子页为第二层Fragment,拍照功能在第二层Fragment中。然后程序意外结束候程序被重建不能收到回调及回调返回不正确导致拍照后无法调起裁剪功能(看起来就是闪退到了之前的界面)
之前的写法是直接在Fragment中调用系统相机,拍照和裁剪的路径都做了本地保存,然后通过路径取照片进行操作:
/**
* 调用系统相机拍照
*/
private void startTakePhoto() {
// 先判断设备是否有SD卡,如果没有则不能拍照
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String savePath = CameraUtils.BASE_PATH + CameraUtils.PHOTO_PATH;
File savedir = new File(savePath);
if (!savedir.exists()) {
savedir.mkdirs();
}
String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String fileName = timeStamp + ".jpg";// 照片命名
File out = new File(savePath, fileName);
Uri uri = Uri.fromFile(out);
origUri = uri;//保存到成员变量
origAbsPath = savePath + fileName;// 该照片的绝对路径
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
this.startActivityForResult(intent, ImageUtils.REQUEST_CODE_GETIMAGE_BYCAMERA);
} else {
UDreamToast.showToast( getActivity(), "未检测到SD卡", UDreamToast.TOAST_TYPE_WARNING);
}
}
/**
* 接收回调
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != Activity.RESULT_OK) {
return;
}
switch (requestCode) {
case ImageUtils.REQUEST_CODE_GETIMAGE_BYCAMERA:
if (!TextUtils.isEmpty(origAbsPath)){
startActionCrop(Uri.fromFile(new File(origAbsPath)));// 拍照后裁剪(用绝对路径取)
}
}
break;
case ImageUtils.REQUEST_CODE_GETIMAGE_BYSDCARD://裁剪后上传
Bitmap cropBitmap = null;
// 获取头像缩略图
if (!TextUtils.isEmpty(cropPath) && cropFile.exists()) {
cropBitmap = ImageUtils.getBitmapByPath(cropPath);
}
if (cropBitmap != null) {
//上传图片
uploadPhoto(cropBitmap);
if (!cropBitmap.isRecycled())
cropBitmap.recycle();
}else {
UDreamToast.showToast(getContext(),"上传失败,请重新上传!",UDreamToast.TOAST_TYPE_FAILED);
}
break;
}
}
/**
* 拍照后裁剪
* @param data 原始图片
*/
private void startActionCrop(Uri data) {
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(data, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 10);// 裁剪框比例
intent.putExtra("aspectY", 11);
intent.putExtra("outputX", CameraUtils.CROP_PRODUCTION_WIDTH);// 输出图片大小
intent.putExtra("outputY", CameraUtils.CROP_PRODUCTION_HEIGHT1);
intent.putExtra("scale", true);// 去黑边
intent.putExtra("scaleUpIfNeeded", true);// 去黑边
intent.putExtra("noFaceDetection", true);// 关闭人脸识别
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("return-data", false);
intent.putExtra(MediaStore.EXTRA_OUTPUT, getUploadTempFile(data));
this.startActivityForResult(intent, ImageUtils.REQUEST_CODE_GETIMAGE_BYSDCARD);
}
/**
* 保存裁剪头像的绝对路径
* @param uri
* @return
*/
private Uri getUploadTempFile(Uri uri) {
String storageState = Environment.getExternalStorageState();
if (storageState.equals(Environment.MEDIA_MOUNTED)) {
File savedir = new File(CameraUtils.BASE_PATH + CameraUtils.CUT_PATH);
if (!savedir.exists()) {
savedir.mkdirs();
}
} else {
UDreamToast.showToast(getContext(), "无法保存要上传的图片,请检查SD卡是否挂载", UDreamToast.TOAST_TYPE_WARNING);
return null;
}
String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String thePath = ImageUtils.getAbsolutePathFromNoStandardUri(uri);
// 如果是标准Uri
if (TextUtils.isEmpty(thePath)) {
thePath = ImageUtils.getAbsoluteImagePath(getActivity(), uri);
}
String fileSuffix = FileUtils.getFileFormat(thePath);
fileSuffix = TextUtils.isEmpty(fileSuffix) ? "jpg" : fileSuffix;
// 照片命名
String cropFileName = timeStamp + "." + fileSuffix;
// 裁剪头像的绝对路径
cropPath = CameraUtils.BASE_PATH + CameraUtils.CUT_PATH + cropFileName;
cropFile = new File(cropPath);
return Uri.fromFile(cropFile);
}
好了,这就是开始在Fragment中调用系统相机拍照并裁剪的流程,代码上应该不会有啥问题,多台测试机跑起来也都正常,拍照和裁剪我分别存在不同的本地路径下的。
然后奇怪的事情发了,上线后部分用户开始使用都挺正常的,然后用着用着一段时间后出问题了,具体出现的机型包括:魅族MX6、魅蓝note3、三星有一款、然后华为mate7和荣耀7i,具体现象是:点击拍照进入系统拍照界面后拍照完成在预览界面点击确定后出现闪退。正常流程应该是拍照后进入系统裁剪页进行裁剪然后裁剪完成上传照片嘛。
然后分析原因:
统计错误列表未发现任何错误日志返回,证明程序未报错;然后开始看网上很多人说拍照闪退是因为部分手机默认相机页面可横竖屏切换导致Activity被销毁重建了,然后按网上说的办法处理了还是不行;然后分析闪退现象猜测应该是调用系统相机时程序被系统销毁了,然后拍照后点击确认闪退回当前页面实际上我们的程序已经被重建一次了,然后数据已经没了所以闪退,ok,那就处理意外销毁的情况吧:
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
if (savedInstanceState.getString("origAbsPath")!= null) {
origAbsPath = savedInstanceState.getString("origAbsPath");
}
}
...
}
/**
* 意外结束时保存绝对路径
* @param outState
*/
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putString("origAbsPath", origAbsPath);
super.onSaveInstanceState(outState);
}
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null)
origAbsPath = savedInstanceState.getString("origAbsPath");
}
本来想保存数据了总可以调起裁剪页了吧,然并卵,依然没用,还是闪退回去了,然后没辙了,只能摸拟被销毁时的过程了,在开发者模式中把不保留后台活动些啊UN些啊ing打开了实验,并打印生命周期以及onSaveInstanceState(Bundle outState)、onViewStateRestored(@Nullable Bundle savedInstanceState)方法;
发现打开相机若之前的界面被销毁的话Fragment的onSaveInstanceState(Bundle outState)方法并没有被调用,这都是小事,然后惊奇的发现Fragment的onActivityResult(…)方法竟然并没有收到回调,exo me?然后参考一篇博客 :
——http://www.open-open.com/lib/view/open1474445652383.html
文中说多重嵌套的Fragment在support-v4到23.2.0以前的版本有个bug导致嵌套多层Fragment中无法收到回调,然后按文中办法操作后发现在程序结束并重建时被销毁的情况下拍照依然是点击确定就闪退。
然后分别打印了Activity、一层Fragment、二层Fragment的onActivityResult方法(ps.因为这里我按链接文章中的传递onActivityResult的回调结果来处理了),打印结果显示resultCode是正确的,可是requestCode发生了改变,也就是结果指向已经不对了,然后发生变化的具体原因可参考:
由于Fragment中直接使用startActivityForResult()时,传到activity中的onActivityResult中的requestCode就会不对,resultCode是对的,当然,这样的话传到每个Fragment中的onActivityResult的requestCode也是不对的。
如果用getActivity().startActivityForResult,则传出来的requestCode和rusultCode就都是对的。
到这里我纠结了很久,最终觉得逐层去传递结果太麻烦而且还会存在问题,然后结合实际情况,我两个viewpager里面的子Fragment页面都有拍照裁剪并上传的操作。so,偷了个懒,直接把这个流程整个放到他们共同的父Activity里面去操作,然后被意外结束的情况下就不会再有回调结果不一致或者收不到回调的情况出现了,因为撇开了Fragment本身。
具体操作是:
/**
* 拍照点击监听(这里自定义了接口,因为我的拍照操作是在Fragment的列表item里面)
*/
@Override
public void clickListener(View v,int hairStyleId,int customerId) {
EventBus.getDefault().post(new TakePhotoEventBus("1",hairStyleId,customerId));
}
然后通过EventBus去通知父Activity进行拍照的操作,然后把需要的信息通过EventBus传递到父Activity里面。然后在父Activity接收消息做相应处理:
@Subscribe
public void onEvent(TakePhotoEventBus eventBus){
//接收排队列表和历史记录列表的拍照请求
if (eventBus.getPageType() instanceof String){
String s = (String) eventBus.getPageType();
hairStyleId = eventBus.getHairStyleId();
customerId = eventBus.getCustomerId();
if ("1".equals(s)) {//区分Fragment
startTakePhoto();//拍照裁剪以及回调具体操作同之前代码,这里就不贴上来了
}else {
startTakePhoto();
}
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
//不调用父类的方法,用于解决fragment界面重叠问题
//super.onSaveInstanceState(outState);
...//保存需要的数据
}
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState == null){
return;
}
...//恢复相应的数据
}
这里我自己代码中其实并没有用onSaveInstanceState、onSaveInstanceState保存,偷懒直接把保存照片的path等相关成员变量用的static修饰,这样在Activity被销毁重建时该静态变量并不会被销毁,具体原因请参考文章:
——android中static修饰的变量在Activity销毁后,还存在吗
ps.第一次写博客,很多不足的地方,多多见谅。主要是对问题做个记录然后给遇到相关问题的童鞋做个参考用,源码就不贴了,无论拍照还是裁剪网上相关代码还是很多的,关键代码和流程文章里都有,有兴趣可以自己看>v<。
作者:debbytang
请大家尊重原创者版权,转载请标明出,谢谢!