1. 程式人生 > >Android從相簿中選取圖片上傳到阿里雲OSS

Android從相簿中選取圖片上傳到阿里雲OSS

    在開發APP軟體中,boss突然提出想在軟體中新增一個多張照片上傳的功能,作為菜鳥的我,琢磨了兩天,才弄出來,今天特地貼出來。本篇部落格主要介紹的是將本地圖片上傳到伺服器的方法技巧。主要技術點是:
一、運用第三方可以從相簿中選取多張圖片。
二、將圖片進行壓縮處理。
三、上傳到阿里雲OSS。

技術相對簡單,主要是將這些技術結合起來的例子並不多,而且阿里雲OSS的資料也不是很豐富,開發文件也是很簡略,因此趁此機會將我的思路共享出來。

PS:請先配置好應用許可權。另外,這是公司專案的一個功能,有些地方只好刪除,可能看著不是很完整,有時間我再自己做個Demo共享一下。

一、圖片選擇

android機型幾乎每個品牌都有自己的相簿,因此為了相容性,自定義一個相簿是比較好的辦法,而且這次開發要求選取多張圖片,使用自定義的開來也是最佳方案,當然為了開發的簡便性,我直接使用的第三方,如果需要自定義的,可以參考下鴻洋大神的部落格。
Android 超高仿微信圖片選擇器 圖片該這麼載入
不過目前,支援多圖選擇的第三方控制元件也是挺多的,比如MultiImageSelector、MultipleImagePick、PhotoPicker、GalleryPick等等,我大致看了下,使用方法是大同小異的。這次開發中,我們使用的是GalleryPick,基本使用方法可以看看github的介紹。

GalleryPick地址
這裡幾乎就沒什麼好介紹了,需要的注意的地方,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貼出。