kill应用以后,android断点续传的实现

来源:互联网 发布:银行业大数据 编辑:程序博客网 时间:2024/05/15 18:15

看了网上很多关于Android断点续传的文章,方法多样,但是涉及断网、断电、kill掉应用的情况却很少。因为我在的公司做的是盒子游戏,通过一个Android系统的机顶盒,用来在Unity3D和服务器之间进行通。老板要求更新APP应用的时候,在wifi断开或者盒子突然断电,重新打开应用的时候,要求继续更新而不是重新下载。这个问题困恼了我几天,最后参考了http://blog.csdn.net/a1533588867/article/details/53129259 这篇博客的内容,并且对他的源码进行一定的修改,也算是完成了老板的需求。万分感谢这位博主!

kill应用的原理和断电的情况一致,经过测试,也是没问题的。其实,断电也就相当于用户把在后台运行的应用强制关闭。

先说一下核心内容,正常流程:在下载文件的时候,创建两个文件,一个是下载后的总文件,一个是临时文件,设定好要写入的大小比如5M,开启一个下载线程,用FileOutputStream往临时文件中进行写入,当写满5M的时候,暂停下载,开启一个文件拼接线程,利用RandomAccessFile将临时文件拼接在总文件中,拼接完以后,删除临时文件,并且继续下载,直到下载完。

当出现异常的时候,比如wifi断开或者断电了,在开启下载线程之后,HttpURLConnection 连接网络之前,对临时文件进行操作,如果临时文件存在,就暂停下载线程,并且开启文件拼接线程,将临时文件拼接在总文件中,拼接完以后,删除临时文件,开启下载线程继续下载。大致就是这样。对于如何初始化线程,如何保存进度,如何分发和接受下载进度,如何管理下载线程等都在上面那篇博客讲到了,还挺详细的,我就不多做描述。

下面贴出一些核心代码,便于理解。


这是下载线程。


