5.31~6.3在线下载服务器文件,应用及通知栏显示进度和断点多线程分段下载

下载应用,进度更新,通知栏显示

  • 下载后软件安装,安装好后打开软件
安装软件
if (beginDownload.getText().equals("安装")){
            Intent intent=new Intent(Intent.ACTION_VIEW);
            intent.setDataAndType(Uri.fromFile(file),"application/vnd.android.package-archive");//Type有哪些
            startActivity(intent);
        }

多线程分段下载,断点下载

如果我自己开发会有哪些疑问:

  • Q:下次下载怎么从上次保存的点下载,不同线程下载的怎么操作同一个文件
    A:skipBytes(long i):从前往后,seek(long p): 从后往前,随机访问类

  • Q:对一个文件要分几个线程下载,每个线程怎么分配数据长度
    A:

// 计算每条线程下载的数据长度,如果整除就平分,不能整除就直接进1
    this.block = (this.fileSize % this.threads.length) == 0 ? this.fileSize
      / this.threads.length
      : this.fileSize / this.threads.length + 1;
  • Q:怎么从服务器的获取那个文件的指定位置开始下载
    A:
    block是长度每条线程下载的长度,downlength是已经下载过的长度
int startPos = block * (threadId - 1) + downLength;// 开始位置
int endPos = block * threadId - 1;// 结束位置
http.setRequestProperty("Range", "bytes=" + startPos + "-"+ endPos);// 设置获取实体数据的范围

如果是第一次下载 downlength为0
比如19分两条线程 block是10, 1,0~9 2,10~19
或者18分三条线程 block是6,1,0~5 2,611,1217

