在Android开发中经常会遇到需要上传图片的场景,虽然现在有很多很好的第三方框架可供使用,而且傻瓜集成,配置简单。但是有些时候总感觉第三方框架过于臃肿,用着总感觉没有自己写的来得踏实。所以让我们也任性一回,自己写一个图片上传吧。

准备工作

图片上传听起来没有多少事要做,但是小细节还是很繁杂的,更有很多藏在背后的知识。一整套下来,收获还是会很大的。那么具体会有那些步骤呢,别急,容我捋捋:

  1. 获得图片源(拍摄或选取)
  2. 编辑(获取基本信息,压缩,调整大小)
  3. 编码
  4. 上传

看吧,虽然看起来很简单的任务,认真下来,会发现有很多的知识会牵扯进来,所以废话不多说,让我们开始吧!

正式开始

获取图片源

一般我们会有两种方式来获得图片源——拍照、从系统选取。先说拍照,拍照对于国内开发者而言简直就是噩梦般的存在,由于某些厂商所谓的定制,在适配中会有各种各样莫名其妙的Bug存在,但由于篇幅有限,今天仅仅展示最常规的操作。
通常,假如需要拍照的话,我们需要在Manifest文件中声明我们需要使用相机硬件,所以需要假如特性声明

<uses-feature android:name="android.hardware.camera" />

接下来就是真正的编码工作了,一般我们有两种途径来获得拍摄结果,假如对图片的要求不是太高,我们可以选择直接获取缩略图。系统拍摄完图片后会将结果编码成一个较小的Bitmap,放在Intent对象的extra里,作为key为data的值,返回给调用者缩略图。这时候我们拿到的直接就是Bitmap对象,就可以直接进入下一步了。但这种情况一般不常见,因为缩略图的使用情况很少,一般会用来做icon。尽管简单,但是我们还是来看看怎样做吧

// 获取图片,仅仅需要缩略图
static final int TAKE_PHOTO=1;
//调用拍照
Intent intent =new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if(null != intent.resolveActivity(getPackagerManager())){startActivityForResult(intent,TAKE_PHOTO);
}@Override
// 拍摄结束
protected void onActivityResult(int requestCode,int resultCode,Intent data){if(RESULT_OK==resultCode && TAKE_PHOTO==requestCode){Bundle extras=data.getExtras();Bitmap bitmap=(Bitmap)extras.get("data");...}
}

但是假如我们需要高清大图呢,为此我们付出的代价是需要码更多的代码。
高清大图需要我们在调用相机拍摄前,提供一个Uri来提供图片的保存位置,名称等信息。既然涉及到保存,那肯定少不了权限声明:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

当然,假如你不想用户拍摄的图片公开,并且应用运行在Android 4.4及以上的平台的话,你不需要这个权限,但是你只能将图片保存在getExternalFilesDir()返回的目录下。
所以通常这个权限还是需要的。
通常,我们会从File对象获得Uri,所以接下来我们需要创建File对象。这里需要注意一个小问题,为了避免文件名冲突造成不必要的问题,我们通常将File对象的名称中加入时间戳,并且放在外部存储的图片目录中。

