美文网首页
5.31~6.3在线下载服务器文件,应用及通知栏显示进度和断点多

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

作者: FredKang | 来源:发表于2016-06-03 16:09 被阅读0次

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

    • 下载后软件安装,安装好后打开软件
    安装软件
    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状态

    相关文章

      网友评论

          本文标题:5.31~6.3在线下载服务器文件,应用及通知栏显示进度和断点多

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