异步生成缩略图

介绍

文件夹中有很多图片,需要一个图片的预览功能,所以需要在文件夹上显示最多前九张的九宫格缩略图图片。为什么需要使用异步,因为图片在保存生成缩略图时,需要读取服务器上传的图片,由于读取特别慢,每张图片平均要2秒,所以需要异步来帮助我们实现。

大体思路

通过建立线程池,把所有要生成的缩略图逻辑放入一个方法内,在Controller中调用service方法。方法连接线程池是通过注解@Async(“名字”)注入

实现结果:

调用保存数据接口,生成缩略图操作在线程中慢慢进行,结果直接返回200,方便我们继续进行别的任务
具体代码
需要准备生成图片缩略图的工具类
工具类介绍
拿到九张服务器图片作为参数,服务器是保存有固定的视频和文档类型的默认图标,方便我们对视频和文件类型处理,最后生成的缩略图保存到服务器并返回一张服务器图片路径。

package com.qizhi.itfin.common.util;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.imageio.ImageIO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/**
* 图片缩略图
*
*/
@Slf4j
@Component
public class ImageUtil {


    public static String combineImage(List<String> imageList, String url, String creator, String partnerId, String path,
        String floorImage, String fileImage, String videoImage) throws Exception {
        // 放入底板图
        String floor = floorImage;
        imageList.add(0, floor);
        String[] Wfiles = {".doc", ".docx", ".pdf", ".txt", ".swf", ".xlsx", ".ppt", "xls"};
        String[] Vfiles = {".avi", ".asf", ".asx", ".wmv", ".wmv9", ".flv", ".f4v", ".mkv", ".mov", ".m4v", ".mts",
            ".3gp", ".mp4", ".mpg", ".mpeg", ".dat", ".ogm", ".vob", ".rm", ".rmvb", ".ts", ".tp", ".ifo", ".nsv"};
        List<String> newUrl = new ArrayList<>();
        // 创建一个BufferedImage数据
        for (String string : imageList) {
            String name = string.substring(string.lastIndexOf(".")).toLowerCase();
            if (Arrays.asList(Wfiles).contains(name)) {
                // 文档
                string = fileImage;
                newUrl.add(string);
            } else if (Arrays.asList(Vfiles).contains(name)) {
                // 视频
                string = videoImage;
                newUrl.add(string);
            } else {
                newUrl.add(string);
            }
        }


        BufferedImage[] imageArray = new BufferedImage[newUrl.size()];
        List<String> localUrl = new ArrayList<>();
        for (String imgUrl : newUrl) {
            String newImgUrl = imgUrl.replace(url, path);
            localUrl.add(newImgUrl);
        }
        // 初始化
        for (int i = 0; i < localUrl.size(); i++) {
            System.out.println("第" + i + "个" + "------" + new Date().getSeconds());
            imageArray[i] = ImageIO.read(new File((String)localUrl.get(i)));
        }
        // 用第一张图片作为底图,在他上面继续合成其他图片
        Graphics2D graphics2d = imageArray[0].createGraphics();


        // 获取第一张图片的width,和height
        int width0 = imageArray[0].getWidth();
        int height0 = imageArray[0].getHeight();


        // 遍历,把剩下的其他图片都画在底图上(也就是从第二张图片开始)
        if (imageArray.length == 1) {
            graphics2d.drawImage(imageArray[0], 0, 0, width0, height0, null);
        } else if (imageArray.length == 2) {
            // 获取第一张图片的width,和height
            graphics2d.drawImage(imageArray[1], 0, 0, width0, height0, null);
        } else {
            for (int i = 1; i < imageArray.length; i++) {
                if (i <= 3) {
                    graphics2d.drawImage(imageArray[i], (i - 1) * 210 + i * 8, 8, 210, 210, null);
                } else if (i > 3 && i <= 6) {
                    graphics2d.drawImage(imageArray[i], (i - 4) * 210 + (i - 3) * 8, 226, 210, 210, null);
                } else if (i > 6 && i <= 9) {
                    graphics2d.drawImage(imageArray[i], (i - 7) * 210 + (i - 6) * 8, 442, 210, 210, null);
                } else {
                    System.out.println("最多显示9张");
                }
            }
        }


        // 生成md5
        String md5;
        md5 = MD5Encryption.getEncryption(System.currentTimeMillis() + "");
        // 生成新的存储路径
        String filePath = File.separator + partnerId + File.separator + creator + File.separator + md5.substring(0, 2)
            + File.separator + md5.substring(2, 4) + File.separator + md5.substring(4, 6) + File.separator
            + md5.substring(6, 8) + File.separator + md5.substring(8, 10) + File.separator + md5.substring(10, 12)
            + File.separator + md5 + ".png";
        // 输出画好的图片
        File file = new File(path + filePath);
        // 判断文件父目录是否存在
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        boolean result = ImageIO.write(imageArray[0], "png", file);
        // 释放图形上下文使用的系统资源
        graphics2d.dispose();


        // 生成图片的绝对引用地址
        String fileUrl = StringUtils.replace(filePath, "\\", "/");
        String visitUrl = url + fileUrl;
        log.info("保存缩略图返回结果" + result + "----本地url---" + file + "----访问路径---" + visitUrl);
        return visitUrl;
    }
}`

准备线程池类

package com.qizhi.itfin.thread;


import java.util.concurrent.Executor;


import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
/**
* 配置线程池
*
* @author xuewei.xia
*/
@Component
public class OrderThreadAsyncConfigurer implements AsyncConfigurer {
    @Value("${field.order.thread.corePoolSize}")
    private Integer corePoolSize;
    @Value("${field.order.thread.maxPoolSize}")
    private Integer maxPoolSize;
    @Value("${field.order.thread.queueCapacity}")
    private Integer queueCapacity;


    /**
     * 注入异步线程池
     */
    @Bean(name = "OrderAsyncExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        threadPool.setCorePoolSize(corePoolSize);
        // 设置最大线程数
        threadPool.setMaxPoolSize(maxPoolSize);
        // 线程池所使用的缓冲队列
        threadPool.setQueueCapacity(queueCapacity);
        // 等待任务在关机时完成--表明等待所有线程执行完
        threadPool.setWaitForTasksToCompleteOnShutdown(true);
        // 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
        threadPool.setAwaitTerminationSeconds(60);
        // 线程名称前缀
        threadPool.setThreadNamePrefix("OrderAsyncTask-");
        // 初始化线程
        threadPool.initialize();
        return threadPool;
    }
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }
}

里面配置参数如图
线性池配置
准备工作完成,看下controller层代码

```
 @SuppressWarnings("unchecked")
    @RequestMapping(value = "/updateImages")
    public R updateImgeMaterial(Long orderId,  @RequestParam("imagesMaterial") String data,
        @RequestParam("type") String type,  @RequestParam("otherType") String otherType, String  removeIdList) {
        PmsUser user = (PmsUser)ShiroUtils.getUserEntity();
        Map<String,
            Object> resultMap =  odrOrderMediaService.updateImgeMaterial(orderId, data, type,  otherType, removeIdList,
                url, String.valueOf(user.getUserId()),  String.valueOf(user.getPartnerId()), path, floorImage,  fileImage,
                videoImage);
        if ((Integer)resultMap.get("result") > 0) {
            // 异步调用生成缩略图
            Map<Integer, List<ImagesManVo>> mapImage =  (Map<Integer, List<ImagesManVo>>)resultMap.get("mapImage");
            Map<Integer, String> frontMap = (Map<Integer,  String>)resultMap.get("frontMap");
            Map<Integer, String> thumbnailMap = (Map<Integer,  String>)resultMap.get("thumbnailMap");
            List<Integer> typeIdList =  (List<Integer>)resultMap.get("typeIdList");
            List<OdrOrderMedia> customList =  (List<OdrOrderMedia>)resultMap.get("customList");
            JSONArray customType =  (JSONArray)resultMap.get("customType");
            if (mapImage != null && !mapImage.isEmpty()) {
                odrOrderMediaService.combineImage(mapImage,  frontMap, thumbnailMap, typeIdList, url,
                    String.valueOf(user.getUserId()),  String.valueOf(user.getPartnerId()), path, floorImage,  fileImage,
                    videoImage, orderId, customList, customType,  user.getBasePartnerId().intValue());
            }
            return R.ok();
        }
        return R.error();
    }