// 生成保存图片的File对象
private File createFile() throws IOException{//文件名String fileName="IMAG_"+new SimpleDataFormat("yyyyMMdd_HHmmss").format(new Date());//文件目录File fileDir=Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);// 创建(文件名,文件扩展名,所属目录)File target=new File(fileName,".jpg",fileDir);return target;
}// 拍摄
private void takePhoto(){Intent takePhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);if (null!=takePhoto .resolveActivity(getPackageManager())) {File photoFile = null;try {photoFile = createImageFile();} catch (IOException ex) {//异常处理...}if (photoFile != null) {takePhoto.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(photoFile));startActivityForResult(takePhoto , TAKE_PHOTO);}}
}

这样,你就可以拿到高清大图了。但是假如你需要提供给用户一种选取图片的功能呢?同样借助Intent,但是问题是通过Intent选取的图片是以Uri的方式返回的,我们希望统一风格,更希望后续操作更方便,那么怎样将其转换成绝对路径呢,这是个问题。但问题总会有解决办法的。既然我们已经通过选取动作知道了图片的Uri,那么反推一下,Android系统中,Uri用得最多的地方是哪呢?当然是四大组件之一的ContentProvider了。由此,我们是不是就可以查特定Uri的信息了,哈哈,问题解决了吗。图样图森破,这里有个深坑,4.4及以上的系统的Uri格式和之前的是不一样,哈哈,傻眼了吧,网上各种方法试过没用,多说无益,用代码说明吧

public static final int PICK_PHOTO=2;
// 通过Uri获取图片的真正路径
public static String getRealFilePath(final Context context, final Uri uri) {String filePath = "";Cursor cursor = null;String[] column = {MediaStore.Images.Media.DATA};if (Build.VERSION.SDK_INT >= 19) {String wholeID = DocumentsContract.getDocumentId(uri);String id = wholeID.split(":")[1];String sel = MediaStore.Images.Media._ID + "=?";cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,column, sel, new String[]{id}, null);} else{CursorLoader cursorLoader = new CursorLoader(context, uri, column, null, null, null);cursor = cursorLoader.loadInBackground();}if (null != cursor && cursor.moveToFirst()) {int columnIndex = cursor.getColumnIndex(column[0]);filePath = cursor.getString(columnIndex);}cursor.close();return filePath;}// 开始选取图片
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);intent.addCategory(Intent.CATEGORY_OPENABLE);intent.setType("image/*");if (null != intent.resolveActivity(getPackageManager())) {startActivityForResult(Intent.createChooser(intent, "选择图片"), CODE_PICK_PHOTO);} else {Toast.makeText(SaveDataActivity.this, "没有应用可供使用,请确保安装了相册应用", Toast.LENGTH_SHORT).show();}

至此,我们已经拿到了,图片的路径,接下来我们就可以进行下一步的工作了。

编辑

因为现在的手机拍摄出的图片一般都很大,需要进行一定的压缩,不然不仅会消耗手机内存,造成内存溢出,同时在上传图片时可能会造成图片上传失败等问题。另外有些手机拍摄出的图片可能会带有旋转角度,因此对图片进行一些常规的编辑是很有必要的。
首先,我们需要先对图片进行压缩,便于显示和上传。这就需要用到解码图片,高效显示高清大图的技能了。一般我们都是在有限的空间里显示图片的,所以我们需要根据实际需要的展示的空间来动态计算图片的缩放比例,根据谷歌的train课堂,我们知道可以用如下方法计算实际的缩放比例。

