編輯:關於Android編程
之前寫過一個對圖片進行高保真壓縮的文章,把圖片壓縮的這麼小當然是為了上傳的,這次就把圖片批量上傳的代碼也一起貼出來,這個方法是基於xUtils的Http模塊
首先這個上傳過程要滿足一下特性
1、開啟多個線程進行圖片的批量同時上傳
2、每張圖片的上傳進度都可以獲取到,並且顯示在界面上
3、如果有一張圖片上傳失敗就宣布上傳過程失敗,然後等待用戶再次發起同樣的上傳命令
4、所有圖片均上傳成功後有一個完畢的回調
5、每張圖片上傳完畢後要從上傳服務器得到一個json記錄上傳圖片的id
6、對於已經上傳過的圖片不再上傳第二遍
7、對於已經壓縮過的圖片不再壓縮第二遍
8、對於像三星這樣的手機,拍攝出來的照片都是寬度大於高度的躺著的照片,需要根據exif信息將圖片旋轉後再上傳
9、對於壓縮的照片,保留原來照片的exif信息
這個工具類裡面還包含了幾個額外的方法
1、對於sd卡中的一張圖片,添加到系統相冊中並保存縮略圖
2、復制一張照片的exif信息到另一張照片
下面就是上傳過程加壓縮的代碼,其中有一部分是《Android 高質量高壓縮比圖像壓縮》裡面的代碼
package com.imaginato.qravedconsumer.utils;
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.util.Log;
import com.imaginato.qravedconsumer.task.AlxAsynTask;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.RequestParams;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
/**
* Created by Alex on 2016/4/6.
*/
public class AlxBitmapUtils {
/**
* 傳入一個bitmap,根據傳入比例進行大小縮放
* @param bitmap
* @param widthRatio 寬度比例,縮小就比1小,放大就比1大
* @param heightRatio
* @return
*/
public static Bitmap scaleBitmap(Bitmap bitmap, float widthRatio, float heightRatio) {
Matrix matrix = new Matrix();
matrix.postScale(widthRatio,heightRatio);
return Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
}
/**
* 傳入圖片路徑,根據圖片進行壓縮,僅壓縮大小,不壓縮質量,
* @param oriFile 源文件
* @param targetFile 這個和 stream傳一個就行,只有穿file的時候才會保留exif信息,但是都會根據旋轉角度進行旋轉
* @param ifDel 是否需要在壓縮完畢後刪除原圖
*/
public static void compressImage(File oriFile, File targetFile, OutputStream stream,boolean ifDel,int rotateDegree,boolean keepExif) {
if(oriFile ==null || !oriFile.isFile()|| !oriFile.exists())return;
// Log.i("Alex","源圖片為"+oriFile);
// Log.i("Alex","目標地址為"+targetFile);
try {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true; // 不讀取像素數組到內存中,僅讀取圖片的信息,非常重要
BitmapFactory.decodeFile(oriFile.getAbsolutePath(), opts);//讀取文件信息,存放到Options對象中
// 從Options中獲取圖片的分辨率
int imageHeight = opts.outHeight;
int imageWidth = opts.outWidth;
int longEdge = Math.max(imageHeight,imageWidth);//取出寬高中的長邊
int pixelCount = (imageWidth*imageHeight)>>20;//看看這張照片有多少百萬像素
// Log.i("Alex","圖片寬為"+imageWidth+"圖片高為"+imageHeight+"圖片像素數為"+pixelCount+"百萬像素");
long size = oriFile.length();
Log.i("Alex","f.length 圖片大小為"+(size>>10)+" KB");
//走到這一步的時候,內存裡還沒有bitmap
Bitmap bitmap = null;
if(pixelCount > 3){//如果超過了3百萬像素,那麼就首先對大小進行壓縮
float compressRatio = longEdge /1280f;
int compressRatioInt = Math.round(compressRatio);
if(compressRatioInt%2!=0 && compressRatioInt!=1)compressRatioInt++;//如果是奇數的話,就給弄成偶數
// Log.i("Alex","長寬壓縮比是"+compressRatio+" 偶數化後"+compressRatioInt);
//尺寸壓縮
BitmapFactory.Options options = new BitmapFactory.Options();
//目標出來的大小1024*1024 1百萬像素,100k
options.inSampleSize = Math.round(compressRatioInt);//注意,此處必須是偶數,根據計算好的比例進行壓縮,如果長邊沒有超過1280*1.5,就不去壓縮,否則就壓縮成原來的一半
options.inJustDecodeBounds = false;//在decode file的時候,不僅讀取圖片的信息,還把像素數組到內存
options.inPreferredConfig = Bitmap.Config.RGB_565;//每個像素占四位,即R=5,G=6,B=5,沒有透明度,那麼一個像素點占5+6+5=16位
//現在開始將bitmap放入內存
bitmap = BitmapFactory.decodeFile(oriFile.getAbsolutePath(), options);//根據壓縮比取出大小已經壓縮好的bitmap
//此處會打印出存入內存的bitmap大小
}else if(imageHeight*imageWidth>2073600){//如果圖片大於1920*1080,為了減少內存開銷,使用RGB——565
BitmapFactory.Options options = new BitmapFactory.Options();
//目標出來的大小1024*1024 1百萬像素,100k
options.inSampleSize = 1;//注意,此處必須是偶數,根據計算好的比例進行壓縮,如果長邊沒有超過1280*1.5,就不去壓縮,否則就壓縮成原來的一半
options.inJustDecodeBounds = false;//在decode file的時候,不僅讀取圖片的信息,還把像素數組到內存
options.inPreferredConfig = Bitmap.Config.RGB_565;//每個像素占四位,即R=5,G=6,B=5,沒有透明度,那麼一個像素點占5+6+5=16位
//現在開始將bitmap放入內存
bitmap = BitmapFactory.decodeFile(oriFile.getAbsolutePath(), options);//根據壓縮比取出大小已經壓縮好的bitmap
//此處會打印出存入內存的bitmap大小
}else {//如果是長圖或者長邊短於1920的圖,那麼只進行質量壓縮
// 現在開始將bitmap放入內存
bitmap = BitmapFactory.decodeFile(oriFile.getAbsolutePath());
//此處會打印出bitmap在內存中占得大小
}
if(rotateDegree!=0)bitmap = AlxImageLoader.rotateBitmap(bitmap,rotateDegree,true);//如果設定了旋轉角度,那麼就旋轉這個bitmap
ExifInterface exif = null;
if(keepExif) exif = new ExifInterface(oriFile.getAbsolutePath());
if(targetFile!=null)compressMethodAndSave(bitmap, targetFile,exif);
if(stream!=null)compressBitmapToStream(bitmap,stream);
if(ifDel) oriFile.delete();//是否要刪除源文件
System.gc();
}catch (Exception e){
Log.d("Alex",""+e.getMessage().toString());
}catch (OutOfMemoryError e) {
Log.i("Alex","壓縮bitmap OOM了",e);
}
}
/**
* 獲取一個bitmap在內存中所占的大小
* @param image
* @return
*/
public static int getSize(Bitmap image){
int size=0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //API 19
size = image.getAllocationByteCount();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {//API 12
size = image.getByteCount();
} else {
size = image.getRowBytes() * image.getHeight();
}
return size;
}
/**
* 根據傳來的bitmap的大小計算一個質量壓縮率,並且保存到指定路徑中去,只壓縮質量,不壓縮大小
* @param image
* @param targetFile
*/
public static void compressMethodAndSave(Bitmap image, File targetFile, ExifInterface exif){
try {
OutputStream stream = new FileOutputStream(targetFile);
int size = compressBitmapToStream(image,stream);
if(size==0)return;
if(exif!=null) copyExifTags(targetFile,exif);//如果傳來的exif,就給壓縮完的圖片設置這個exif
long afterSize = targetFile.length();
Log.i("Alex","壓縮完後圖片大小"+(afterSize>>10)+"KB 壓縮率:::"+afterSize*100/size+"%");
}catch (Exception e){
Log.i("Alex","壓縮圖片出現異常",e);
}
}
public static int compressBitmapToStream(Bitmap image,OutputStream stream){
if(image==null || stream==null)return 0;
try {
Bitmap.CompressFormat format = Bitmap.CompressFormat.JPEG;
int size = getSize(image);
Log.i("Alex","存入內寸的bitmap大小是"+(size>>10)+" KB 寬度是"+image.getWidth()+" 高度是"+image.getHeight());
int quality = getQuality(size);//根據圖像的大小得到合適的有損壓縮質量
Log.i("Alex","目前適用的有損壓縮率是"+quality);
long startTime = System.currentTimeMillis();
image.compress(format, quality, stream);//壓縮文件並且輸出
if (image != null) image.recycle();//此處把bitmap從內存中移除
Log.i("Alex","壓縮圖片並且存儲的耗時"+(System.currentTimeMillis()-startTime));
return size;
}catch (Exception e){
Log.i("Alex","壓縮圖片出現異常",e);
}
return 0;
}
/**
* 復制一個圖片的exif信息到另一個圖片
* @param targetFile
* @return
*/
public static boolean copyExifTags(File targetFile,ExifInterface sourceExif){
if(sourceExif == null || targetFile==null || !targetFile.isFile())return false;
try {
ExifInterface targetExif = new ExifInterface(targetFile.getAbsolutePath());
Log.i("Alex","源圖片有縮略圖?"+sourceExif.hasThumbnail());
targetExif.setAttribute(ExifInterface.TAG_APERTURE,sourceExif.getAttribute(ExifInterface.TAG_APERTURE));
targetExif.setAttribute(ExifInterface.TAG_DATETIME,sourceExif.getAttribute(ExifInterface.TAG_DATETIME));
targetExif.setAttribute(ExifInterface.TAG_EXPOSURE_TIME,sourceExif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME));
targetExif.setAttribute(ExifInterface.TAG_FLASH,sourceExif.getAttribute(ExifInterface.TAG_FLASH));
targetExif.setAttribute(ExifInterface.TAG_FOCAL_LENGTH,sourceExif.getAttribute(ExifInterface.TAG_FOCAL_LENGTH));
targetExif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE,sourceExif.getAttribute(ExifInterface.TAG_GPS_ALTITUDE));
targetExif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF,sourceExif.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF));
targetExif.setAttribute(ExifInterface.TAG_GPS_DATESTAMP,sourceExif.getAttribute(ExifInterface.TAG_GPS_DATESTAMP));
targetExif.setAttribute(ExifInterface.TAG_GPS_LATITUDE,sourceExif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
targetExif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF,sourceExif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF));
targetExif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE,sourceExif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));
targetExif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF,sourceExif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF));
targetExif.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD,sourceExif.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD));
targetExif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH,sourceExif.getAttribute(ExifInterface.TAG_IMAGE_LENGTH));
targetExif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH,sourceExif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH));
targetExif.setAttribute(ExifInterface.TAG_ISO,sourceExif.getAttribute(ExifInterface.TAG_ISO));
targetExif.setAttribute(ExifInterface.TAG_MAKE,sourceExif.getAttribute(ExifInterface.TAG_MAKE));
targetExif.setAttribute(ExifInterface.TAG_MODEL,sourceExif.getAttribute(ExifInterface.TAG_MODEL));
targetExif.setAttribute(ExifInterface.TAG_ORIENTATION,sourceExif.getAttribute(ExifInterface.TAG_ORIENTATION));
targetExif.setAttribute(ExifInterface.TAG_WHITE_BALANCE,sourceExif.getAttribute(ExifInterface.TAG_WHITE_BALANCE));
targetExif.saveAttributes();
return true;
} catch (IOException e) {
Log.i("Alex","復制圖片的exif出現異常",e);
return false;
}
}
/**
* 根據圖像的大小得到合適的有損壓縮質量,因為此時傳入的bitmap大小已經比較合適了,靠近1000*1000,所以根據其內存大小進行質量壓縮
* @param size
* @return
*/
private static int getQuality(int size){
int mb=size>>20;//除以100萬,也就是m
int kb = size>>10;
Log.i("Alex","准備按照圖像大小計算壓縮質量,大小是"+kb+"KB,兆數是"+mb);
if(mb>70){
return 17;
}else if(mb>50){
return 20;
}else if(mb>40){
return 25;
}else if(mb>20){
return 40;
}else if(mb>10){
return 50;
}else if(mb>3){//目標壓縮大小 100k,這裡可根據實際情況來判斷
return 60;
}else if(mb>=2){
return 70;
}else if(kb > 1800) {
return 75;
}else if(kb > 1500){
return 80;
}else if(kb > 1000){
return 85;
}else if(kb>500){
return 90;
}else if(kb>100){
return 95;
}
else{
return 100;
}
}
/**
* 從assets文件夾中根據文件名得到一個Bitmap
* @param fileName
* @return
*/
public static Bitmap getDataFromAssets(Context context,String fileName,RequestCallBack callBack){
Log.i("Alex","准備從assets文件夾中讀取文件"+fileName);
try {
//可以直接使用context.getResources().getAssets().open(fileName);得到一個InputStream再用BufferedInputStream通過緩沖區獲得字符數組
AssetFileDescriptor descriptor = context.getResources().getAssets().openFd(fileName);//此處獲得文件描述之後可以得到FileInputStream,然後使用NIO得到Channel
long fileSize = descriptor.getLength();
Log.i("Alex","要讀取的文件的長度是"+fileSize);//注意這個地方如果文件大小太大,在decodeStream需要設置參數進行裁剪
Bitmap bitmap = BitmapFactory.decodeStream(context.getResources().getAssets().open(fileName));
// Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);//注意,AssetFileDescriptor只能用來獲取文件的大小,不能用來獲取inputStream,用FileDescriptor獲取的輸入流BitmapFactory.decodeStream不能識別
if(bitmap==null)Log.i("Alex","decode bitmap失敗");
return bitmap;
} catch (Exception e) {
Log.i("Alex","讀取文件出現異常",e);
e.printStackTrace();
}
return null;
}
/**
* 上傳一個文件到服務器
* @param uploadFile 文件指針
* @param apiUrl 服務器端口地址
* @param callBack 回調
*/
public static void uploadOnePhoto(File uploadFile,String apiUrl,RequestCallBack callBack){
if(uploadFile==null || !uploadFile.isFile() || uploadFile.length()<1024){
JLogUtils.i("Alex","傳來文件指針錯誤");
return;
}
JLogUtils.i("Alex","上傳文件地址是"+uploadFile.getAbsolutePath()+"大小是"+uploadFile.length()+"接口是"+apiUrl);
RequestParams params = new RequestParams();
params.addBodyParameter("up-image",uploadFile);//設置要上傳文件的本地路徑,第一個參數就是html中標簽中的name屬性,是與服務器傳輸文件字節流以外的文本信息的重要渠道,在servlet中這樣獲得fileitem.getFieldName();
HttpUtils http = new HttpUtils();
http.configCurrentHttpCacheExpiry(10000); //設置超時時間 10s
http.configRequestRetryCount(2);
http.send(HttpRequest.HttpMethod.POST, apiUrl, params, callBack);
}
/**
* 一個封裝好的業務函數,如果想要部分實現功能可以用單個壓縮和上傳函數
* 給出待上傳的文件指針,一次性全部壓縮並且上傳,此方法在子線程執行,在全部上傳完成後,執行回調函數
* @param context
* @param oriFiles
*/
public static void compressAndUploadPhotos(final Context context, final File[] oriFiles, final String uploadUrl, final UploadCallBack callBack){
if(oriFiles==null || oriFiles.length==0)return;
int length = oriFiles.length;
final File[] files = new File[length];//用於存放壓縮完後的緩存文件
final Boolean[] flags = new Boolean[length];//用觀察每個文件是否成功
final String[] returnJsons = new String[length];//用於接收php服務器的返回信息
new AlxAsynTask(){
@Override
protected Void doInBackground(Void... params) {
JLogUtils.i("Alex","現在開始壓縮,保留rxif信息,旋轉角度按照rxif確定");
for(int i=0;i1000){
JLogUtils.i("Alex","這個文件以前已經壓縮過了,准備跳過"+files[i].getAbsolutePath());
continue;//如果以前壓縮過這張圖片,那麼就跳過
}
int rotateDegree = AlxImageLoader.readPictureDegree(oriFiles[i].getAbsolutePath());
AlxBitmapUtils.compressImage(oriFiles[i],files[i],null,false,rotateDegree,true);//現在開始壓縮到文件
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
JLogUtils.i("Alex","所有照片壓縮完畢,准備上傳");
int i = 0;
for(final File f:files){
final int index = i++;//index用於標記這是第幾個文件
final long startTime = System.currentTimeMillis();
AlxBitmapUtils.uploadOnePhoto(f, uploadUrl, new RequestCallBack() {
@Override
public void onStart() {
super.onStart();
JLogUtils.i("Alex","第"+index+"個文件上傳開始"+ f.getName());
}
@Override
public void onSuccess(ResponseInfo responseInfo) {
JLogUtils.i("Alex","第"+index+"個文件上傳成功,耗時"+(System.currentTimeMillis()-startTime));
JLogUtils.i("Alex","響應是"+responseInfo.reasonPhrase+" "+responseInfo.result);//這裡的responseInfo.result是關鍵,也是顯示在浏覽器上的東西,一般是String,也有可能是音視頻流
flags[index] = true;
callBack.onItemCompleted(index,responseInfo.result);
if(responseInfo.result instanceof String)returnJsons[index] = (String)responseInfo.result;
if(checkAllSuccess(flags))callBack.onCompleted(flags,returnJsons);//檢查全部文件是否都已經結束,這裡要確保線程安全
}
@Override
public void onLoading(long total, long current, boolean isUploading) {
super.onLoading(total, current, isUploading);
int progress = (int)(current*100/total);
JLogUtils.i("Alex","第"+index+"個文件總大小"+total+"目前進度"+current+" 百分比 "+(short)progress+"%");
callBack.onLoading(index,(short)progress);//通知主函數進度
}
@Override
public void onFailure(HttpException error, String msg) {
JLogUtils.i("Alex","第"+index+"個文件上傳照片失敗"+msg+" 錯誤碼是"+error.getExceptionCode()+"錯誤信息"+error.getMessage()+error.getLocalizedMessage());
JLogUtils.i("Alex","上傳失敗",error.getCause());
flags[index] = false;
for(StackTraceElement e:error.getStackTrace()){
JLogUtils.i("Alex",e.toString());
}
callBack.onItemFailed(index);
if(checkAllSuccess(flags))callBack.onCompleted(flags,returnJsons);//檢查全部文件是否都已經結束
}
});
}
}
}.executeDependSDK();
}
/**
* 檢查所有文件是否全部上傳成功,包括有的失敗了,這個方法要確保線程安全
* @param flags
* @return
*/
public synchronized static boolean checkAllSuccess(Boolean flags[]){
for(Boolean b:flags){
if(b==null)return false;//這個方法要確保線程安全
}
return true;
}
public interface UploadCallBack{
void onCompleted(Boolean[] status,String[] results);
void onLoading(int index,short progress);
void onItemCompleted(int index,Object result);
void onItemFailed(int index);
}
/**
* 獲取系統總內存,返回單位為kb
* @return
*/
private long getPhoneTotalMemory() {
String str1 = "/proc/meminfo";// 系統內存信息文件
String str2;
String[] arrayOfString;
long initial_memory = 0;
try {
FileReader localFileReader = new FileReader(str1);
BufferedReader localBufferedReader = new BufferedReader(localFileReader, 8192);
str2 = localBufferedReader.readLine();// 讀取meminfo第一行,系統總內存大小
arrayOfString = str2.split("\\s+");
for (String num : arrayOfString) {
Log.i("Alex",str2+"內存是::::"+num + "\t");
}
initial_memory = Integer.valueOf(arrayOfString[1]).intValue();// 獲得系統總內存,單位是KB,乘以1024轉換為Byte
localBufferedReader.close();
} catch (IOException e) {
Log.i("Alex","獲取系統總內存出現異常",e);
} catch (Exception e){
Log.i("Alex","獲取系統總內存出現異常2",e);
}
return initial_memory;
}
/**
* 獲取系統當前可用內存
* @return
*/
private long getAvailMemory(Context context) {
// 獲取android當前可用內存大小
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
am.getMemoryInfo(mi);
return mi.availMem;// 將獲取的內存大小規格化
}
/**
* 向系統相冊插入一張圖片並且創建一個縮略圖供系統相冊顯示,返回值存放圖片的地址(如果成功放到相冊裡就返回相冊的地址,否則就是源文件的地址)
* 如果害怕源文件插入到相冊之後源文件還存在浪費空間並且重復顯示,就deleteSourceFile 傳true來刪除源文件
* Insert an image and create a thumbnail for it.
*
* @param contentResolver The content resolver to use
* @return The URL to the newly created image, or null if the image failed to be stored
* for any reason.
*/
public static String insertImage(Context context, ContentResolver contentResolver, File sourceImage,boolean deleteSourceFile) {
if(sourceImage==null || !sourceImage.isFile() || !sourceImage.exists())return null;
ContentValues values = new ContentValues();
// values.put(MediaStore.Images.Media.TITLE, title);
// values.put(MediaStore.Images.Media.DESCRIPTION, description);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
// values.put(MediaStore.Images.Media.DATE_ADDED, JTimeUtils.getCurrentTimeLong());
// values.put(MediaStore.Images.Media.DATE_MODIFIED, JTimeUtils.getCurrentTimeLong());
// values.put(MediaStore.Images.Media.DATE_TAKEN, JTimeUtils.getCurrentTimeLong());
// values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
// values.put(MediaStore.Images.Media.LATITUDE, QravedApplication.getPhoneConfiguration().getLocation().getLatitude());
// values.put(MediaStore.Images.Media.LONGITUDE, QravedApplication.getPhoneConfiguration().getLocation().getLongitude());
// values.put(MediaStore.Images.Media.BUCKET_DISPLAY_NAME, "6666");
Uri url = null;
String stringUrl = null; /* value to be returned */
try {
url = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);//用contentResolver注冊一個路徑,這裡的路徑大約是/storage/sdcard0/DCIM/Camera/1461321370065.jpg,文件名很有可能是title
//現在需要把文件拷貝到這個url所指向的位置
Log.i("Alex","contentResolver分配的圖片位置是"+url);
if (url != null) {
OutputStream imageOut = contentResolver.openOutputStream(url);//獲得一個輸出流,以便把圖片拷貝到系統目錄下
FileInputStream in = null;
int byteread; // 讀取的字節數
try {//把拍攝後保存的圖片復制到相冊文件夾裡去,練exif信息也會一起復制
in = new FileInputStream(sourceImage);
byte[] buffer = new byte[1024];
while ((byteread = in.read(buffer)) != -1) {
imageOut.write(buffer, 0, byteread);
}
} catch (Exception e){
Log.i("Alex","將圖片插入系統相冊異常",e);
} finally {
if(in!=null)in.close();
if (imageOut != null) imageOut.close();
}
long id = ContentUris.parseId(url);
Log.i("Alex","媒體id是"+id);
// Wait until MINI_KIND thumbnail is generated.
Bitmap miniThumb = MediaStore.Images.Thumbnails.getThumbnail(contentResolver, id, MediaStore.Images.Thumbnails.MINI_KIND, null);
// This is for backward compatibility.
Bitmap microThumb = StoreThumbnail(contentResolver, miniThumb, id, 50F, 50F, MediaStore.Images.Thumbnails.MICRO_KIND);
} else {
Log.i("Alex", "Failed to create thumbnail, removing original");
if (url != null) {
contentResolver.delete(url, null, null);
}
url = null;
}
} catch (Exception e) {
Log.i("Alex", "Failed to insert image", e);
if (url != null) {
contentResolver.delete(url, null, null);
url = null;
}
}
if (url != null) {
// can post image
String[] proj = {MediaStore.Images.Media.DATA};
Cursor cursor = context.getContentResolver().query(url,
proj, // Which columns to return
null, // WHERE clause; which rows to return (all rows)
null, // WHERE clause selection arguments (none)
null); // Order-by clause (ascending by name)
if (cursor != null) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
stringUrl = cursor.getString(column_index);
}
}
//對某些不更新相冊的應用程序強制刷新
Intent intent2 = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File newPhotoFile = new File(stringUrl);
Uri uri = Uri.fromFile(newPhotoFile);
if(stringUrl!=null && stringUrl.length()>0 && newPhotoFile.isFile() && newPhotoFile.exists() && newPhotoFile.length()>1000 && newPhotoFile.canRead()){//如果確實存入相冊了,就不需要原來的文件了,就可以刪掉
intent2.setData(uri);
context.sendBroadcast(intent2);
if(deleteSourceFile)sourceImage.delete();//防止圖片重復,刪掉相機生成的圖片
}else {
Log.i("Alex","插入相冊失敗");
return sourceImage.getAbsolutePath();
}
JLogUtils.i("Alex","發送廣播的url是"+stringUrl);
return stringUrl;
}
private static Bitmap StoreThumbnail(
ContentResolver cr,
Bitmap source,
long id,
float width, float height,
int kind) {
// create the matrix to scale it
Matrix matrix = new Matrix();
float scaleX = width / source.getWidth();
float scaleY = height / source.getHeight();
matrix.setScale(scaleX, scaleY);
Bitmap thumb = Bitmap.createBitmap(source, 0, 0,
source.getWidth(),
source.getHeight(), matrix,
true);
ContentValues values = new ContentValues(4);
values.put(MediaStore.Images.Thumbnails.KIND, kind);
values.put(MediaStore.Images.Thumbnails.IMAGE_ID, (int) id);
values.put(MediaStore.Images.Thumbnails.HEIGHT, thumb.getHeight());
values.put(MediaStore.Images.Thumbnails.WIDTH, thumb.getWidth());
Uri url = cr.insert(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, values);
try {
OutputStream thumbOut = cr.openOutputStream(url);
thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);
thumbOut.close();
return thumb;
} catch (FileNotFoundException ex) {
return null;
} catch (IOException ex) {
return null;
} catch (Exception ex) {
return null;
}
}
}
private HashMapcompletedFiles = new HashMap<>();//這個hashMap用來存儲已經上傳成功的文件,key是未壓縮的文件路徑,value是上傳成功後返回的imageUrl final boolean[] isFailed = new boolean[1];//如果有一個失敗,那麼就標記全部失敗 AlxBitmapUtils.compressAndUploadPhotos(this, oriFiles, ApplicationConfigurationEntity.HTTP_UPPHOTO_URL,new AlxBitmapUtils.UploadCallBack() { @Override public void onCompleted(Boolean[] status,String[] results) {//文件全部發送成功 if(isFailed[0])return;//如果之前有個文件上傳失敗了,那麼就不執行完成方法 if(!QravedApplication.getAppConfiguration().isLogin()){//如果沒有上傳 JViewUtils.showToast(UploadMenuActivity.this,null,"Please login first"); uploadFailed(); return; } String[] successUrls = completedFiles.values().toArray(new String[completedFiles.size()]);//從記錄所有成功map裡取出圖片的url registMenu(restaurantId,successUrls);//向服務器注冊新上傳的menu } @Override public void onLoading(int index,short progress) {//index是第幾個文件,progress是上傳進度的百分比 //此處是全部文件的進度,用於更新ui } @Override public void onItemCompleted(int index, Object result) {//沒個文件上傳成功之後,存到hashmap裡面 //某個文件成功了 LogUtils.i("Alex","php 返回的imgPath字段是"+result);// if(!(result instanceof String)){onItemFailed(index);}//如果php返回為null,那麼就算失敗 } @Override public void onItemFailed(int index) { //某個文件失敗了,此時立即終止上傳 if(isFailed[0])return;//只彈出一遍土司 isFailed[0] = true; LogUtils.i("Alex","第"+index+"個文件發送失敗"); } }); break; }
Google官方 詳解 Android 性能優化
為什麼關注性能對於一款APP,用戶首先關注的是 app的性能,而不是APP本身的屬性功能,用戶不關心你是否是搞社交,是否搞電商,是否是一款強大的美圖濾鏡app,用戶首先關
Android四大組件之BroadcastReceiver
廣播機制簡介Android中的廣播主要可以分為兩種類型,標准廣播和有序廣播。標准廣播(Normal broadcasts)是一種完全異步執行的廣播,在廣播發出之後,所有的
Android - Linkify 詳解
Linkify 詳解 Linkify是一個輔助類, 在TextView(包含派生類)中通過RegEx模式匹配創建超鏈接; Lin
學習Android從0開始之基礎篇(2)-AndroidMainfest.xml文件詳解
AndroidMainfest.xml文件詳解一、關於AndroidManifest.xmlAndroidManifest.xml 是每個android程序中必須的文件。
Android從零開搞系列:自定義View(6)ScrollTo+ScrollBy+Scroller+NestedScrolling機制(上)
本菜開源的一個自己寫的Demo,希望能給Androider們有所幫助,