所有跟缩略图相关的数据要单独拉出来处理,如上代码是保存后从map中拿到所有文件夹中的图片数据,来另写生成缩略图方法执行,如上代码生成缩略图方法为combineImage方法。让我们看看这个方法:

   /**
     * 异步线程生成缩略图
     */
    @Async("OrderAsyncExecutor")
    @Transactional
    @Override
    public void combineImage(Map<Integer, List<ImagesManVo>> mapImage, Map<Integer, String> frontMap,
        Map<Integer, String> thumbnailMap, List<Integer> typeIdList, String url, String creator, String partnerId,
        String path, String floorImage, String fileImage, String videoImage, Long orderId,
        List<OdrOrderMedia> customList, JSONArray customType, Integer basePartnerId) {
        customList.clear();
        log.info("进入生成图片缩略图方法");
        // 保存缩略图
        for (Integer key : mapImage.keySet()) {
            // 比对保存前后 前九张拼接的id值是否一样
            log.info("frontMap对象-----" + frontMap.get(key));
            log.info("thumbnailMap对象-----" + thumbnailMap.get(key));
            if (thumbnailMap != null && !thumbnailMap.get(key).equals(frontMap.get(key))) {
                typeIdList.add(key);
                // 拿到同个类型的文件
                List<ImagesManVo> otherIdList = mapImage.get(key);
                log.info("otherIdList大小-----" + otherIdList.size());
                if (CollectionUtils.isNotEmpty(otherIdList)) {
                    for (ImagesManVo imagesManVo : otherIdList) {
                        int i = 0;
                        List<ImagesVo> list = imagesManVo.getImageList();
                        if (CollectionUtils.isNotEmpty(list)) {
                            List<String> urlList = new ArrayList<>();
                            for (ImagesVo imagesVo : list) {
                                if (i < 9) {
                                    urlList.add(imagesVo.getVisitUrl());
                                    i++;
                                }
                            }
                            try {
                                // 生成缩略图
                                String visitUrl = ImageUtil.combineImage(urlList, url, creator, partnerId, path,
                                    floorImage, fileImage, videoImage);
                                OdrOrderMedia odrOrderMedia = new OdrOrderMedia();
                                odrOrderMedia.setOrderId(orderId);
                                odrOrderMedia.setThumbnail(1);
                                odrOrderMedia.setTypeId(imagesManVo.getTypeId());
                                odrOrderMedia.setVisitUrl(visitUrl);
                                odrOrderMedia.setCreateTime(new Date());
                                customList.add(odrOrderMedia);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
        log.info("生成的所有的缩略图集合 :" + customList);
        // 抓取空集合所在的图片类型 添加到删除缩略图
        if (CollectionUtils.isNotEmpty(customType)) {
            for (int i = 0; i < customType.size(); i++) {
                typeIdList.add(customType.getInteger(i));
            }
        }
        // 删除缩略图
        if (CollectionUtils.isNotEmpty(typeIdList)) {
            QueryWrapper<OdrOrderMedia> queryWrapper = new QueryWrapper<OdrOrderMedia>();
            queryWrapper.eq("orderId", orderId);
            queryWrapper.eq("thumbnail", 1);
            queryWrapper.in("typeId", typeIdList);
            odrOrderMediaMapper.delete(queryWrapper);
        }
        // 保存所有缩略图
        if (CollectionUtils.isNotEmpty(customList)) {
            odrOrderMediaMapper.insertBathMedia(customList);
        }
    }

注解 @Async(“OrderAsyncExecutor”) 对应我们线程池注入的 @Bean(name = “OrderAsyncExecutor”)
这段代码逻辑:比对保存前后图片九张的图片id是否一致,一致不生成缩略图,不一致说明有发生改变,要重新生成新的缩略图。
第一次写博客,有补充的或好的想法欢迎给予建议。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在安卓平台上,可以使用ffmpeg库来生成网络视频的缩略图。以下是一个简单的代码示例: ```java private void generateThumbnail(String videoUrl) { FFmpegMediaMetadataRetriever retriever = new FFmpegMediaMetadataRetriever(); retriever.setDataSource(videoUrl); // 获取视频时长 String durationStr = retriever.extractMetadata(FFmpegMediaMetadataRetriever.METADATA_KEY_DURATION); long duration = Long.parseLong(durationStr); // 获取第一帧的缩略图 Bitmap bitmap = retriever.getFrameAtTime(0); // 缩放图片 int width = bitmap.getWidth(); int height = bitmap.getHeight(); float scale = Math.min(200f / width, 200f / height); Matrix matrix = new Matrix(); matrix.postScale(scale, scale); Bitmap thumb = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); // 保存缩略图 String thumbPath = getApplicationContext().getCacheDir().getPath() + "/thumb.jpg"; try (FileOutputStream out = new FileOutputStream(thumbPath)) { thumb.compress(Bitmap.CompressFormat.JPEG, 90, out); } catch (IOException e) { e.printStackTrace(); } retriever.release(); } ``` 其中,FFmpegMediaMetadataRetriever是一个基于ffmpeg库的多媒体元数据获取器,可以用来获取视频的各种信息,包括时长、帧率等等。通过调用getFrameAtTime()方法可以获取视频的第一帧缩略图。最后,通过Bitmap对象的createBitmap()方法和compress()方法可以对缩略图进行缩放和保存。 ### 回答2: 安卓平台上生成网络视频的缩略图可以通过使用Android系统自带的MediaMetadataRetriever类来实现。 首,我们需要在AndroidManifest.xml文件中添加读取网络和本地存储的权限: <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> 然后,在生成缩略图的Activity中,我们可以使用以下代码来获取网络视频的缩略图: ```java String videoUrl = "网络视频的URL"; // 替换为你要获取缩略图的网络视频地址 Bitmap thumbnail = null; // 创建 MediaMetadataRetriever 对象 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); try { // 设置要获取缩略图的视频地址 retriever.setDataSource(videoUrl, new HashMap<>()); // 获取第一帧的缩略图 thumbnail = retriever.getFrameAtTime(0); } catch (IllegalArgumentException e) { e.printStackTrace(); } finally { // 释放资源 retriever.release(); } // 在这里可以将缩略图显示在ImageView上或保存到本地 // imageView.setImageBitmap(thumbnail); // saveThumbnailToLocal(thumbnail); ``` 上述代码中,首创建了一个MediaMetadataRetriever对象,然后使用setDataSource方法设置要获取缩略图的视频地址。接着,调用getFrameAtTime方法即可获取第一帧的缩略图。 获取到缩略图后,你可以选择将其显示在ImageView上或保存到本地。可以使用imageView.setImageBitmap方法将缩略图显示在ImageView上,或者编写一个saveThumbnailToLocal方法将缩略图保存到本地存储中。 需要注意的是,上述代码需要在后台线程中执行,以避免阻塞主线程。可以使用AsyncTask或其他异步方式执行该代码。 总结起来,要在安卓平台上生成网络视频的缩略图,可使用Android系统的MediaMetadataRetriever类来实现,通过设置要获取缩略图的视频地址并调用getFrameAtTime方法来获取第一帧的缩略图。最后,将缩略图显示在ImageView上或保存到本地即可。 ### 回答3: 在安卓系统中,我们可以使用一些库和技术来生成网络视频的缩略图。 首,我们需要使用一个网络视频加载库,例如播放器库ExoPlayer或VideoView来加载网络视频。这些库提供了方便的接口来加载和处理网络视频。 一旦视频加载完成,我们可以使用Android的MediaMetadataRetriever类来提取视频的元数据。通过使用该类的getFrameAtTime()方法,我们可以获取视频的指定时间点的帧图像,并将其作为缩略图生成缩略图的过程可以通过以下步骤完成: 1. 初始化网络视频加载库,加载网络视频。 2. 在视频加载完成后,使用MediaMetadataRetriever类提取视频元数据。 3. 设定要提取缩略图的时间点,使用getFrameAtTime()方法获取该时间点的帧图像。 4. 将获取到的帧图像进行缩放和裁剪,生成所需的缩略图。 5. 可选择将缩略图保存到本地文件,以便之后使用。 需要注意的是,由于网络视频的大小和加载时间可能会有所不同,生成缩略图的效率和质量也可能会受到影响。为了更好的用户体验,可以考虑使用异步操作或后台线程来处理缩略图生成的过程。 总之,通过使用安卓的网络视频加载库和MediaMetadataRetriever类,我们可以方便地生成网络视频的缩略图并进行进一步处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值