/*计算图片缩放比例*/public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {final int height = options.outHeight;final int width = options.outWidth;int inSampleSize = 1;if (height > reqHeight || width > reqWidth) {final int halfHeight = height / 2;final int halfWidth = width / 2;// Calculate the largest inSampleSize value that is a power of 2 and keeps both// height and width larger than the requested height and width.while ((halfHeight / inSampleSize) > reqHeight&& (halfWidth / inSampleSize) > reqWidth) {inSampleSize *= 2;}}return inSampleSize;}

有了缩放比例,我们就可以开心地进行真正的压缩操作了,这里我把图片的宽高设置为比较小的400*800.

 /*根据路径获取图片*/public static Bitmap getSmallBitmap(String filePath) {final BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeFile(filePath, options);options.inSampleSize = calculateInSampleSize(options, 480, 800);options.inJustDecodeBounds = false;return BitmapFactory.decodeFile(filePath, options);}

接下来让我们来修正旋转的图片。在这里就需要用到一个获取图片旋转角度的类——ExifInterface。该类提供了一系列读取和修改JPEG图片Exif信息的方法和常量,有了它我们可以很方便的获取到图片的旋转角度。该类有一个很常用的方法

public int getAttributeInt (String tag, int defaultValue)

tag是指代的属性名,defaultValue是获取不到值后返回的默认值。而指代图片旋转角度的tag是TAG_ORIENTATION,因此,我们可以用下面的方法获取到指定绝对路径下的图片的旋转角度。

// 获取图片旋转角度
public static int readPictureDegree(String path) {int degree = 0;try {ExifInterface exifInterface = new ExifInterface(path);int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL);switch (orientation) {case ExifInterface.ORIENTATION_ROTATE_90:degree = 90;break;case ExifInterface.ORIENTATION_ROTATE_180:degree = 180;break;case ExifInterface.ORIENTATION_ROTATE_270:degree = 270;break;}} catch (IOException e) {e.printStackTrace();}return degree;}

那么怎样旋转图片能呢?很简单,使用Matrix。其实大家可以记住一点,一遇到图片,必定少不了Matrix,因为Matrix和图片有太多剪不断理还乱的关系。很多高级操作都要用到Matrix。Matrix有一个设置图片旋转的方法,

public static Bitmap rotationPicture(Bitmap bitmap,int rotation){// 创建操作图片用的matrix对象Matrix matrix = new Matrix();// 旋转图片动作if (rate == 270) {matrix.postRotate(270);} else if (rate == 90) {matrix.postRotate(90);} else {matrix.postRotate(-rate);}// 创建新的图片return Bitmap.createBitmap(bitmap, 0, 0,bitmap.getWidth(), bitmap.getHeight(), matrix,true);
}

好了,基本编辑进行得差不多了,进入下一步。

编码

有些朋友可能不太清楚,上传图片时最好需要编一下码。通常情况下,会将图片转换为Base64编码。因为图片一般比较大,而Base64编码可用于在HTTP环境下传递较长的标识信息。Base64是一种二进制转文本的编码方式,使用64个 ASCII码字来替换原始字符
所以可以将二进制信息转换成文本信息,便于传输。
而在Android中,由于使用Java作为编程语言,Java的Sdk已经写好了Base64的编码,解码方法,我们可以直接用了,不再需要手动写,具体使用如下。

/*base64编码图片*/public static String bitmapToString(Bitmap bitmap) {ByteArrayOutputStream baos = new ByteArrayOutputStream();bm.compress(Bitmap.CompressFormat.JPEG, 40, baos);byte[] b = baos.toByteArray();return Base64.encodeToString(b, Base64.DEFAULT);}

这里又进行了一次压缩,压缩率为40%。

上传

终于进行到激动人心的一步了,我选择使用Retrofit框架作为上传工具。虽然Retrofit可以直接上传文件,但是我就是要自己写,你打我啊。开玩笑,我不想说的是,由于服务器端要求上传Base64编码的图片,和一系列标识信息,所以宝宝心里苦啊,但是宝宝还是要说。其实Retrofit上传图片,网上一大堆的教程,但是都用不上,这可愁死我了。肿么办,还是自己动手丰衣足食。
定义接口走起

public interface UploadData {@FormUrlEncoded@POST("saveproject")Call<ResponseBody> upload(@FieldMap Map<String, Object> fields);
}

上传走起

public static boolean update(String param1, String param2, String param3, int type, String imgData) {Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL).build();Map<String, Object> params = new HashMap<>();params.put("param1", param1);params.put("param2", param2);params.put("param3", param3);params.put("type", new Integer(type));if (null != data) {params.put("datas", data);}UploadData uploadData = retrofit.create(UploadData.class);Call<ResponseBody> call = uploadData.upload(params);try {ResponseBody body = call.execute().body();if (null == body) {return false;}JSONArray array = new JSONArray(body.string());return array.getJSONObject(0).getInt("flag") == 1;} catch (IOException e) {e.printStackTrace();return false;} catch (JSONException e) {return false;}}

ok,打完收工,虽然简单粗暴,但是管用啊,今天先写到这里,要吃饭去了。

