okdownload下载mp4视频文件

因为使用aria下载后有些视频不能正常播放.找了很多原因也没找出来.后来可能想是视频加密解密的问题.改了加解密逻辑之后问题还是存在,aria的源码也不想分析,想着可能是断点下载的时候丢了几个字节.就想着换个下载库会不会好点,就看了okdownload这个开源库,有不少人在使用.是把项目建了个分支,下载代码改成okdownload了.结果证明不是aria下载框架的问题.是播放器的缓存没有及时清理导致的.这个问题其实也可以证明:卸载app后重新下载就可以正常播放.

aria这个下载库相对还说使用还是比较简单好用的.okdownload这个库还是需要仔细研究一番才能知道怎么用,状态怎么控制.得花个2-3天的时间去消化代码.网上很多都是官方的文档照着搬过来,自己不摸索还是一头雾水.本篇只说明怎么使用,不在介绍api.

aria地址:Introduction · Aria 使用指南

okdownload的文档这个介绍还是比较全的,有源码分析:OKDownload 下载框架 断点续传 MD - 白乾涛 - 博客园          

项目代码中有用到litepal用于记录下载记录和文件加密的地方,不做介绍.owdownload本身存储的记录是不能作为下载记录的.因为他存储的是下载时的状态数据.下载完成之后就自动清除了. 项目中尽量避免使用okdownload的下载id作为查询条件. 多用户的情况可以使用下载url和一些其他的参数作为查询条件.

 1.引用

    //    // core
    implementation "com.liulishuo.okdownload:okdownload:1.0.7"
    // provide sqlite to store breakpoints
    implementation "com.liulishuo.okdownload:sqlite:1.0.7"
    // provide okhttp to connect to backend
    implementation "com.liulishuo.okdownload:okhttp:1.0.7"

如果下载不下来需要指定仓库:

repositories {
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}

2.初始化参数设置,按需要设置

void initOkDownload() {
        OkDownload.Builder builder = new OkDownload.Builder(this)
                .downloadStore(Util.createDefaultDatabase(this))// //断点信息存储的位置,默认是SQLite数据库
                .callbackDispatcher(new CallbackDispatcher() {
                })监听回调分发器,默认在主线程回调
                .downloadDispatcher(new DownloadDispatcher() {
                })下载管理机制,最大下载任务数、同步异步执行下载任务的处理
                .connectionFactory(Util.createDefaultConnectionFactory())//选择网络请求框架,默认是OkHttp
                .outputStreamFactory(new DownloadUriOutputStream.Factory())// //构建文件输出流DownloadOutputStream,是否支持随机位置写入
                .downloadStrategy(new DownloadStrategy())//下载策略,文件分为几个线程下载
                .processFileStrategy(new ProcessFileStrategy())//多文件写文件的方式,默认是根据每个线程写文件的不同位置,支持同时写入
                .monitor(new DownloadMonitor() {
                    //开始任务
                    @Override
                    public void taskStart(DownloadTask task) {
                        LogUtil.i("taskStart");
                    }

                    //断点下载任务
                    @Override
                    public void taskDownloadFromBreakpoint(@NonNull DownloadTask task, @NonNull BreakpointInfo info) {
                        LogUtil.i("断点任务下载taskDownloadFromBreakpoint:" + task.getFilename());
                    }

                    //新任务
                    @Override
                    public void taskDownloadFromBeginning(@NonNull DownloadTask task, @NonNull BreakpointInfo info,
                                                          @Nullable ResumeFailedCause cause) {
                        LogUtil.i("新建任务下载taskDownloadFromBeginning:" + task.getFilename());
                    }

                    //任务结束
                    @Override
                    public void taskEnd(DownloadTask task, EndCause cause, @Nullable Exception realCause) {
                        LogUtil.i("下载完成taskEnd");
                        // 此处可以实现自己的逻辑,比如修改下载记录的任务状态,对下载文件进行加密

                    }
                });下载状态监听
        OkDownload.setSingletonInstance(builder.build());
//        DownloadDispatcher.setMaxParallelRunningCount(3);
        //设置监视器
//        OkDownload.with().setMonitor(new DownloadMonitor() {});
        //最大并行下载数
//        DownloadDispatcher.setMaxParallelRunningCount(3);
        //
//        RemitStoreOnSQLite.setRemitToDBDelayMillis(3000);
        //取消全部任务
//        OkDownload.with().downloadDispatcher().cancelAll();
        //移除某个任务
//        OkDownload.with().breakpointStore().remove(taskId);
    }

