Android從相簿中選取圖片上傳到阿里雲OSS
在開發APP軟體中,boss突然提出想在軟體中新增一個多張照片上傳的功能,作為菜鳥的我,琢磨了兩天,才弄出來,今天特地貼出來。本篇部落格主要介紹的是將本地圖片上傳到伺服器的方法技巧。主要技術點是:
一、運用第三方可以從相簿中選取多張圖片。
二、將圖片進行壓縮處理。
三、上傳到阿里雲OSS。
技術相對簡單,主要是將這些技術結合起來的例子並不多,而且阿里雲OSS的資料也不是很豐富,開發文件也是很簡略,因此趁此機會將我的思路共享出來。
PS:請先配置好應用許可權。另外,這是公司專案的一個功能,有些地方只好刪除,可能看著不是很完整,有時間我再自己做個Demo共享一下。
一、圖片選擇
android機型幾乎每個品牌都有自己的相簿,因此為了相容性,自定義一個相簿是比較好的辦法,而且這次開發要求選取多張圖片,使用自定義的開來也是最佳方案,當然為了開發的簡便性,我直接使用的第三方,如果需要自定義的,可以參考下鴻洋大神的部落格。
Android 超高仿微信圖片選擇器 圖片該這麼載入
不過目前,支援多圖選擇的第三方控制元件也是挺多的,比如MultiImageSelector、MultipleImagePick、PhotoPicker、GalleryPick等等,我大致看了下,使用方法是大同小異的。這次開發中,我們使用的是GalleryPick,基本使用方法可以看看github的介紹。
這裡幾乎就沒什麼好介紹了,需要的注意的地方,YangcyYe也介紹的很詳細。程式碼:
ImageConfig imageConfig
= new ImageConfig.Builder(
// GlideLoader 可用自己用的快取庫
new GlideLoader())
// 如果在 4.4 以上,則修改狀態列顏色 (預設黑色)
.steepToolBarColor (getResources().getColor(R.color.blue))
// 標題的背景顏色 (預設黑色)
.titleBgColor(getResources().getColor(R.color.blue))
// 提交按鈕字型的顏色 (預設白色)
.titleSubmitTextColor(getResources().getColor(R.color.white))
// 標題顏色 (預設白色)
.titleTextColor(getResources().getColor(R.color.white))
// 開啟多選 (預設為多選) (單選 為 singleSelect)
.mutiSelect()
.crop()
// 開啟拍照功能 (預設關閉)
.showCamera()
// 多選時的最大數量 (預設 9 張)
.mutiSelectMaxSize(6)
// 已選擇的圖片路徑
.pathList(path)
// 拍照後存放的圖片路徑(預設 /temp/picture)
.filePath("/ImageSelector/Pictures")
.requestCode(REQUEST_CODE)
.build();
ImageSelector.open(OrderDetailActivity.this, imageConfig); // 開啟圖片選擇器
圖片選擇後是在onActivityResult函式的回撥的,和自帶的相簿是一樣的。
二、圖片壓縮
我們公司的專案主要是上傳手機拍的照片,現在的手機畫素都是極高的,一不小心就是OOM,更重要的是在天朝流量是“奢侈品”,讓使用者一不小心就是十幾M的流量沒了,這也是不行的,所以上傳前必須對照片進行壓縮優化。
網上相關介紹也是頗多,我直接貼程式碼,各位看看,應該是挺好理解的。
public class BitmapUtils {
//壓縮圖片尺寸
public static Bitmap compressBySize(String pathName, int targetWidth,
int targetHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;// 不去真的解析圖片,只是獲取圖片的頭部資訊,包含寬高等;
Bitmap bitmap = BitmapFactory.decodeFile(pathName, options);
// 得到圖片的寬度、高度;
float imgWidth = options.outWidth;
float imgHeight = options.outHeight;
// 分別計算圖片寬度、高度與目標寬度、高度的比例;取大於等於該比例的最小整數;
int widthRatio = (int) Math.ceil(imgWidth / (float) targetWidth);
int heightRatio = (int) Math.ceil(imgHeight / (float) targetHeight);
options.inSampleSize = 1;
// 如果尺寸接近則不壓縮,否則進行比例壓縮
if (widthRatio > 1 || widthRatio > 1) {
if (widthRatio > heightRatio) {
options.inSampleSize = widthRatio;
} else {
options.inSampleSize = heightRatio;
}
}
//設定好縮放比例後,載入圖片進內容;
options.inJustDecodeBounds = false; // 這裡一定要設定false
bitmap = BitmapFactory.decodeFile(pathName, opts);
return bitmap;
}
}
一般而言,大小保持在720P左右即可,至少我是這麼設定的。壓縮的圖片,可以通過介面卡實現QQ控制元件圖片上傳時展現的效果。程式碼:
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3);
recycler.setLayoutManager(gridLayoutManager);
adapter = new Adapter(this, path);
recycler.setAdapter(adapter);
其介面卡的完整程式碼
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
private Context context;
private LayoutInflater mLayoutInflater;
private List<String> result;
private final static String TAG = "Adapter";
public Adapter(Context context, List<String> result) {
mLayoutInflater = LayoutInflater.from(context);
this.context = context;
this.result = result;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(mLayoutInflater.inflate(R.layout.image, null));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Glide.with(context)
.load(result.get(position))
.centerCrop()
.into(holder.image);
}
@Override
public int getItemCount() {
return result.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ImageView image;
public ViewHolder(View itemView) {
super(itemView);
image = (ImageView) itemView.findViewById(R.id.image);
}
}
}
圖片如此處理就可以了,但我們公司使用的是阿里雲Oss,因此,我把處理過的檔案又重新儲存到sd裡面去了。也把程式碼貼出來吧:
long time = System.currentTimeMillis();
String t = new SimpleDateFormat("yyyy_MM_dd").format(new Date(time));
String d = new SimpleDateFormat("HH_mm").format(new Date(time));
// 壓縮圖片儲存的目錄路徑
String filePath = MyApplication.getTruename() + "/" + t + "/" + d;
// 壓縮後圖片儲存的檔名
String fileName = "photo" + "(" + number + ")" + ".jpg";
File file = new File(getSDPath() + "/" + "myApp" + "/" + filePath);
File dir = new File(file, fileName);//將要儲存圖片的路徑,android推薦這種寫法,將目錄名和檔名分開,不然容易報錯。
try {
//壓縮圖片
Bitmap bitmap = BitmapUtils.compressBySize(srcPath, 1280, 768);
if (!file.exists()) {
file.mkdirs();
}
if (!dir.exists()) {
try {
dir.createNewFile();
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dir));
bitmap.compress(Bitmap.CompressFormat.JPEG, 30, bos);
bos.flush();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
getSdPath就是獲取SD的路徑,程式碼如下:
public static String getSDPath() {
File sdDir = null;
boolean sdCardExist = Environment.getExternalStorageState()
.equals(android.os.Environment.MEDIA_MOUNTED);//判斷sd卡是否存在
if (sdCardExist) {
sdDir = Environment.getExternalStorageDirectory();//獲取根目錄
} else {
getBaseContext().getCacheDir().getAbsolutePath(); // 獲取內建記憶體卡目錄
}
return sdDir.toString();
}
這樣基本就完成了圖片處理的操作,現在可以開始進行上傳操作了。
三、圖片上傳
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK && data != null) {
// 處理邏輯,requestcode是自己設定和傳過來的,RESULT_OK是常量
path = data.getStringArrayListExtra(ImageSelectorActivity.EXTRA_RESULT);
// 如果多張圖片,可以採取迴圈上傳
for(String srcPath, path){
Log.i("MyTag", srcPath) ; // 可以看到每張原始圖片的地址
// 圖片處理和儲存的邏輯
// 圖片上傳的邏輯,推薦不要放在迴圈中,多張圖片容易造成執行緒過多
};
initOss(); // 配置OSS
// 第一張圖片的上傳
OssUpload(path.get(0));
}
}
OSS的配置,建議只例項化一次,即不要放在迴圈體中。
// ACCESS_ID,ACCESS_KEY是在阿里雲申請的
OSSCredentialProvider credentialProvider = new OSSPlainTextAKSKCredentialProvider(ACCESS_ID, ACCESS_KEY);
ClientConfiguration conf = new ClientConfiguration();
conf.setConnectionTimeout(15 * 1000); // 連線超時,預設15秒
conf.setSocketTimeout(15 * 1000); // socket超時,預設15秒
conf.setMaxConcurrentRequest(8); // 最大併發請求數,預設5個
conf.setMaxErrorRetry(2); // 失敗後最大重試次數,預設2次
// oss為全域性變數,OSS_ENDPOINT是一個OSS區域地址
oss = new OSSClient(getApplicationContext(), OSS_ENDPOINT, credentialProvider, conf);
例項化後,就要將圖片上傳了,這個邏輯應該在onActivityResult這個方法中執行
public void ossUpload(String strPath){
// 判斷圖片是否全部上傳
// 如果已經是最後一張圖片上傳成功,則跳出
number++;
if (number == path.size()) {
// 結束的處理邏輯,並退出該方法
return;
}
// 指定資料型別,沒有指定會自動根據字尾名判斷
ObjectMetadata objectMeta = new ObjectMetadata();
objectMeta.setContentType("image/jpeg");
// 構造上傳請求
// 這裡的objectKey其實就是伺服器上的路徑,即目錄+檔名
//因為目錄命名邏輯涉及公司資訊,被我刪去,造成不知道這個objectKey不知為何物,如下是我們公司的大致命名邏輯
//String objectKey = keyPath + "/" + carArr[times] + ".jpg";
PutObjectRequest put = new PutObjectRequest(BUCKET_NAME, objectKey,dir.getPath());
put.setMetadata(objectMeta);
try {
PutObjectResult putObjectResult = oss.putObject(put);
} catch (ClientException e) {
e.printStackTrace();
} catch (ServiceException e) {
e.printStackTrace();
}
// 非同步上傳時可以設定進度回撥
put.setProgressCallback(new OSSProgressCallback<PutObjectRequest>() {
@Override
public void onProgress(PutObjectRequest request, long currentSize, long totalSize) {
// 在這裡可以實現進度條展現功能
Log.d("PutObject", "currentSize: " + currentSize + " totalSize: " + totalSize);
}
});
OSSAsyncTask task = oss.asyncPutObject(put, new OSSCompletedCallback<PutObjectRequest, PutObjectResult>() {
@Override
public void onSuccess(PutObjectRequest request, PutObjectResult result) {
Log.d("PutObject", "UploadSuccess");
Log.d("ETag", result.getETag());
Log.d("RequestId", result.getRequestId());
// 這裡進行遞迴單張圖片上傳,在外面判斷是否進行跳出
if (number <= path.size() - 1) {
upOss(path.get(number));
}
}
@Override
public void onFailure(PutObjectRequest request, ClientException clientExcepion, ServiceException serviceException) {
// 請求異常
if (clientExcepion != null) {
// 本地異常如網路異常等
clientExcepion.printStackTrace();
}
if (serviceException != null) {
// 服務異常
Log.e("ErrorCode", serviceException.getErrorCode());
Log.e("RequestId", serviceException.getRequestId());
Log.e("HostId", serviceException.getHostId());
Log.e("RawMessage", serviceException.getRawMessage());
}
ToastUtils.showShort(mContext, "圖片上傳失敗,請重新上傳");
return;
}
});
}
至此,整個多圖上傳的過程就完成了,作為菜鳥的我,目前想出來的就是這個笨方法,希望各位大神有更好的方法,不吝賜教!
2016-10-25 PS:寫文章時,只是想將個人想法拿出來探討,沒想到能有這麼人評論與瀏覽,如今看來,這篇部落格還存在許多問題,我計劃在本週更新本篇部落格,以及將demo貼出。