<span style="font-size:18px;">在一个项目里面,有一个需求是让用户自己选择图片,然后上传到服务器。看似一个很简单的需求,就是选择图片,把图片装好,然后通过网络请求上传到后台,OK。但是事实并非如此,因为我们可以android项目,他是open的,他有更多的可能性,当然你也会遇到更多古灵精怪的问题。</span>
获取图片有3种方法,一是自己用surface控件,利用镜头来获取图片;二是调用系统相机,并且返回拍到的图片;三是直接在利用图库获取本地图片;这里我只使用后面两种方法来获取图片(系统提供了方法,为什么不用呢,还要费那么大劲去开发一个新的已经有了的功能,不重复造轮子)。在这过程中,我遇到了好几个很奇葩和让人难以理解的问题。弄了我大半天时间,现在做一下记录。
我所遇到的问题大概有以下:
1、利用系统图库获取图片,返回的图片地址,因系统不同而不同;
2、利用拍照返回的照片是经过压缩的,分辨率很低,压根看不清楚;
3、
第一个问题:返回的图片地址不同。为什么这样讲呢,因为我们知道android已经有很多的深度定制的系统,像是小米,华为,魅族,锤子...等等,都对原生的android系统作了修改优化,至少我不知道它们能在底层改了什么东西,我们只有直面底层的返回,去适应它。在这里,我们利用显示intent方式打开本地图库,代码如下
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);//设置动作
<span style="font-size:18px;"><span style="white-space:pre"> </span>intent.setType("image/*");//开启Pictures画面Type设定为image <span style="white-space:pre"> </span>startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE1);</span>这样就可以打开系统的图库来选去需要的图片了。
那么,问题就来了。我在魅蓝2上面打开后,系统跳到的是最近的拍照照片,而在小米2s上面也是跳到最近的拍摄的图片集合里面去,在华为honor6 ?上面则是跳到最近编辑过的图片集合里面去了。最后在onactivityresult()回调方法里面利用以下代码得到图片地址:
<span style="font-size:18px;"><span style="white-space:pre"> </span>Uri uri = data.getData(); String pathImg = uri.getPath();</span>魅蓝获取到2获取到的图片路径是:/external/images/media/640543,小米2s的路径是:/storage/sdcard0/DCIM/Camera/IMG_20160217_142658.jpg,华为的类似魅蓝2的路径,这里没有打印出来。在这里我要的是选取图片的完整路径,如果不用不需要完整路径,也是可以获取到图片的,代码如下:
Bitmap bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri)); 这个方法返回来的是一个bitmap,一看到bitmap应该立即想到的是oom,这是一个程序猿又爱又恨的东西啊。现在的手机随便拍个照,都有1000(+)*1000(+)的像素,在android里面一个像素点,用4个字节存放。所以,读入一个1000*1000像素的图片,读入来,占用的字节数有1000*1000*4.这对一个程序来说,几乎是致命的。这样的做法显然不可取。
在上面返回的路径看来,小米2s返回的路径是对的,但是我的代码还要在魅蓝上运行,显然也是不通过的。所以我又找了另外一个办法来获取返回的图片路径:
String picturePath = ""; String[] proj = {MediaStore.Images.Media.DATA}; Cursor cursor = getContentResolver().query(uri, proj, null, null, null); if (cursor != null && cursor.moveToFirst()) { int columnIndex = cursor.getColumnIndex(proj[0]); picturePath = cursor.getString(columnIndex); System.out.println("-=-==->>picturePath = " + picturePath); }此方法,在华为honor6?上面返回的的cursor为null,魅蓝上是OK的,小米的也是null。这就让人很费解了。同样是显示调用系统的方法,但是返回来的却是截然不同的东西。而我的目标是要不oom和完整的路径。
所以在这里,我用了一个猥琐的方法来绕过这个坑。就是我同时用两种方法来获取两个路径,然后判断哪一个路径是不为null,并且带有图片格式后缀的路径,就拿来用。
这里的用,是压缩图片后,再将压缩图片读进内存来。以下是我的方法:
</pre>
/** * 根据图片路径,得到压缩过的位图 * * @param path * @param width * @param height * @return */ public Bitmap getPressedBitmap(String path, int width, int height) { BitmapFactory.Options options = new BitmapFactory.Options();//new一个options options.inJustDecodeBounds = true;//先设置为true,即不读入图片到内存,先获取图片的信息,比如长宽等信息 Bitmap bitmap = BitmapFactory.decodeFile(path, options);//此句代码是真正的去读取图片的长宽等信息,并且存储在options里面,在后面的代码中我们可以看到options.outWidth 和 options.outHeight得到的是图片的宽和高。这句代码所以不能没有,否则无法压缩图片。这里得到的bitmap是为null options.inSampleSize = getBitmapSampleSize(options, width, height) ;//根据给定的宽高来压缩图片的比例 options.inJustDecodeBounds = false;//设置为false,是要将图片以一定比例压缩后读入内存中<span style="white-space:pre"> </span>Bitmap bitmap1 = BitmapFactory.decodeFile(path, options);// 这里得到的bitmap才是不为null return bitmap1;}<pre name="code" class="java">/** * 根据要去的宽高,压缩图片 * * @param options * @param reqWidth * @param reqHeight * @return */ public int getBitmapSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { int imgWidth = options.outWidth; int imgHeight = options.outHeight; int inSimpleSize = 1; if (imgWidth > imgHeight || imgWidth < imgHeight) { final int heightRatio = imgWidth / reqWidth; final int widthRatio = imgHeight / reqHeight; inSimpleSize = widthRatio < heightRatio ? widthRatio : heightRatio; } return inSimpleSize; }
最后,我将图片选取好,上传到后台,发现图片的像素很低,几乎看不清上传的是什么,这个也是不符合需求的。所以要修改。我发现把options.inSampleSize = 5;//getBitmapSampleSize(options, width, height)改成这样子,inSampleSize改为你觉得可以看清楚的int就OK了。(这里提醒一下下,也要注意读取进来的bitmap的大小,图片也不能太大,因为上传是耗费流量的,我们一定要站在用户的角度去想问题)最后,问题实际上解决了,但是理论上还没有,想不通,怎么会我也没有时间去一一对看源码。等找个空时间,再去扒源码。
对于第二个问题:利用拍照返回的照片是经过压缩的,分辨率很低,压根看不清楚。我们用显式intent来打开系统摄像头,然后回传图片信息,代码如下:bundle = data.getExtras(); Bitmap bitmap = (Bitmap) bundle.get("data");// 获取相机返回的数据,并转换为Bitmap图片格式可以看到,调用系统相机拍照所返回的图片数据,是放到bundle里面的。我将bitmap拿出来,发现像素也是低到不行,根本没办法看,原来返回的是压缩得不行不行的图片。这当然也不行了。发现拍照的图片也没有保存在本地,不能重复使用那张图片。既然这样,我就查资料,想着系统应该会有方法设置可以返回原图的,但是我暂时没有找到。然后看到另外一个方法,先把拍照的照片存在指定的位置,然后利用指定位置去获取原图,压缩后,在读取进来。
一开始在启动调用系统相机的时候,指定照片存储的位置。
// 利用系统自带的相机应用:拍照 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File filePath = getTakePhotoPath();//得到图片文件存储的路径 Uri imgUri = null; imgUri = Uri.fromFile(filePath); cacheImgPath = filePath.getAbsolutePath();//将图片的路径保存起来 intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri);//把路径传给系统,系统会自动存储到你指定的路径下 getParent().startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE2);
/** * 返回一个存储拍照的路径 * * @return path */ private File getTakePhotoPath() { SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss"); Date date = new Date(System.currentTimeMillis()); String fileName = format.format(date); File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM); String imgPath = file.getAbsolutePath() + "/test/"; File fileDcim = new File(imgPath); if (!fileDcim.exists()) { fileDcim.mkdirs(); } File filePicPath = new File(fileDcim, "SBD_" + fileName + ".jpg"); if (!filePicPath.exists()) { try { filePicPath.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } String path = filePicPath.getAbsolutePath(); if (path == null) { return null; } else { return filePicPath; } }在onactivityresult()回调中,利用保存起来的的路径,像上面一样获取本地图片的压缩图就OK了。当然,在做以上操作时,千万别忘了要添加相应的权限。
以上就是我个人对这个问题的看法和解决方法,如果发现有何不对,或者有更好的方法解决问题,请赐教!