上面的一般需求都能满足.

3.创建下载任务

 /**
     * 创建下载任务实例
     *
     * @param url
     * @param parentPath
     * @param fileName
     * @return
     */
    public static DownloadTask createTasK(String url, String parentPath, String fileName) {
        return new DownloadTask.Builder(url, parentPath, fileName)
                .setFilenameFromResponse(false)//是否使用 response header or url path 作为文件名,此时会忽略指定的文件名,默认false
                .setPassIfAlreadyCompleted(true)//如果文件已经下载完成,再次下载时,是否忽略下载,默认为true(忽略),设为false会从头下载
                .setConnectionCount(1)  //需要用几个线程来下载文件,默认根据文件大小确定;如果文件已经 split block,则设置后无效
                .setPreAllocateLength(false) //在获取资源长度后,设置是否需要为文件预分配长度,默认false
                .setMinIntervalMillisCallbackProcess(1500) //通知调用者的频率,避免anr,默认3000
                .setWifiRequired(false)//是否只允许wifi下载,默认为false
                .setAutoCallbackToUIThread(true) //是否在主线程通知调用者,默认为true
                //.setHeaderMapFields(new HashMap<String, List<String>>())//设置请求头
                //.addHeader(String key, String value)//追加请求头
                .setPriority(0)//设置优先级,默认值是0,值越大下载优先级越高
                .setReadBufferSize(4096)//设置读取缓存区大小,默认4096
                .setFlushBufferSize(16384)//设置写入缓存区大小,默认16384
                .setSyncBufferSize(65536)//写入到文件的缓冲区大小,默认65536
                .setSyncBufferIntervalMillis(2000) //写入文件的最小时间间隔,默认2000
                .build();

    }

4.监听下载

在初始化的时候有个设置监视的地方:DownloadMonitor 这方法里面有任务从创建到执行完成的一些状态回调.如果要监听下载中的进度就要针对每个任务添加任务监听,

1.okdownload提供了6种:

DownloadListener、DownloadListener1、DownloadListener2、DownloadListener3、DownloadListener4、DownloadListener4WithSpeed.每个类都有不同的实现.任务完成后自动移除监听.可以去官方文档看看每个监听实现了哪些方法.根据自己的业务需求选择使用.

例举两个:

DownloadListener2只监视任务的创建和任务的结束,这个是用在下载目录中选择下载项后标记是否下载,下载是否否完成的.因为这个地方不用显示下载进度.只要知道哪项记录有没有添加下载;

DownloadListener4WithSpeed提供了更多的回调方法包括下载进度.下载进度需要自己保存,暂时没有找到可以获取的api,可以用hashmap用url作为key临时保存下载进度.

2.另外一个比较重要的UnifiedListenerManager

如果一个任务同时需要多个监听.比如有几个页面都需要监听同一个任务的状态就要用到这个辅助类.这个类的必须使用单例模式创建,保证全局只有一个实例.

public class MyUnifiedListenerManager {
    //内存屏障 关键字volatile
    volatile static UnifiedListenerManager sListenerManager;

    public static UnifiedListenerManager getListenerManager() {
        if (sListenerManager == null) {
            //线程锁
            synchronized (MyUnifiedListenerManager.class) {
                sListenerManager = new UnifiedListenerManager();
            }
        }
        return sListenerManager;
    }
}

添加监听

1.下载任务和监听绑定关系(不管任务是不是在执行中)

 MyUnifiedListenerManager.getListenerManager().attachAndEnqueueIfNotRun(preTasK,mDownloadListener4WithSpeed);

 2.如果任务没有运行的话就开始执行下载任务并关联监听.

 MyUnifiedListenerManager.getListenerManager().attachAndEnqueueIfNotRun(downloadTask,mDownloadListener4WithSpeed);

 3.立即执行任务并关联监听.

 MyUnifiedListenerManager.getListenerManager().enqueueTaskWithUnifiedListener(preTasK,mDownloadListener4WithSpeed);