public class DownloadThread extends Thread {    private FileBean fileBean;    private ThreadBean threadBean;    private DownloadCallBack callback;    private Boolean isPause = false;    private Context mContext;    private final int divisionLen = 1024 * 1024 * 5;    private boolean downloadOver = false;    public DownloadThread(FileBean fileBean, ThreadBean threadBean, DownloadCallBack callback, Context mContext) {        this.fileBean = fileBean;        this.threadBean = threadBean;        this.callback = callback;        this.mContext = mContext;    }    public void setPause(Boolean pause) {        isPause = pause;    }    @Override    public void run() {        HttpURLConnection connection = null;        FileOutputStream fos = null;        InputStream inputStream = null;        //设置下载起始位置        int start = threadBean.getStart() + threadBean.getFinished();        String sp_tempFileName = Utils.getShardPreferences(mContext, "tempFileName");        Log.e("tag", "sp_tempFileName = " + sp_tempFileName);        if (sp_tempFileName != null) {            Log.e("tag", "sp中存在存在tempFileName");            File sp_tempFile = new File(Config.downLoadPath,sp_tempFileName);            //处理上次没有下载完的文件            if (sp_tempFile.exists() && sp_tempFile.length() <= divisionLen) {                Log.e("tag", "上次没有下载完的文件:"+sp_tempFileName+",长度为: " + sp_tempFile.length());                //调用暂停下载方法,销毁 下载线程                // 一定要先调用暂停方法,去销毁下载线程,再开启 文件拼接的线程。                //因为断电的时候,临时文件的大小有可能为0KB,导致如果先开启文件拼接的线程,拼接过程瞬间完成,                // 使得下载线程可能还没销毁完成,就开启了下载线程,然后下载线程又被销毁                Utils.pauseDownload(mContext,fileBean);                new AddFileThread(sp_tempFileName, fileBean, start, (int) sp_tempFile.length(), mContext, false).start();                threadBean.setFinished((int) (threadBean.getFinished() + sp_tempFile.length()));                //修改进度                callback.pauseCallBack(threadBean);                return;            }        } else {            Log.e("tag", "不存在 tempFileName 说明是第一次");        }        try {            URL url = new URL(threadBean.getUrl());            connection = (HttpURLConnection) url.openConnection();            connection.setConnectTimeout(10000);            connection.setRequestMethod("GET");            connection.setRequestProperty("Range", "bytes=" + start + "-" + threadBean.getEnd());            //设置写入位置 写入临时文件            String tempFileName = System.currentTimeMillis() + "tempFile";            Utils.setShardPreferences(mContext, "tempFileName", tempFileName);            File tempFile = new File(Config.downLoadPath, tempFileName);            fos = new FileOutputStream(tempFile);            //开始下载            if (connection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) {                inputStream = connection.getInputStream();                byte[] bytes = new byte[1024];                int len = -1;                int subsLen = 0;                Log.e("tag", "---开始下载---");                while (subsLen < divisionLen) {                    len = inputStream.read(bytes);                    //下载完毕 退出循环                    if (len < 0) {                        Log.e("tag", "下载完毕,退出循环");                        downloadOver = true;                        break;                    }                    //下载达到分次下载量 退出循环                    if ((subsLen + len) > divisionLen) {                        Log.e("tag", "达到分批下载量,退出循环 countLen = " + subsLen);                        break;                    }                    if (isPause){                        //调用暂停下载方法,销毁 下载线程                        Log.e("tag", "调用暂停下载方法,销毁下载线程");                        Utils.pauseDownload(mContext,fileBean);                        break;                    }                    fos.write(bytes, 0, len);                    subsLen += len;                    //将加载的进度回调出去                    callback.progressCallBack(len);                }                if (!isPause){                    // 对于整个文件 一共下载的进度。保存进度                    int lengthCount = threadBean.getFinished() + subsLen;                    threadBean.setFinished(lengthCount);                    //分批下载完,需要将数据保存到数据库                    callback.pauseCallBack(threadBean);                    Log.e("tag", "一共下载了:lengthCount = " + lengthCount);                    //这里因为文件需要进行拼接 5M 是一个耗时过程,所以 销毁下载线程 和 开启 文件拼接线程 的顺序可先可后                    //调用暂停下载方法,销毁 下载线程                    Utils.pauseDownload(mContext,fileBean);                    //开启线程 进行文件的拼接                    new AddFileThread(tempFileName, fileBean, start, subsLen, mContext, downloadOver).start();                }                //下载完成                if (downloadOver) {                    callback.threadDownLoadFinished(threadBean);                }            }        } catch (Exception e) {            //当断网的时候,会抛出连接超时异常,所以要在 catch 里回调 pauseCallBack 接口,进行数据的保存            Log.e("tag", "调用暂停下载方法,销毁下载线程");            Utils.pauseDownload(mContext,fileBean);        } finally {            try {                inputStream.close();                fos.close();                connection.disconnect();            } catch (Exception e) {                Log.e("tag", "Exception-111--" + e.getMessage());            }        }    }}


接下来是文件拼接的线程


public class AddFileThread extends Thread {    private String tempFileName;    private FileBean fileBean;    private int filePointer;    private int downloadLen;    private Context context;    private boolean downloadOver;    /**     * @param tempFileName 临时文件名     * @param filePointer 从文件的哪里拼接     * @param downloadLen 拼接的长度     * @param downloadOver 是否下载完     */    public AddFileThread(String tempFileName, FileBean fileBean, int filePointer, int downloadLen, Context context,boolean downloadOver){        this.tempFileName = tempFileName;        this.fileBean = fileBean;        this.filePointer = filePointer;        this.downloadLen = downloadLen;        this.context = context;        this.downloadOver = downloadOver;    }    @Override    public void run() {        super.run();        File file = new File(Config.downLoadPath,fileBean.getFileName());        File tempFile = new File(Config.downLoadPath,tempFileName);        RandomAccessFile raf = null;        FileInputStream fis =null;        try {            raf = new RandomAccessFile(file,"rw");            raf.seek(filePointer);            fis = new FileInputStream(tempFile);            Log.e("tag","拼接文件的线程:临时文件 tempFile.length() = "+tempFile.length());            int len = -1;            int countLen = 0;            byte buf[] = new byte[1024];            while(countLen < downloadLen){                len = fis.read(buf);                countLen += len;                raf.write(buf,0,len);                //处理最后一点字节                if((countLen + 1024) > downloadLen){                    byte buff[] = new byte[downloadLen - countLen];                    len = fis.read(buff);                    Log.e("tag","最后一次读取的长度为:" + len);                    raf.write(buff,0,len);                    break;                }            }        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }finally {            try {                raf.close();                fis.close();            } catch (IOException e) {                Log.e("tag","Exception --- "+e.getMessage());            }        }        //删除临时文件        tempFile.delete();        //开启下载线程 接着去下载文件        if (!downloadOver){            Log.e("tag","继续下载");            Utils.startDownload(context,fileBean);        }    }}


因为文件拼接的过程是一个耗时的操作,但是拼接速度很快大概200ms,基本可以忽略不计。如果你运气是真的好,在200ms的文件拼接过程中,kill掉了应用或者断电了,拼接过程中断,下载完成以后,apk安装包解析不会出错,但是会出现安装更新失败的情况。如果真的出现了,快去买一张彩票!说不定呢,对吧!

附上测试拼接文件过程所耗费的时间(这里拼接的文件大小为10M):



保留了自己调试的一些Log,应该没太大问题吧。其余代码都和那位博主的源码差不多。


如果有下载完apk文件,自动安装的话,在下载线程中要稍作修改。


//下载完成                if (downloadOver) {                    callback.threadDownLoadFinished(threadBean);                }

这里应该修改成:


AddFileThread addFileThread = new AddFileThread(tempFileName, fileBean, start, subsLen, mContext, downloadOver);                addFileThread.start(); //下载完成                if (downloadOver) {                    while (true){                        //必须等待 文件拼接线程结束才可以去回调接口通知下完完毕                        //否则可能会出现 文件还没拼接完毕,就调用了安装apk文件的方法,                        // 导致出现apk解析错误                        if (!addFileThread.isAlive()){                            callback.threadDownLoadFinished(threadBean);                            break;                        }                    }                }

最后提一下,那位博主的demo支持多线程下载,我的应该不支持,我没去调试。然后,那个暂停按钮可能存在一点bug,同样的代码测试着测试着就没问题了[费解][费解]因为我的需求里不需要暂停按钮,就没有多做研究,个人觉得没有太大问题。


最后附上我的源码:http://download.csdn.net/download/baidu_35697065/9933925