Android 调用系统功能实现图片选择器会遇到的问题汇总资料.docx
- 文档编号:7423475
- 上传时间:2023-01-23
- 格式:DOCX
- 页数:11
- 大小:20.29KB
Android 调用系统功能实现图片选择器会遇到的问题汇总资料.docx
《Android 调用系统功能实现图片选择器会遇到的问题汇总资料.docx》由会员分享,可在线阅读,更多相关《Android 调用系统功能实现图片选择器会遇到的问题汇总资料.docx(11页珍藏版)》请在冰豆网上搜索。
Android调用系统功能实现图片选择器会遇到的问题汇总资料
Android调用系统功能实现图片选择器会遇到的问题汇总
图片选择器在手机应用中屡见不鲜,设置头像、聊天传图等常见类似场景都需要使用。
为了保持不同设备上体验的一致性和较好的兼容性,比较稳妥的做法是在应用内自实现相机拍照、相册选图和图片裁剪功能。
但是,这个实现过程比较复杂,费时费力。
更多时候,或者说在项目初期,我们都会选择直接调用系统提供的这些功能来完成一个图片选择器。
然而,由于安卓设备的多样性,总会遇到各种各样的兼容问题。
本文就来总结总结,调用系统相机、相册和裁剪功能实现图片选择器的过程中,我们需要注意的一些地方。
示例代码
这里简单使用一个示例代码,演示调用系统相机或相册,获取图片,然后使用系统裁剪功能处理图片,并显示到一个ImageButton视图里面:
publicclassMainActivityextendsFragmentActivity{
publicstaticfinalintREQUEST_CAMERA=1;
publicstaticfinalintREQUEST_ALBUM=2;
publicstaticfinalintREQUEST_CROP=3;
publicstaticfinalStringIMAGE_UNSPECIFIED="image/*";
privateImageButtonmPictureIb;
privateFilemImageFile;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPictureIb=(ImageButton)findViewById(R.id.ib_picture);
}
publicvoidonClickPicker(Viewv){
newAlertDialog.Builder(this)
.setTitle("选择照片")
.setItems(newString[]{"拍照","相册"},newOnClickListener(){
@Override
publicvoidonClick(DialogInterfacedialogInterface,inti){
if(i==0){
selectCamera();
}else{
selectAlbum();
}
}
})
.create()
.show();
}
privatevoidselectCamera(){
createImageFile();
if(!
mImageFile.exists()){
return;
}
IntentcameraIntent=newIntent(MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(mImageFile));
startActivityForResult(cameraIntent,REQUEST_CAMERA);
}
privatevoidselectAlbum(){
IntentalbumIntent=newIntent(Intent.ACTION_PICK);
albumIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,IMAGE_UNSPECIFIED);
startActivityForResult(albumIntent,REQUEST_ALBUM);
}
privatevoidcropImage(Uriuri){
Intentintent=newIntent("com.android.camera.action.CROP");
intent.setDataAndType(uri,IMAGE_UNSPECIFIED);
intent.putExtra("crop","true");
intent.putExtra("aspectX",1);
intent.putExtra("aspectY",1);
intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(mImageFile));
startActivityForResult(intent,REQUEST_CROP);
}
privatevoidcreateImageFile(){
mImageFile=newFile(Environment.getExternalStorageDirectory(),System.currentTimeMillis()+".jpg");
try{
mImageFile.createNewFile();
}catch(IOExceptione){
e.printStackTrace();
Toast.makeText(this,"出错啦",Toast.LENGTH_SHORT).show();
}
}
@Override
protectedvoidonActivityResult(intrequestCode,intresultCode,Intentdata){
super.onActivityResult(requestCode,resultCode,data);
if(RESULT_OK!
=resultCode){
return;
}
switch(requestCode){
caseREQUEST_CAMERA:
cropImage(Uri.fromFile(mImageFile));
break;
caseREQUEST_ALBUM:
createImageFile();
if(!
mImageFile.exists()){
return;
}
Uriuri=data.getData();
if(uri!
=null){
cropImage(uri);
}
break;
caseREQUEST_CROP:
mPictureIb.setImageURI(Uri.fromFile(mImageFile));
break;
}
}
}
拍照图片存储问题
调用系统相机实现拍照功能的核心代码如下:
IntentcameraIntent=newIntent(MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(mImageFile));
startActivityForResult(cameraIntent,REQUEST_CAMERA);
其中MediaStore.EXTRA_OUTPUT数据表示,拍照所得图片保存到指定目录下的文件(一般会在SD卡中创建当前应用的目录,并创建临时文件保存图片)。
然后,在onActivityResult方法中根据文件路径获取图片。
如果不为intent添加该数据的话,将在onActivityResult的intent对象中返回一个Bitmap对象,通过如下代码获取:
Bitmapbmp=data.getParcelableExtra("data");
值得注意的是,这里的Bitmap对象是拍照所得图片的一个缩略图,尺寸很小!
系统这么做也是充分考虑到应用的内存占用问题。
试想一下,如今手机设备中高清相机拍出来的照片,一张图的大小高达十几兆,如果返回这么大的图片,内存占用相当严重,何况很多时候知识临时使用而已。
所以,调用系统相机时,一般都会添加MediaStore.EXTRA_OUTPUT参数,避免返回Bitmap对象。
当然,这么做也能保证应用产生的数据,包括文件,都能存储在应用目录下,方便清理缓存时统一清除。
拍照图片旋转问题
部分手机,比如三星手机,调用系统相机拍照所得的照片可能会发生自动旋转问题,常见为旋转90°。
所以,要求我们在拍照之后,使用图片之前,判断图片是否发生过旋转,如果是,要将照片旋转回来。
这是获取图片旋转角度的代码:
/**
*获取图片旋转角度
*@parampath图片路径
*@return
*/
privateintparseImageDegree(Stringpath){
intdegree=0;
try{
ExifInterfaceexifInterface=newExifInterface(path);
intorientation=exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL);
switch(entation){
caseExifInterface.ORIENTATION_ROTATE_90:
degree=90;
break;
caseExifInterface.ORIENTATION_ROTATE_180:
degree=180;
break;
caseExifInterface.ORIENTATION_ROTATE_270:
degree=270;
break;
}
}catch(IOExceptione){
e.printStackTrace();
}
returndegree;
}
这是根据指定角度旋转图片的代码:
/**
*图片旋转操作
*
*@parambm需要旋转的图片
*@paramdegree旋转角度
*@return旋转后的图片
*/
privateBitmaprotateBitmap(Bitmapbm,intdegree){
BitmapreturnBm=null;
Matrixmatrix=newMatrix();
matrix.postRotate(degree);
try{
returnBm=Bitmap.createBitmap(bm,0,0,bm.getWidth(),bm.getHeight(),matrix,true);
}catch(OutOfMemoryErrore){
}
if(returnBm==null){
returnBm=bm;
}
if(bm!
=returnBm){
bm.recycle();
}
returnreturnBm;
}
横竖屏切换问题
在部分手机,调用系统拍照功能时,可能会发生横竖屏切换过程,导致返回应用时当前Activity发生销毁重建,各个生命周期又重新走了一遍。
此时,一些应用内的变量数据可能丢失,使用时容易发生空值异常,进而导致app崩溃退出。
为了避免这种现象,我们需要在AndroidManifest.xml文件的对应
android:
configChanges="orientation|screenSize"
这样,当发生屏幕旋转时,不会导致Activity销毁重建,而是执行onConfigurationChanged()方法:
@Override
publicvoidonConfigurationChanged(ConfigurationnewConfig){
super.onConfigurationChanged(newConfig);
}
调用系统裁剪问题
示例中调用系统裁剪的代码如下:
Intentintent=newIntent("com.android.camera.action.CROP");
intent.setDataAndType(uri,IMAGE_UNSPECIFIED);
intent.putExtra("crop","true");
intent.putExtra("aspectX",1);
intent.putExtra("aspectY",1);
intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(mImageFile));
startActivityForResult(intent,REQUEST_CROP);
可以看出,调用系统裁剪功能,需要设置一些Extra参数,很多人容易在这里产生疑惑,不知如何取舍,如何设值。
这里列举一下常用的Extra名字、值类型和作用:
crop:
String类型数据,发送裁剪信号
aspectX和aspectY:
int类型数据,设置裁剪框的X与Y值比例
outputX和outputY:
int类型数据,设置裁剪输出的图片大小
scale:
boolean类型数据,设置是否支持裁剪缩放
return-data:
boolean类型数据,设置是否在onActivityResult方法的intent值中返回Bitmap对象
MediaStore.EXTRA_OUTPUT:
Uri类型数据,设置是否将裁剪结果保存到指定文件中
需要注意的是:
第一,设置return-data参数为true时,返回的Bitmap对象也为缩略图,获取方式与前面所述相机拍照获取Bitmap的方式一致;
第二,调用系统相册并裁剪时,如果使用MediaStore.EXTRA_OUTPUT参数,Uri尽量不要设置为源文件对应的Uri值,另做保存,不损坏系统相册中的源图文件;
第三,根据经验,outputX与outputY值设置太大时,容易出现卡屏现象;
第四,可以不设置outputX与outputY参数,使用户根据自身按比例自由裁剪,就像示例代码这样。
setImageURI()注意事项
你可能会用到setImageURI()方法给ImageView设置图片内容,这里也有一个地方需要注意。
我们先看一下这个方法的源码:
publicvoidsetImageURI(Uriuri){
if(mResource!
=0||
(mUri!
=uri&&
(uri==null||mUri==null||!
uri.equals(mUri)))){
updateDrawable(null);
mResource=0;
mUri=uri;
finalintoldWidth=mDrawableWidth;
finalintoldHeight=mDrawableHeight;
resolveUri();
if(oldWidth!
=mDrawableWidth||oldHeight!
=mDrawableHeight){
requestLayout();
}
invalidate();
}
}
可以看到,这里的uri参数在内部持有缓存变量,当多次调用该方法而uri参数值不变时,图片展示内容不变。
问题就在这,如果你多次拍照或裁剪保存的图片文件路径相同时,虽然每次处理过后实际存储的文件内容发生变化,但由于路径相同,uri参数一致,导致多次调用setImageURI()设置图片内容时,ImageView显示内容不变!
这也是为什么示例代码中我用时间戳处理图片文件名的原因所在,保证每次存储的图片路径不同。
根据Uri获取文件地址
有时候,我们需要根据Uri获取文件路径。
比如如果你不需要使用裁剪功能的话,调用系统相册选择图片后返回的就是一个Uri对象,我们需要从这个Uri对象中解析出对应的图片文件路径,便于上传至服务器等后续处理。
比如,这个Uri对象可能是:
content:
//media/external/images/media/3066
很多朋友相信有过这样的经验,使用toString()或者getPath()方法获取Uri对象所对应的文件路径,其实这是错误的!
通过getPath()获取的结果字符串是:
media/external/images/media/3066
而正确的获取方式是:
privateStringparseFilePath(Uriuri){
String[]filePathColumn={MediaStore.Images.Media.DATA};
Cursorcursor=getContentResolver().query(uri,filePathColumn,null,null,null);
cursor.moveToFirst();
intcolumnIndex=cursor.getColumnIndex(filePathColumn[0]);
StringpicturePath=cursor.getString(columnIndex);
cursor.close();
returnpicturePath;
}
其对应的文件路径应该是这个样子的:
/storage/emulated/0/Pictures/Screenshots/S70302-131606.jpg
Base64文件编码处理
现在很多网络框架内部都做了封装处理,上传图片时只需要传递一个文件路径即可。
但是,少数情况下,根据服务器需要,我们要对图片文件字节流编码后再上传。
这是使用Base64编码并根据字节数组获取字符串的处理过程:
publicstaticStringfileToBase64String(StringfilePath){
FilephotoFile=newFile(filePath);
try{
FileInputStreamfis=newFileInputStream(photoFile);
ByteArrayOutputStreambaos=newByteArrayOutputStream(10000);
byte[]fer=newbyte[1000];
while(fis.read(buffer)!
=-1){
baos.write(buffer);
}
baos.close();
fis.close();
returnArrays.toString(Base64.encode(baos.toByteArray(),Base64.DEFAULT));
}catch(IOExceptione){
e.printStackTrace();
}
returnnull;
}
zip压缩文件处理
当上传多张图片至服务器时,为了提升传输效率,往往会采用zip格式压缩处理。
这里提供一个递归压缩代码,方便大家有需要的时候借鉴参考:
publicStringzipCompass(StringfilePath){
FilezipFile=newFile(Environment.getExternalStorageDirectory(),System.currentTimeMillis()+".zip");
try{
//指定了两个待压缩的文件,都在assets目录中
String[]filenames=newString[]{"activity_main.xml","strings.xml"};
FileOutputStreamfos=newFileOutputStream(zipFile);
ZipOutputStreamzos=newZipOutputStream(fos);
inti=1;
//枚举filenames中的所有待压缩文件
while(i<=filenames.length){
//从filenames数组中取出当前待压缩的文件名,作为压缩后的名称,以保证压缩前后文件名一致
ZipEntryzipEntry=newZipEntry(filenames[i-1]);
//打开当前的zipEntry对象
zos.putNextEntry(zipEntry);
FileInputStreamis=newFileInputStream(filePath);
byte[]ffer=newbyte[8192];
intcount=0;
//写入数据
while((count=is.read(buffer))>=0){
zos.write(buffer,0,count);
}
zos.flush();
zos.closeEntry();
is.close();
i++;
}
zos.finish();
zos.close();
returnzipFile.getAbsolutePath();
}
catch(Exceptione){
Toast.makeText(this,e.getMessage(),Toast.LENGTH_LONG).show();
returnnull;
}
}
添加系统权限
说了这么多,别忘了在AndroidManifest.xml文件中添加系统权限(前面示例代码中没有考虑到Android6.0运行时权限的问题,实际使用时注意添加处理):
name="android.permission.CAMERA"/> name="android.permission.READ_EXTERNAL_STORAGE"/> name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Android 调用系统功能实现图片选择器会遇到的问题汇总资料 调用 系统 功能 实现 图片 选择器 遇到 问题 汇总 资料