移除监听

MyUnifiedListenerManager.getListenerManager().detachListener(sameTask,mDownloadListener4WithSpeed);

5.获取下载状态

               DownloadTask preTasK = DownloadUtil.createPreTasK(item);
                StatusUtil.Status status = StatusUtil.getStatus(preTasK);
StatusUtil.getStatus()有两种使用方法:可以传入任务对象,也可以传入任务对象的id.共有5个状态
PENDING:任务已经添加到下载队列了
RUNNING:任务正在运行
COMPLETED:任务下载完成.如果没有断点记录,本地有下载文件不管是不是完成都会认为是下载完成.
IDLE:任务空闲状态,就是暂停的任务
// may completed, but no filename can't ensure.
UNKNOWN:未添加的任务,如果项目中已下载的任务在杀死app后在打开app之后的任务状态还是unknow,可以检查第一次获取任务状态时候创建任务的方法.需要使用上面完整的方式创建任务实例.这样okdownload会从自己的记录中匹配相同参数的任务,如果有了就会返回已经存在任务的信息.没有则会创建新的任务.不要使用new DownloadTask.Builder(url, parentPath, fileName).build()这种简单的方式创建,这种就会返回unknown.完成写法能找到任务记录,返回任务id.

6.任务操作

1.暂停任务

DownloadTask preTasK = DownloadUtil.createPreTasK(item);
preTasK.cancel();

或者使用

OkDownload.with().downloadDispatcher().cancel(preTasK.getId());

2.开始任务或者继续任务

 MyUnifiedListenerManager.getListenerManager().attachAndEnqueueIfNotRun(preTasK,mDownloadListener4WithSpeed);

3.取消任务

 DownloadTask downloadTask = DownloadUtil.createPreTasK(downLoadVideo);
                    //删除任务并移除文件
                    MyUnifiedListenerManager.getListenerManager().detachListener(downloadTask.getId());
                    OkDownload.with().downloadDispatcher().cancel(downloadTask.getId());//暂停下载
                    OkDownload.with().breakpointStore().remove(downloadTask.getId());
                    File file = new File(DownloadUtil.getDownloadFilePath(downLoadVideo.getCourseId(),
                            DownloadUtil.getDownloadFileName(downLoadVideo.getVideoId(), downLoadVideo.getVideoUrl())));
                    if (file != null && file.exists()) {
                        file.delete();
                    }
                    LitePal.delete(DownLoadVideo.class, downLoadVideo.getId());

7.DownlaodUtils工具类

public class DownloadUtil {
    /**
     * 获取下载根目录
     *
     * @param context
     * @param type
     * @return
     */
    public static File getRootDir(Context context, String type) {
        String state = Environment.getExternalStorageState();
        File root;
        if (state.equals(Environment.MEDIA_MOUNTED)) {
            root = context.getApplicationContext().getExternalFilesDir(type);
        } else {
            String path = context.getApplicationContext().getFilesDir().getPath();
            root = new File(path + File.separator + type);
            if (!root.exists() || !root.isFile()) {
                root.mkdirs();
            }
        }
        return root;
    }

    /**
     * 获取课程下载目录
     *
     * @param courseId
     * @return
     */
    public static String getDownloadCourseDir(String courseId) {
        String dir = getRootDir(App.getContext().getApplicationContext(),
                Environment.DIRECTORY_DOWNLOADS) + File.separator + "." + courseId;
        File file = new File(dir);
        if (!file.exists() || !file.isDirectory()) {
            file.mkdirs();
        }
        // + File.separator + videoInfo.getVideoName() + videoInfo.getVideoUrl().substring(videoInfo.getVideoUrl()
        // .lastIndexOf('.'))
        return dir;
    }

    public static String getDownloadUrl(String videoUrl) {
        return !TextUtils.isEmpty(videoUrl) ? videoUrl.replace("/index.m3u8", ".mp4") : videoUrl;
    }

