Android获取本地视频文件的截图

作者: 程序亦非猿 | 来源:发表于2015-06-17 13:44 被阅读10417次
    插图.png

    上次在异步之AsyncTask(一)中我们讲了AsyncTask的基础,这次来再结合新的功能来巩固一下知识点.

    阅读本文你需要掌握AsyncTask的基本用法,如果不懂AsyncTask,推荐阅读异步之AsyncTask(一).

    通过阅读本文你将能够学到:

    1. 如何创建本地视频缩略图
    2. 如何AsyncTask使用自带线程池
    3. 如何使用回调
    4. 如何防止ListView加载多张图片造成位置混乱
    5. 如何使用内存/磁盘缓存

    由于功能需求要获取本地视频文件的截图,用于显示,而我们所用的图片加载工具为ImageLoader,很不巧,UIL不支持该需求,所以需要我们自己写了.
    So,动手开始写吧:

    1. 获取本地视频截图

    在尝试过许多方法之后,我最终使用了ThumbnailUtils这个类,使用ThumbnailUtils来获取视频的缩略图比较简单,它有个方法可以用来创建视频的缩略图:

    /**
    * Create a video thumbnail for a video. May return null if the video is
    * corrupt or the format is not supported.
    *
    * @param filePath the path of video file   文件路径
    * @param kind could be MINI_KIND or MICRO_KIND   种类,该参数决定了获取到的图片的大小 MINI_KIND:512 x 384 ,MICRO_KIND:96 x 96
    */
    public static Bitmap createVideoThumbnail(String filePath, int kind) 
    

    有了这个方法就好办了,我们封装一个方法用于获取:

    
    /**
         * @param videoPath 视频路径
         * @param width       图片宽度
         * @param height      图片高度
         * @param kind      eg:MediaStore.Video.Thumbnails.MICRO_KIND   MINI_KIND: 512 x 384,MICRO_KIND: 96 x 96
         * @return
         */
    private Bitmap getVideoThumbnail(String videoPath, int width, int height,int kind) {
            // 获取视频的缩略图
            Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(videoPath, kind);
           //extractThumbnail 方法二次处理,以指定的大小提取居中的图片,获取最终我们想要的图片
            bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
            return bitmap;
        }
    

    2.自定义VideoThumbnailLoader

    方法我们写完了,但是实际运用时候,发现非常的卡,因为创建bitmap耗时非常严重,达到上百毫秒,按16毫秒1帧算,加载一个缩略图就要卡上好几帧!!卡爆了!!
    相信机智的你早已经想到,下一步我们需要把创建缩略图的处理放在异步线程中去.

    另外我们需要考虑一个问题,我们不停地去加载,会发现,同一个视频我们加载了好几次缩略图,这个时候怎么办?
    这个时候就需要引入缓存了,使用过UIL等图片加载工具的同学应该知道了一级缓存(内存缓存),二级缓存(文件/磁盘缓存)这些概念.

    刚好,我们项目中使用了UIL,所以为了进一步提高加载效率,我顺带加了缓存.写了一个单例VideoThumbnailLoader类,来负责加载.

    那么问题又来了,我用的单例,那么多图片在加载,我怎么通知界面我加载完成了呢?
    这个时候回调就派上大用场了,在VideoThumbnailLoader中我增加了一个回调,通过回调方式告诉外部,我加载完成了,你可以显示了:

    //自己定义一个回调,通知外部图片加载完毕
    public interface ThumbnailListener{
        void onThumbnailLoadCompleted(String url,ImageView iv,Bitmap bitmap);
    }
    

    好了,思考完毕,准备完毕,上代码!~
    最终代码如下:

    public class VideoThumbnailLoader {
    
        private static final String TAG = "VideoThumbnailLoader";
        private MemoryCache mMCache;//一级缓存,内存缓存
    
        private static VideoThumbnailLoader ins = new VideoThumbnailLoader();
    
        public static VideoThumbnailLoader getIns(){
            return ins;
        }
    
        private VideoThumbnailLoader() {
            mMCache = ImageLoader.getInstance().getMemoryCache();
        }
    
        public void display(TLLiveEntity mEntity,String url,ImageView iv,int width,int height,ThumbnailListener thumbnailListener){
    
            new ThumbnailLoadTask(mEntity,url,iv,width,height,thumbnailListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);//使用AsyncTask自带的线程池
    
        }
    
    
        private class ThumbnailLoadTask extends AsyncTask<Void, Void, Bitmap> {
    
            private String url;
            private ImageView iv;
            private ThumbnailListener thumbnailListener;
            private int width;
            private int height;
            public ThumbnailLoadTask(String url,ImageView iv,int width,int height,ThumbnailListener thumbnailListener){
                this.url = url;
                this.iv = iv;
                this.width = width;
                this.height = height;
                this.thumbnailListener = thumbnailListener;
            }
            
            @Override
            protected Bitmap doInBackground(Void... params) {
                /**
                 * 注意,由于我们使用了缓存,所以在加载缩略图之前,我们需要去缓存里读取,如果缓存里有,我们则直接获取,如果没有,则去加载.并且加载完成之后记得放入缓存.
                 */
                Bitmap bitmap = null;
                if (!TextUtils.isEmpty(url)) {
                    String key = getMemoryKey(url);
                    bitmap = mMCache.get(key);//先去内存缓存取
    
                    if (bitmap == null||bitmap.isRecycled()) {
    
                        File file = TLFileUtils.getExternalFile(path, "xxx.png");//创建文件,这里由于项目原因,我就随便写一个,实际情况不是这样,大家留意一下
                        if (null != file && file.exists()) {//去磁盘缓存取
                            bitmap = BitmapFactory.decodeFile(file.getPath());
                            if (null==bitmap) {
                                bitmap = getVideoThumbnail(url, width, height, MediaStore.Video.Thumbnails.MICRO_KIND);
                                //将图片保存到磁盘文件,作为缓存
                                BitmapUtils.saveBitmapToFile(file, bitmap,Bitmap.CompressFormat.PNG);
                            }
                        } else {
                            bitmap = getVideoThumbnail(url, width, height, MediaStore.Video.Thumbnails.MICRO_KIND);
    
                            if (null==bitmap) {
                                bitmap = getVideoThumbnail(url, width, height, MediaStore.Video.Thumbnails.MICRO_KIND);
                                //将图片保存到磁盘文件,作为缓存
                                BitmapUtils.saveBitmapToFile(file, bitmap,Bitmap.CompressFormat.PNG);
                            }
    
                        }
    
                        if (null != bitmap) {
                            mMCache.put(key, bitmap);//存入内存缓存
                        }
    
                    }
                }
                return bitmap;
            }
    
    
    
            @Override
            protected void onPostExecute(Bitmap bitmap) {
                super.onPostExecute(bitmap);
                thumbnailListener.onThumbnailLoadCompleted(url, iv, bitmap);//回调
            }
        }
    
        /**
         * @param videoPath 视频路径
         * @param width
         * @param height
         * @param kind      eg:MediaStore.Video.Thumbnails.MICRO_KIND   MINI_KIND: 512 x 384,MICRO_KIND: 96 x 96
         * @return
         */
        private Bitmap getVideoThumbnail(String videoPath, int width, int height,
                                         int kind) {
            // 获取视频的缩略图
            Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(videoPath, kind);
            bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
            return bitmap;
        }
    
        /**
         *  imageloader 的内存缓存的 key 以_ 结尾  截取key比较的时候如果没有加_ 会报错崩溃,所以自己自定义
         * @param filePath 文件地址
         * @return
         */
        private  String getMemoryKey(String filePath) {
    
            String key ;
            int index = filePath.lastIndexOf("/");
            key = filePath.substring(index + 1, filePath.length())+"_";
            return key;
        }
        
        //自己定义一个回调,通知外部图片加载完毕
        public interface ThumbnailListener{
            void onThumbnailLoadCompleted(String url,ImageView iv,Bitmap bitmap);
        }
    }
    
    

    在需要加载的地方使用:

    mHolder.ivBg.setTag(imgUrl);  //这里注意,我们给imageview设置了一个Tag,后面自有妙用
    VideoThumbnailLoader.getIns().display(entity,url, mHolder.ivBg,mImgWidth,mImgHeight, new VideoThumbnailLoader.ThumbnailListener() {
                @Override
                public void onThumbnailLoadCompleted(String url, ImageView iv, Bitmap bitmap) {
                    //通过判断imageview的tag,和我们加载的图片的url是否是同一个来判断是否显示,这样可以避免滑动造成的位置错乱等问题.
                    String tag = (String) iv.getTag();
                    if (null != bitmap&&null!=tag&&tag.equals(url)) {
                        iv.setImageBitmap(bitmap);
                    }else{
                        iv.setImageResource(R.mipmap.defaultvideo);
                    }
                }
            });
    

    至此功能已经实现,并且使用了线程池,缓存,效率比一开始提高不少,不过还是有地方可以优化,至于优化,后面有机会再写吧~

    好了,本文结束,如果有疑问,欢迎提问,如果喜欢,欢迎点赞,欢迎关注我,关注我的专题~~

    谢谢,下次见!

    与我联系,交个朋友

    我的新浪微博

    我的Github

    打个广告:新建了一个qq群用于简书用户交流,进群请报简书地址:


    qq群

    相关文章

      网友评论

      • e6a56e3205c8:gofair是现在最好用的小语种短视频营销方法。
      • cf2e29eff2d6:非常好,修改了下整合进了自己的项目,思路很好,多谢指导
        程序亦非猿:@我该换个名了 不客气
      • 08_carmelo:视频缩略图在sd卡已经有了 不需要自己创建
        Cursor cursor = getContentResolver().query(MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI
        , null
        , MediaStore.Video.Thumbnails.VIDEO_ID + "=?"
        , new String[]{videoId+""}
        , null);
      • b84f87723563:楼主用的xutils保存的图片吗?xutils不是有很多兼容问题吗
        程序亦非猿:@duanjiahao 不要用 xutils 我没用
      • 码读先生:可以使用picasso这个库结合来使用,就不需要UIL了。
        程序亦非猿: @aca1b56751ab 谢谢提醒
      • 14fb544c0ed5:楼主,代码呢
        程序亦非猿:@布衣_行者 不好意思 代码我已经删掉了....
      • 程序亦非猿:不好意思 一直没看到- - 代码我已经删掉了....
      • 李新阳:@程序亦非猿 想运行看看
      • 程序亦非猿: @李新阳 那个代码几乎是所有的了 因为是在项目里的 拿不出来呢 不好意思 有哪里看不懂吗?
      • 李新阳:源码留下
        程序亦非猿:@盛国强 ?
        李新阳:@盛国强 exo me?你是
        盛国强:咦,阳哥,这里看见你了
      • 程序亦非猿:如果有更好的方式,请赐教
        程序猿007:@程序亦非猿 你的两个工具类TLFileUtils,BitmapUtils也顺便贴下呗,谢谢了!需要用到的,谢谢你!

      本文标题:Android获取本地视频文件的截图

      本文链接:https://www.haomeiwen.com/subject/ocdsqttx.html