Android保存多张图片到本地

目录介绍

  • 01.实际开发保存图片遇到的问题
  • 02.直接用http请求图片并保存本地
  • 03.用glide下载图片保存本地
  • 04.如何实现连续保存多张图片
  • 05.关于其他介绍

想换个工作,渴望同行内推我

  • 个人信息
    • 姓名:杨充【26岁】
    • 邮箱:yangchong211@163.com
    • 微信:13667225184
    • GitHub:https://github.com/yangchong211
    • 博客汇总:https://github.com/yangchong211/YCBlogs
    • 干活集中营:Android端技术博客和开源项目审核员
    • 目前工作情况:在职状态
    • 技术项目和博客:GitHub项目7k以上star,follower1.1k以上,发表博客100多篇。
    • 热爱技术:开源项目和博客多次被鸿洋,郭霖,Android技术周刊,干活集中营等等推荐。
    • 学历:武汉软件工程职业学院,大专学历
    • 工作年限:3年多
    • 工作地点:北京
  • 关于近期投递简历一点感想
    • 从进入Android这个行业以来,前两次几乎都是朋友内推,面试机会相对容易,都是一个App一个人做或者两个人做,用户相对来说并不多。这次想着离职,主要是想进入一个较大的平台,大概可以理解为Android端有个至少四五人,可以进行技术交流,渴望自己能够在技术上突破,这就像自己平时独自跑步,和跟着一群跑马拉松的人跑步,那种紧张感肯定是不一样的。
    • 近段时间,尝试着向一些较大的公司投递简历,大概在拉钩上投了15个左右(不喜欢海投),发现绝大多数简历到不了技术那里,就被人事说学历不够,经验不够,工作不匹配等情况回绝。不过也可以理解,看简历无非就是学历和经验,貌似自己的履历是差了一点。
    • 这大概是第一次在网上发一个主动希望同行内推的介绍,如果你的公司有Android方面的招聘,能否内推一下我这个小人物,感谢。

好消息

  • 博客笔记大汇总【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/yangchong211/YCVideoPlayer

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • 一、缓存介绍: (一)、Android中缓存的必要性: 智能手机的缓存管理应用非常的普遍和需要,是提高用户体验的有...
    温暖的外星阅读 875评论 0 12
  • 一、基础知识:1、JVM、JRE和JDK的区别:JVM(Java Virtual Machine):java虚拟机...
    杀小贼阅读 2,360评论 0 4
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,335评论 0 17
  • 一. Java基础部分.................................................
    wy_sure阅读 3,780评论 0 11
  • 唐代元稹的《会真记》将崔莺莺无情的抛弃在追求功名的道路上,好似祸水、好似累赘,好似爱情就是张生的迷魂药、就是进仕的...
    梓相阅读 189评论 0 0