    /**
     * 下载文件名
     *
     * @param fileName
     * @param videoUrl
     * @return
     */
    public static String getDownloadFileName(String fileName, String videoUrl) {
        return videoUrl.lastIndexOf(".") > 0 ? fileName + videoUrl.substring(videoUrl.lastIndexOf(".")) :
                fileName + "." + videoUrl;
    }

    /**
     * 获取下载文件路径,
     *
     * @param courseId 直播或者录播id
     * @param fileName 文件存储的名字,前期用的视频名字,后面改用视频id
     * @return
     */
    public static String getDownloadFilePath(String courseId, String fileName) {
        String dir =
                getRootDir(App.getContext().getApplicationContext(),
                        Environment.DIRECTORY_DOWNLOADS) + File.separator + "." + courseId;
        File file = new File(dir);
        if (!file.exists() || !file.isDirectory()) {
            file.mkdirs();
        }
        return dir + File.separator + fileName;
    }

    /**
     * 使用下载载体创建下载实例
     *
     * @param item
     * @return
     */
    public static DownloadTask createPreTasK(DownLoadVideo item) {
        return createPreTasK(item.getVideoUrl(), getDownloadCourseDir(item.getCourseId()),
                getDownloadFileName(item.getVideoId(), item.getVideoUrl()));
    }

    /**
     * 简单创建下载
     *
     * @param url
     * @param parentPath
     * @param fileName
     * @return
     */
    public static DownloadTask createPreTasK(String url, String parentPath, String fileName) {
        return new DownloadTask.Builder(url, parentPath, fileName).build();
    }

    /**
     * 使用下载载体创建下载实例
     *
     * @param item 存储下载信息的自定义对象.
     * @return
     */
    public static DownloadTask createTasK(DownLoadVideo item) {
        return createTasK(item.getVideoUrl(), getDownloadCourseDir(item.getCourseId()),
                getDownloadFileName(item.getVideoId(), item.getVideoUrl()));
    }

    /**
     * 创建下载任务实例
     *
     * @param url
     * @param parentPath
     * @param fileName
     * @return
     */
    public static DownloadTask createTasK(String url, String parentPath, String fileName) {
        return new DownloadTask.Builder(url, parentPath, fileName)
                .setFilenameFromResponse(false)//是否使用 response header or url path 作为文件名,此时会忽略指定的文件名,默认false
                .setPassIfAlreadyCompleted(true)//如果文件已经下载完成,再次下载时,是否忽略下载,默认为true(忽略),设为false会从头下载
                .setConnectionCount(1)  //需要用几个线程来下载文件,默认根据文件大小确定;如果文件已经 split block,则设置后无效
                .setPreAllocateLength(false) //在获取资源长度后,设置是否需要为文件预分配长度,默认false
                .setMinIntervalMillisCallbackProcess(1500) //通知调用者的频率,避免anr,默认3000
                .setWifiRequired(false)//是否只允许wifi下载,默认为false
                .setAutoCallbackToUIThread(true) //是否在主线程通知调用者,默认为true
                //.setHeaderMapFields(new HashMap<String, List<String>>())//设置请求头
                //.addHeader(String key, String value)//追加请求头
                .setPriority(0)//设置优先级,默认值是0,值越大下载优先级越高
                .setReadBufferSize(4096)//设置读取缓存区大小,默认4096
                .setFlushBufferSize(16384)//设置写入缓存区大小,默认16384
                .setSyncBufferSize(65536)//写入到文件的缓冲区大小,默认65536
                .setSyncBufferIntervalMillis(2000) //写入文件的最小时间间隔,默认2000
                .build();

    }
}

后面还有任务队列.类似下载组任务的功能.项目中没用到就不说明了.使用okdownload主要是在两个地方耽误了不少时间,一个是UnifiedListenerManager,一开始没有想到这个是干嘛用.不能实现项目中的业务,后来看了一点代码才明白是怎么使用的,还有一个就是下载任务的状态,已下载的任务杀死app之后总是不能使用断点续传的功能.跟踪代码发现是在创建任务实例的时候没有找到okdownload记录的断点记录,每次都当成新的任务,使用完整创建实例的方法就可以正常断点下载了.

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值