参考帖子:
http://blog.csdn.net/wwj_748/article/details/20146869

  • 用本地数据库记录不同线程上次下载的位置,根据这个位置去服务器获取指定位置的字节。

              HttpURLConnection http = (HttpURLConnection) downUrl
                      .openConnection();
              http.setConnectTimeout(5 * 1000); // 设置连接超时
              http.setRequestMethod("GET"); // 设置请求方法,这里是“GET”
              // 浏览器可接受的MIME类型
              http.setRequestProperty(
                      "Accept",
                      "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
              http.setRequestProperty("Accept-Language", "zh-CN"); // 浏览器所希望的语言种类,当服务器能够提供一种以上的语言版本时要用到
              http.setRequestProperty("Referer", downUrl.toString());// 包含一个URL,用户从该URL代表的页面出发访问当前请求的页面。
              http.setRequestProperty("Charset", "UTF-8"); // 字符集
              int startPos = block * (threadId - 1) + downLength;// 开始位置
              int endPos = block * threadId - 1;// 结束位置
              http.setRequestProperty("Range", "bytes=" + startPos + "-"
                      + endPos);// 设置获取实体数据的范围
              
              // 浏览器类型,如果Servlet返回的内容与浏览器类型有关则该值非常有用。
              http.setRequestProperty(
                      "User-Agent",
                      "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
              http.setRequestProperty("Connection", "Keep-Alive"); // 设置为持久连接
    
              // 得到输入流
              InputStream inStream = http.getInputStream();
              byte[] buffer = new byte[1024];
              int offset = 0;
              print("Thread " + this.threadId
                      + " start download from position " + startPos);
              // 随机访问文件
              RandomAccessFile threadfile = new RandomAccessFile(
                      this.saveFile, "rwd");
              // 定位到pos位置
              threadfile.seek(startPos);
              while (!downloader.getExit()
                      && (offset = inStream.read(buffer, 0, 1024)) != -1) {
                  // 写入文件
                  threadfile.write(buffer, 0, offset);
                  downLength += offset; // 累加下载的大小
                  downloader.update(this.threadId, downLength); // 更新指定线程下载最后的位置
                  downloader.append(offset); // 累加已下载大小
              }
              threadfile.close();
              inStream.close();
              print("Thread " + this.threadId + " download finish");
              this.finish = true;
    
  • 缓存各线程下载的长度


   Map<Integer, Integer> logdata = fileService  
                        .getData(downloadUrl);// 获取下载记录           

    /* 缓存各线程下载的长度 */  
    private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();  
for (Map.Entry<Integer, Integer> entry : logdata.entrySet())  //entryS et()获取对象
   data.put(entry.getKey(), entry.getValue());// 把各条线程已经下载的数据长度放入data中  

获取文件名,获取连接最后一个“/”后面的或者从HeaderField获取

 private String getFileName(HttpURLConnection conn) {  
        String filename = this.downloadUrl.substring(this.downloadUrl  
                .lastIndexOf('/') + 1);  
        if (filename == null || "".equals(filename.trim())) {// 如果获取不到文件名称  
            for (int i = 0;; i++) {  

                String mine = conn.getHeaderField(i);  

                if (mine == null)  
                    break;  

// content-disposition 就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名
                if ("content-disposition".equals(conn.getHeaderFieldKey(i)  
                        .toLowerCase())) {  

//匹配字段获取文件名,正则表达式
                    Matcher m = Pattern.compile(".*filename=(.*)").matcher(  
                            mine.toLowerCase());  

                    if (m.find())  
                        return m.group(1);  
                }  
            }  
            filename = UUID.randomUUID() + ".tmp";// 默认取一个文件名  
        }  
        return filename;  
    }  

报头的Content-disposition
就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名
就像用电脑的时候,弹窗让用户保存东西的时候有个默认的名字
如果用于电脑,服务端要做的事:
1.当代码里面使用Content-Disposition来确保浏览器弹出下载对话框的时候。
response.addHeader("Content-Disposition","attachment");一定要确保没有做过关于禁止浏览器缓存的操作。如下:
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "No-cache");
response.setDateHeader("Expires", 0);

JAVA正则表达式 Pattern和Matcher

随机访问文件类RandomAccessFile
我觉得简单来说就是 分段下载或者断点下载的分割类,可以看做 节点流

 // 随机访问文件  
                RandomAccessFile threadfile = new RandomAccessFile(  
                        this.saveFile, "rwd");  
                // 定位到pos位置  
                threadfile.seek(startPos);  

有点切割字符串的方式切割文件打印字符串

白纸习题

|123456789|123456789|123456789|
                          1条记录   2条记录   3条记录

//练习随机访问文件类
import java.io.*;
class Student
{
                              String name="aaaaaaa";
                              int age=0;
                              public static final int LEN=8;
                              public Student (String n,int a)
                              {
                                   if(n.length( )>LEN)
                                   {
                                          n=n.substring(0,LEN);
                                   }
                                  if(n.length( )<LEN)
                                   {
                                          while(n.length( )<LEN)
                                          {
                                            n+="/u0000";//空格
                                          }
                                   }
                                  this.name=n;
                                   this.age=a;
                              }
}
class userText1
{
                              public static void main(String args[ ])throws Exception
                              {
                                 Student stu1=new Student("Ada",23);
                                   Student stu2=new Student("Shirlenjklcxvfchfhj",24);
                                  Student stu3=new Student("sunfcvvcxvdfdas",25);
                              RandomAccessFile ra=new RandomAccessFile("student.txt","rw");//写流
                                   ra.write(stu1.name.getBytes( ));//第一的前8个字节,stu1 name属性
                                                                 //将当前对象的name属性的字符串转为8字节数组
                                   ra.write(stu1.age);//(文件的第九个字节)将int类型的age以单字节的保存在文件中,占有一个字节
                                   ra.write(stu2.name.getBytes( ));//第二对象的前8个字节,stu2 name属性
                                   ra.write(stu2.age);
                                   ra.write(stu3.name.getBytes( ));//第三对象的前8个字节,stu3 name属性
                                   ra.write(stu3.age);
                                   ra.close( );
                                   int len=0;
                                  byte buf[]=new byte[8];//长度为8的字节数组.
                              RandomAccessFile raf=new RandomAccessFile("student.txt","r");//读流
                                   //------------------------读对象2属性name,age
                                   raf.skipBytes(9);//跳过9个字节
                                       System.out.println (raf.getFilePointer( ));//指针位置
                                    len=raf.read(buf);//##从文件当中读到的字节放在字节数组中最多只能放8个,并返回读取字节的个数。
                                String str=null;//对象   
                                   str=new String(buf,0,len);//0-8//将字节数组buf[]中的全部内容转为String类型。
                                   System.out.println (str+":"+raf.read( ));
                                   //-------------------------读对象1属性name,age
                                   raf.seek(0);//对指示器进行决对定位
                                       System.out.println (raf.getFilePointer( ));//指针位置
                                   len=raf.read(buf);//读取8个字节
                                   str=new String(buf,0,len);
                                   System.out.println (str+":"+raf.read( ));//age取一个字节
                                  //--------------------------读对象3属性name,ag
                                   raf.skipBytes(9);
                                       System.out.println (raf.getFilePointer( ));//指针位置
                                   len=raf.read(buf);//读取8个字节
                                   str=new String(buf,0,len);
                                   System.out.println (str+":"+raf.read( ));
                                     System.out.println (raf.getFilePointer( ));//指针位置
                                  raf.close( );
                              }

}

多线程分段下载的意义:
网速带宽是一定的,那么为什么多线程下载能加速?(TCP单流很难利用满带宽)
https://www.zhihu.com/question/19914902

F2 AS跳转到错误处
Q:RandomAccessFile中mode,rws和rwd的区别?
A:

先写这个多线程分段下载
我是这么个步骤,由浅入深
1 我先写了个下载
2 再写单线程下载+暂停
3 最后才来写多线程下载+暂停

瓶颈:
类的方法分类,比如一个是线程类,一个是下载器类,在线程类处理哪些跟下载器处理哪些归类不清晰
收获:
思路很重要

对已 单线程下载+暂停

首次下载 获取httpurlConnection,connect,获取输入流,创建本地文件,设置缓冲区大小, 每次将缓冲的数据写入文件,记录暂停的位置,插入数据库

    SqlCreater sqlCreater = new SqlCreater(GetNetworkSize.this);
    DbOperator dbOperator = new DbOperator(sqlCreater.getWritableDatabase());
    HttpURLConnection urlConnection=Network.urlConnection(apkpath);
    if (isFirst){
        isFirst=false;
        try {
            InputStream in=urlConnection.getInputStream();
            apksize=urlConnection.getContentLength()+"";
            file=new File(apkpath);
            FileOutputStream fo=new FileOutputStream(file);
            byte[]b=new byte[1024];
            int line=0;
            while ((line=in.read())!=-1){
                fo.write(b,0,line);
                currentApksize+=line;
                //第一次下载是添加
                dbOperator.add(1,currentApksize);


                p = (float) currentApksize / (float) Integer.valueOf(apksize) * 100;
                pro = (int) p;
                handler.sendEmptyMessage(SINGLEDOWN);
            }
            fo.close();
            in.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    else{
        //第二次下载是更新数据库的内容,向服务器请求进度, 并且拿到已经保存的文件,根据进度,随机访问文件下载
        int startpos=dbOperator.getCurrentApkSize("multidownload");
        try {
            URL url=new URL(apkpath);
            HttpURLConnection conn= (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setReadTimeout(5000);
            conn.setRequestProperty("Range","byte="+startpos+"-"+apksize);
            InputStream is=conn.getInputStream();
            RandomAccessFile continuefile=new RandomAccessFile(file,"rwd");
            byte[]b=new byte[1024];
            int line=0;
            while ((line=is.read())!=-1){
                continuefile.write(b,0,line);
                currentApksize+=line;
                handler.sendEmptyMessage(SINGLEDOWN);
            }
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        dbOperator.update(1,currentApksize);

    }

}

非首次下载 获取上次的位置startpos,向服务器获取Range参数
conn.setRequestProperty("Range","byte="+startpos+"-"+apksize);
更新数据库当前线程的下载位置

继续下载不需要再次调用connect方法,已经是connected状态

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