1. 程式人生 > >Android儲存多張圖片到本地

Android儲存多張圖片到本地

目錄介紹

  • 01.實際開發儲存圖片遇到的問題
  • 02.直接用http請求圖片並儲存本地
  • 03.用glide下載圖片儲存本地
  • 04.如何實現連續儲存多張圖片
  • 05.關於其他介紹

好訊息

  • 部落格筆記大彙總【16年3月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計N篇[近100萬字,陸續搬到網上],轉載請註明出處,謝謝!
  • 連結地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!

01.實際開發儲存圖片遇到的問題

  • 業務需求
    • 在素材list頁面的九宮格素材中,展示網路請求載入的圖片。如果使用者點選儲存按鈕,則儲存若干張圖片到本地。具體做法是,使用glide載入圖片,然後設定listener監聽,在圖片請求成功onResourceReady後,將圖片資源resource儲存到集合中。這個時候,如果點選儲存控制元件,則迴圈遍歷圖片資源集合儲存到本地資料夾。
  • 具體做法程式碼展示
    • 這個時候直接將請求網路的圖片轉化成bitmap,然後儲存到集合中。然後當點選儲存按鈕的時候,將會儲存該組集合中的多張圖片到本地資料夾中。
    //bitmap圖片集合
    private ArrayList<Bitmap> bitmapArrayList = new ArrayList<>();
    
    
    RequestOptions requestOptions = new RequestOptions()
            .transform(new GlideRoundTransform(mContext, radius, cornerType));
    GlideApp.with(mIvImg.getContext())
            .asBitmap()
            .load(url)
            .listener(new RequestListener<Bitmap>() {
                @Override
                public boolean onLoadFailed(@Nullable GlideException e, Object model,
                                            Target<Bitmap> target, boolean isFirstResource) {
                    return true;
                }
    
                @Override
                public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target,
                                               DataSource dataSource, boolean isFirstResource) {
                    bitmapArrayList.add(resource);
                    return false;
                }
            })
            .apply(requestOptions)
            .placeholder(ImageUtils.getDefaultImage())
            .into(mIvImg);
    
    
    
    //迴圈遍歷圖片資源集合,然後開始儲存圖片到本地資料夾
    mBitmap = bitmapArrayList.get(i);
    savePath = FileSaveUtils.getLocalImgSavePath();
    FileOutputStream fos = null;
    try {
        File filePic = new File(savePath);
        if (!filePic.exists()) {
            filePic.getParentFile().mkdirs();
            filePic.createNewFile();
        }
        fos = new FileOutputStream(filePic);
        // 100 圖片品質為滿
        mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    } finally {
        if (fos != null) {
            try {
                fos.flush();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //重新整理相簿
        if (isScanner) {
            scanner(context, savePath);
        }
    }
    
  • 遇到的問題
    • 儲存圖片到本地後,發現圖片並不是原始的圖片,而是展現在view控制元件上被裁切的圖片,也就是ImageView的尺寸大小圖片。
  • 為什麼會遇到這種問題
    • 如果你傳遞一個ImageView作為.into()的引數,Glide會使用ImageView的大小來限制圖片的大小。例如如果要載入的圖片是1000x1000畫素,但是ImageView的尺寸只有250x250畫素,Glide會降低圖片到小尺寸,以節省處理時間和記憶體。
    • 在設定into控制元件後,也就是說,在onResourceReady方法中返回的圖片資源resource,實質上不是你載入的原圖片,而是ImageView設定尺寸大小的圖片。所以儲存之後,你會發現圖片變小了。
  • 那麼如何解決問題呢?
    • 第一種做法:九宮格圖片控制元件展示的時候會載入網路資源,然後載入圖片成功後,則將資源儲存到集合中,點選儲存則迴圈儲存集合中的資源。這種做法只會請求一個網路。由於開始
    • 第二種做法:九宮格圖片控制元件展示的時候會載入網路資源,點選儲存九宮格圖片的時候,則依次迴圈請求網路圖片資源然後儲存圖片到本地,這種做法會請求兩次網路。

02.直接用http請求圖片並儲存本地

  • http請求圖片
    /**
     * 請求網路圖片
     * @param url                       url
     * @return                          將url圖片轉化成bitmap物件
     */
    private static long time = 0;
    public static InputStream HttpImage(String url) {
        long l1 = System.currentTimeMillis();
        URL myFileUrl = null;
        Bitmap bitmap = null;
        HttpURLConnection conn = null;
        InputStream is = null;
        try {
            myFileUrl = new URL(url);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        try {
            conn = (HttpURLConnection) myFileUrl.openConnection();
            conn.setConnectTimeout(10000);
            conn.setReadTimeout(5000);
            conn.setDoInput(true);
            conn.connect();
            is = conn.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                    conn.disconnect();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            long l2 = System.currentTimeMillis();
            time = (l2-l1) + time;
            LogUtils.e("毫秒值"+time);
            //儲存
        }
        return is;
    }
    
  • 儲存到本地
    InputStream inputStream = HttpImage(
            "https://img1.haowmc.com/hwmc/material/2019061079934131.jpg");
    String localImgSavePath = FileSaveUtils.getLocalImgSavePath();
    File imageFile = new File(localImgSavePath);
    if (!imageFile.exists()) {
        imageFile.getParentFile().mkdirs();
        try {
            imageFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    FileOutputStream fos = null;
    BufferedInputStream bis = null;
    try {
        fos = new FileOutputStream(imageFile);
        bis = new BufferedInputStream(inputStream);
        byte[] buffer = new byte[1024];
        int len;
        while ((len = bis.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (bis != null) {
                bis.close();
            }
            if (fos != null) {
                fos.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

03.用glide下載圖片儲存本地

  • glide下載圖片
    File file = Glide.with(ReflexActivity.this)
            .load(url.get(0))
            .downloadOnly(500, 500)
            .get();
    
  • 儲存到本地
    String localImgSavePath = FileSaveUtils.getLocalImgSavePath();
    File imageFile = new File(localImgSavePath);
    if (!imageFile.exists()) {
        imageFile.getParentFile().mkdirs();
        imageFile.createNewFile();
    }
    copy(file,imageFile);
    
    /**
     * 複製檔案
     *
     * @param source 輸入檔案
     * @param target 輸出檔案
     */
    public static void copy(File source, File target) {
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            fileInputStream = new FileInputStream(source);
            fileOutputStream = new FileOutputStream(target);
            byte[] buffer = new byte[1024];
            while (fileInputStream.read(buffer) > 0) {
                fileOutputStream.write(buffer);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

04.如何實現連續儲存多張圖片

  • 思路:迴圈子執行緒
    • 可行(不推薦), 如果我要下載9個圖片,將子執行緒加入for迴圈內,並最終呈現。
    • 有嚴重缺陷,執行緒延時,圖片順序不能做保證。如果是執行緒套執行緒的話,第一個子執行緒結束了,巢狀在該子執行緒f的or迴圈內的子執行緒還沒結束,從而主執行緒獲取不到子執行緒裡獲取的圖片。
    • 還有就是如何判斷所有執行緒執行完畢,比如所有圖片下載完成後,吐司下載完成。
  • 不建議的方案
    • 建立一個執行緒池來管理執行緒,關於執行緒池封裝庫,可以看執行緒池簡單封裝
    • 這種方案不知道所有執行緒中請求圖片是否全部完成,且不能保證順序。
    ArrayList<String> images = new ArrayList<>();
    for (String image : images){
        //使用該執行緒池,及時run方法中執行異常也不會崩潰
        PoolThread executor = BaseApplication.getApplication().getExecutor();
        executor.setName("getImage");
        executor.execute(new Runnable() {
            @Override
            public void run() {
                //請求網路圖片並儲存到本地操作
            }
        });
    }
    
  • 推薦解決方案
    ArrayList<String> images = new ArrayList<>();
    ApiService apiService = RetrofitService.getInstance().getApiService();
    //注意:此處是儲存多張圖片,可以採用非同步執行緒
    ArrayList<Observable<Boolean>> observables = new ArrayList<>();
    final AtomicInteger count = new AtomicInteger();
    for (String image : images){
        observables.add(apiService.downloadImage(image)
                .subscribeOn(Schedulers.io())
                .map(new Function<ResponseBody, Boolean>() {
                    @Override
                    public Boolean apply(ResponseBody responseBody) throws Exception {
                        saveIo(responseBody.byteStream());
                        return true;
                    }
                }));
    }
    // observable的merge 將所有的observable合成一個Observable,所有的observable同時發射資料
    Disposable subscribe = Observable.merge(observables).observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Consumer<Boolean>() {
                @Override
                public void accept(Boolean b) throws Exception {
                    if (b) {
                        count.addAndGet(1);
                        Log.e("yc", "download is succcess");
    
                    }
                }
            }, new Consumer<Throwable>() {
                @Override
                public void accept(Throwable throwable) throws Exception {
                    Log.e("yc", "download error");
                }
            }, new Action() {
                @Override
                public void run() throws Exception {
                    Log.e("yc", "download complete");
                    // 下載成功的數量 和 圖片集合的數量一致,說明全部下載成功了
                    if (images.size() == count.get()) {
                        ToastUtils.showRoundRectToast("儲存成功");
                    } else {
                        if (count.get() == 0) {
                            ToastUtils.showRoundRectToast("儲存失敗");
                        } else {
                            ToastUtils.showRoundRectToast("因網路問題 儲存成功" + count + ",儲存失敗" + (images.size() - count.get()));
                        }
                    }
                }
            }, new Consumer<Disposable>() {
                @Override
                public void accept(Disposable disposable) throws Exception {
                    Log.e("yc","disposable");
                }
            });
    
    
    
    private void saveIo(InputStream inputStream){
        String localImgSavePath = FileSaveUtils.getLocalImgSavePath();
        File imageFile = new File(localImgSavePath);
        if (!imageFile.exists()) {
            imageFile.getParentFile().mkdirs();
            try {
                imageFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        FileOutputStream fos = null;
        BufferedInputStream bis = null;
        try {
            fos = new FileOutputStream(imageFile);
            bis = new BufferedInputStream(inputStream);
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (bis != null) {
                    bis.close();
                }
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            //重新整理相簿程式碼省略……
        }
    }
    

其他介紹

01.關於部落格彙總連結

02.關於我的部落格

專案案例:https://github.com/yangchong2