玩转 Android图片上传相关推荐

  1. 图片上传压缩android,android 图片上传压缩常见问题分析

    图片的上传与压缩是android经常需要用到的步骤,那么,如何解决上传图片oom问题呢?android 图片上传压缩常见问题分析,希望可以帮助大家更加的了解android 图片方面的困惑. 下面,是我 ...

  2. Android图片上传的两种方式

    图片上传,以及带参数的图片上传是Android开发中,很常见的需求.但也是接口联调难度相对比较大的技术实现,本文介绍两种可靠的图片上传方式.一是通过 MultipartBody 来实现:二是通过图片转 ...

  3. Android图片上传服务器(File格式)

    1.首先我们需要声明几个变量 TreeMap<String, String> paramsMap;. RequestBody requestBody = null; Request req ...

  4. Android图片上传(头像裁切+原图原样)

    还是那句话,最近项目比较忙拖了很久这篇文章终于完成了! 先看一下效果图: (一)头像裁切.上传服务器(效果图) 一般都是有圆形显示头像的,这里我自定义了一个ImageView,页面很干净但是看着很上档 ...

  5. android图片上传后台后旋转的关键原因:ExifInterface

    先说照片 出问题的都是jpg照片,一个完整的照片分为两部分, 一部分是:照片信息,照片信息存有里有照片里的很多东西,包括照片方向,拍摄位置,分辨率,长宽高等等,在这里对我们影响最大的是:旋转的方向以及 ...

  6. 第二篇 ( wcf 与 android 图片上传下载)

    老规矩废话不多说,直接入主题 注:wcf 使用rest风格,传递json数据,图片是经过base64编码,android 使用common-codec-1.5.jar 进行base64编码 服务器端 ...

  7. android 图片预览动画,Android图片上传实现预览效果

    首先具体分析一下,实现的功能,其中需求分析是必不可少的,需求.逻辑清除之后,再上手写代码,思路会很清晰. 1.多图上传首先得选择图片(这里项目需求是既可以拍照上传也可以从相册中选择) 2.拍照上传很简 ...

  8. php android 图片上传,android上传图片到PHP的过程详解

    这篇文章主要介绍了android上传图片到PHP的过程详解,需要的朋友可以参考下 今天在做上传头像的时候,总是提交连接超时错误,报错信息如下:XXXXXXSokcetTimeOutXXXXXXXX 然 ...

  9. android 图片上传java,php服务器

    项目上传图片到后台,前端总是传不上去,翻阅代码,详细查看,原来是php的头和java的不同. 总体的思路是,可以拍照上传,也可以本地上传.利用onActivityResult,从返回的Intent中得 ...

最新文章

  1. 自己动手实现OpenGL-OpenGL原来如此简单(二)
  2. 【Spring注解系列11】Spring后置处理器BeanPostProcessor用法与原理
  3. 笔记-信息化与系统集成技术-国务院关于印发新一代人工智能发展规划的通知...
  4. MacOS 好用的插件和图形界面程序
  5. python+[:]+切片_我从C ++到Python的方式:概念上的改变
  6. python二分法查找算法_排序算法和二分法查找
  7. c 服务器文件路径,c打开服务器文件路径
  8. tensorflow随笔——VGG网络
  9. 【损失函数】一文弄懂各种loss function
  10. 7-8 评委打分 (5 分)
  11. java mq5.15,ActiveMQ 5.15.x Release安装和配置--Linux篇
  12. 用海康威视摄像头做图像处理问题
  13. 多领域中文语音识别数据集 WenetSpeech 正式发布——有效下载教程
  14. 5G NR CSI Report中的codebook/PMI
  15. [矩阵论] 谱半径小于1,则I-A可逆
  16. Unity三步实现动态头像(RawImage用法)
  17. 计算机中文核心期刊哪个快,计算机中文核心期刊要多久cpci加急,又快又水的sci期刊...
  18. 水文章(bushi)
  19. Python-shogun安装问题
  20. Linux内核学习方法

热门文章

  1. 【专题三】第三弹!全球首个微信应用号开发教程!通宵吐血赶稿,每日更新!
  2. Visio画盒图(N-S图)
  3. 软件工程 用例建模 习题
  4. vim插件管理器:Vundle的介绍及安装(很全)
  5. 【Unity3D】公告栏与开始界面的布置
  6. 有趣的C++代码(上)
  7. mac os netcat -lp 不能使用情况解决
  8. 荣耀linux电脑开机后黑屏,华为电脑开机屏幕没反应(开机黑屏的3个原因及解决法)...
  9. Android开发 PK IOS开发
  10. 一个 Angular 程序员两年多的远程办公经验分享