android_day05

1.使用httpurlconnection方式把数据提交到服务器
    基于什么协议 http
    get方式:组拼url地址把数据组拼到url上  有大小限制 1kb 4kb
    post方式:post方式提交安全 没有大小限制
    
    get方式提交数据,代码如下:
        // 点击按钮 进行get方式提交数据
        public void click1(View v) {
            new Thread() {
                public void run() {
                    try {
                        // 2.获取用户名和密码
                        String name = et_username.getText().toString().trim();
                        String pwd = et_password.getText().toString().trim();
                        // 2.1定义get方式要提交的路径 小细节 如果提交中文要对name和pwd进行一个urlencode编码
                        String path = "http://192.168.10.98:8080/login/LoginServlet?username="
                                + URLEncoder.encode(name, "utf-8")
                                + "&password="
                                + URLEncoder.encode(pwd, "utf-8") + "";

                        // (1)创建一个url对象 参数就是网址
                        URL url = new URL(path);
                        // (2)获取hrrpURLConnection连接对象
                        HttpURLConnection conn = (HttpURLConnection) url
                                .openConnection();
                        // (3)设置参数 发送get请求
                        conn.setRequestMethod("GET");// 默认请求 就是get 要大写
                        // (4)设置网络连接的超时时间
                        conn.setConnectTimeout(5000);
                        // (5)获取服务器返回的状态码
                        int code = conn.getResponseCode();// 200 代表获取服务器资源全部成功
                                                            // 206请求部分资源成功
                        if (code == 200) {
                            // (6)获取服务器返回的数据 以流的形式返回
                            InputStream inputStream = conn.getInputStream();

                            // (6.1)把inputstream转换成string
                            String content = StreamTools.readStream(inputStream);

                            // (7)把服务器返回的数据展示到toast上 不能在子线程展示toast
                            showToast(content);
                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                };
            }.start();

        }
        
        
    post方式和get区别
    [1]路径不同 http://192.168.10.98:8080/login/LoginServlet
    [2]请求方式post
    [3]通过请求体的方式把数据写给服务器(以流的形式写给服务器)
        post和get方式提交数据 区别  要多设置2个请求头信息
        //设置头信息
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        conn.setRequestProperty("Content-Length", data.length()+"");
            
    post方式提交代码如下:
        // [1]点击按钮,进行post方式提交数据
        public void click2(View v) {
            new Thread() {
                public void run() {
                    try {
                        //[2]获取用户名和密码
                        String name=et_username.getText().toString().trim();
                        String pwd=et_password.getText().toString().trim();
                        //[2.1]定义post方式要提交的路径
                        String data="username="+URLEncoder.encode(name,"utf-8")+"&password="+URLEncoder.encode(pwd,"utf-8");//请求体的内容
                        
                    //一 post和get方式提交数据  区别  路径不同
                        String path="http://192.168.10.98:8080/login/LoginServlet";
                        
                        //(1)创建一个url对象  参数就是网址
                        URL url=new URL(path);
                        //(2)获取httpurlconnection对象
                        HttpURLConnection conn=(HttpURLConnection) url.openConnection();
                    //二 post和get方式提交数据区别  设置请求方式是post
                        //(3)设置参数  发送post请求
                        conn.setRequestMethod("POST");//默认请求 就是get 要大写
                        //(4)设置网络超时时间
                        conn.setConnectTimeout(5000);
                    //三 post和get方式提交数据 区别  要多设置2个请求头信息
                        //设置头信息
                        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                        conn.setRequestProperty("Content-Length", data.length()+"");
                        
                    //四 post和get方式提交数据 区别 把我们组好的数据提交给服务器  以流的形式提交
                        conn.setDoOutput(true);//设置一个标记  允许输出
                        conn.getOutputStream().write(data.getBytes());
                        
                        //(5)获取服务器返回的状态码
                        int code=conn.getResponseCode();// 200 代表获取服务器资源全部成功  206请求部分资源成功
                        if(code==200){
                            //(6)获取服务器返回的数据  以流的形式返回
                            InputStream inputStream = conn.getInputStream();
                            
                            //(6.1)把inputStream转换成string
                            String content=StreamTools.readStream(inputStream);
                            
                            //(7)把服务器返回的数据展示到toast上  不能再子线程展示toast
                            showToast(content);
                            
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    
                };
            }.start();

        }
    
    其中使用到的工具类:
        public class StreamTools {

            //把一个inputStream转换成一个String
            public static String readStream(InputStream in) throws Exception{
                
                //定义一个内存输出流
                ByteArrayOutputStream baos=new ByteArrayOutputStream();
                int len=-1;
                byte[] buffer=new byte[1024];//1kb
                while((len=in.read(buffer))!=-1){
                    baos.write(buffer,0,len);
                }
                in.close();
                String content=new String(baos.toByteArray(),"utf-8");
                
                return content;
                
            }
            
        }
    

2.乱码问题解决
    编码与解码:
    1.文字->(数字01代码):编码encode:就是把看得懂的内容,转换成看不懂的内容。 
      2.(数字01代码) -> 文字 : 解码decode:就是把看不懂的内容,转换成看懂的内容。
    【注意】:url的中文要URLEncoder.encode(name, "utf-8"),只对中文编码
    
    服务器与浏览器交互时对数据的处理方式
    Html使用utf-8编码(变成流二进制)
        ---1--->Tomcat使用ISO-8859-1解码,编解码不一致
        ---2--->Serlvet直接使用,必然乱码
        ---3先编码--->使用ISO-8859-1编码(变成二进制)
        ---4后解码--->使用UTF-8解码
    用这种:new String(name.getBytes("iso-8859-1"), "utf-8")
    
    1.浏览器发送数据
        html使用的编码格式是utf-8,所以浏览器中输入了某一数据,浏览器会先将这个数据以utf-8作为编码表将它进行编码,然后把编码后的01数据发送到服务器
    2.tomcat服务器的特点:
        而tomcat服务器默认使用的是ISO-8859-1的编码表,因此当服务器接收到浏览器发送的01数据时,服务器会默认使用ISO-8859-1的编码表将其进行解码
    3.英文与标点不会乱码的原因:
        ISO-8859-1与utf-8都是兼容ASCII码的,因此从浏览器发送到服务器中属于ASCII码的内容时不会乱码的,因为它们编解码都是按照ASCII码
    4.汉字会出现乱码的原因:
        ISO-8859-1不支持汉字,即使时GBK这种支持汉字的,通过他们编码再使用utf-8编码也是会出现乱码;因为ISO-8859-1不支持汉字,没有汉字对应的编码表,而GBK中同一个汉字对应的01代码与utf-8对应的01代码不一致,因为他们单个字符的占的字节数时不同的。
    5.tomcat服务器接收数据:
        当tomcat服务器接收到从服务器发来的01数据时,tomcat服务器会将它用ISO-8859-1编码表进行解码。因此这个01数据在浏览器中代表的时汉字时,那么tomcat用ISO-8859-1编码表解码后一定时一个乱码字符
    6.解决服务端乱码问题:【new String(name.getBytes("iso-8859-1"), "utf-8")】
        要想在服务器端获取的数据是一个表示正确的汉字,就需要把这个乱码的字符先用ISO-8859-1编码成01数据,然后再通过utf-8编码表解码成对应的字符,然后存入服务器中字符串一个变量内。这个字符串显示的就是与浏览器相同的正常的汉字。
    7.解决浏览器端乱码问题:【或者用流输出utf-8编码后的数据(01数据、byte)】
        如果把在服务器端显示正常中文字符串发送给浏览器显示,也是乱码的结果。因为这个汉字会先用ISO-8859-1编码表进行编码成01数据,之后发送给浏览器,浏览器会以utf-8编码表进行解码,那解码出来的肯定是一个乱码。
        要想让浏览器显示出与服务器端一样的正常中文字符,需要在服务器端先把汉字字符串用utf-8的编码表进行编码,再用ISO-8859-1编码表进行解码得到一个乱码的字符串。然后服务器在发送数据时会将这个乱码字符串先用ISO-8859-1编码表进行编码后发送到浏览器。浏览器接收到这个01数据后使用utf-8进行解码,就可以得到一个正常的中文字符啦~~~
        
        
    
        

3.以httpclient方式把数据提交到服务器
        //点击按钮  进行get方式提交数据
        public void click1(View v){
            new Thread(){
                public void run() {
                    try {
                        String name=et_username.getText().toString().trim();
                        String pwd=et_password.getText().toString().trim();
                        //2.1定义get方式要提交的路径  小细节 如果提交中文要对name和pwd进行urlencode编码
                        String path="http://192.168.10.98:8080/login/LoginServlet?username="+
                        URLEncoder.encode(name,"utf-8")+"&password="+URLEncoder.encode(pwd,"utf-8")+"";
                        
                        //3.获取httpclient的实例
                        DefaultHttpClient client=new DefaultHttpClient();
                        //3.1准备get请求  定义一个httpget实现
                        HttpGet get=new HttpGet(path);
                        //3.2执行一个get请求
                        HttpResponse response = client.execute(get);
                        
                        //4.获取服务器返回的状态码
                        int code = response.getStatusLine().getStatusCode();
                        if(code==200){
                            //5.获取服务器返回的数据  以流的形式返回
                            InputStream inputStream = response.getEntity().getContent();
                            
                            //6.把流转换成字符串
                            String content=StreamTools.readStream(inputStream);
                            
                            //7.展示结果
                            showToast(content);
                        }
                        
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                };
            }.start();
        }

        //1.点击按钮  进行post方式提交数据
        public void click2(View v){
            new Thread(){
                public void run() {
                    try {
                        //2.获取用户名和密码
                        String name=et_username.getText().toString().trim();
                        String pwd=et_password.getText().toString().trim();
                        String path="http://192.168.10.98:8080/login/LoginServlet";
                        
                        //3.以httpclient方式进行 post提交
                        DefaultHttpClient client=new DefaultHttpClient();
                        //3.1准备post请求
                        HttpPost post=new HttpPost(path);
                        
                        //3.1.0准备parameters
                        List<NameValuePair> lists=new ArrayList<NameValuePair>();
                        //3.1.1准备NameValuePair 实际上就是我们要提交的用户名和密码  key是服务器key:username
                        NameValuePair nameValuePair=new BasicNameValuePair("username", name);
                        NameValuePair pwdValuePair=new BasicNameValuePair("password", pwd);
                        //3.1.2把nameValuePair和pwdValuePair加入到集合中
                        lists.add(nameValuePair);
                        lists.add(pwdValuePair);
                        
                        //3.1.3准备entity
                        UrlEncodedFormEntity entity=new UrlEncodedFormEntity(lists, HTTP.UTF_8);
                        //3.2准备post方式提交的正文  以实体形式准备(键值对形式)
                        post.setEntity(entity);
                        
                        HttpResponse response = client.execute(post);
                        //4.获取服务器返回的状态码
                        int code = response.getStatusLine().getStatusCode();
                        if(code==200){
                            //5.获取服务器返回的数据  以流的形式返回
                            InputStream inputStream = response.getEntity().getContent();
                            
                            //6.把流转换成字符串
                            String content = StreamTools.readStream(inputStream);
                            
                            
                            //7.展示结果
                            showToast(content);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                };
            }.start();
            
            
        }


4.开源项目方式(asynchttpclient)把数据提交到服务器
    先把com包  源码包拷贝到当前工程
    开源项目get方式提交代码:
        //点击按钮  进行get方式提交数据
        public void click1(View v){
            String name=et_username.getText().toString().trim();
            String pwd=et_password.getText().toString().trim();
            String path=null;
            //2.1定义get方式要提交的路径 小细节  如果提交中文要对name和pwd进行一个urlencode编码
            try {
                path="http://192.168.10.98:8080/login/LoginServlet?username="+
            URLEncoder.encode(name,"utf-8")+"&password="+URLEncoder.encode(pwd,"utf-8")+"";
            } catch (Exception e) {
                e.printStackTrace();
            }
            
            //3.使用开源项目进行get请求
            //3.1创建asynchttpclient
            AsyncHttpClient client=new AsyncHttpClient();
            //3.2进行get请求
            client.get(path, new AsyncHttpResponseHandler() {
                //请求成功的回调方法
                @Override
                public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
                    try {
                        Toast.makeText(getApplicationContext(), new String(responseBody,"utf-8"), 1).show();
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                }
                //请求失败
                @Override
                public void onFailure(int statusCode, Header[] headers,
                        byte[] responseBody, Throwable error) {
                    // TODO Auto-generated method stub
                    
                }
            });
            
            
        }
    
    开源项目post方式提交代码:
        //1.点击按钮  进行post方式提交数据
        public void click2(View v){
            //2.获取用户名和密码
            String name=et_username.getText().toString().trim();
            String pwd=et_password.getText().toString().trim();
            String path="http://192.168.10.98:8080/login/LoginServlet";
            //3.1.0创建asynchttpclient
            AsyncHttpClient client=new AsyncHttpClient();
            
            //3.1.1准备请求体的内容
            RequestParams params=new RequestParams();
            params.put("username", name);
            params.put("password", pwd);
            
            //3.2进行post请求 params 请求参数封装
            client.post(path, params, new AsyncHttpResponseHandler() {
                //请求成功   登录成功
                @Override
                public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
                    try {
                        Toast.makeText(getApplicationContext(), new String(responseBody,"utf-8"), 1).show();
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                    
                }
                
                @Override
                public void onFailure(int statusCode, Header[] headers,
                        byte[] responseBody, Throwable error) {
                    // TODO Auto-generated method stub
                    
                }
            });
            
            
            
        }

总结
[1]httpurlconnetion
[2]httpclient(了解  没有人用)
[3]开源项目(asynchttpclient)

    
5.javase多线程下载
    设置请求服务器文件的位置
    //设置一个请求头Range(作用告诉服务器每个线程下载的开始位置和结束位置)
    conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
    
    为什么多线程能够提高速度 (分块传输) 
    [1]获取文件大小
    [2]在客户端创建一个大小和服务器一模一样的文件(为了提前申请空间)
    [2.1]算出每个线程下载的开始位置和结束位置
    [3]开多个线程去下载文件
    [4]知道每个线程什么时候下载完毕了
    
    每个线程下载的计算公式(最后一个线程的结束位置为length-1)
    blockSize=length(10)/thread(3)=3
    {0 1 2} {3 4 5} {6 7 8 9}
    
    0 0~2 0*blockSize ~ 1*blockSize-1  0~2
    1 3~5 1*blockSize ~ 2*blockSize-1  3~5
    n     n*blockSize ~ (n+1)*blockSize-1
    m      m*blockSize~length-1
    
    

6.断点续传实现
    toy app 练习api 360市场  安卓市场 机锋市场 谷歌市场 4000多行  2年时间
    [1]没有sd卡  判断一下sd卡状态是否可用
    [2]也没有判断手机网络类型
    [3]下载文件之前要先判断  文件是否是一个病毒文件
    ......
    
    目的:RandomAccessFile
    
    普通java项目,下载到项目下,代码如下:
        public class MutilDownLoad {
            
            //1.定义下载的路径
            private static String path="http://192.168.10.98:8080/pic.jpg";
            
            private static final int threadCount=3;//假设开三个线程
            
            private static int runningThread;//代表当前正在运行的线程
            
            public static void main(String[] args) {
                //一 获取服务器文件的大小  要计算每个线程下载的开始位置和结束位置
                try {
                    //(1)创建一个url对象  参数就是网址
                    URL url=new URL(path);
                    //(2)获取HttpURLConnection连接对象
                    HttpURLConnection conn=(HttpURLConnection) url.openConnection();
                    //(3)设置参数  发送get请求
                    conn.setRequestMethod("GET");//默认请求  就是get 要大写
                    //(4)设置连接网络的超时时间
                    conn.setConnectTimeout(5000);
                    //(5)获取服务器返回的状态码
                    int code=conn.getResponseCode();//200 代表获取服务器资源全部成功  206请求部分资源
                    if(code==200){
                        //(6)获取服务器文件的大小
                        int length=conn.getContentLength();
                        
                        //6.1把线程的数量赋值给正在运行的线程
                        runningThread=threadCount;
                        
                        System.out.println("length:"+length);
                        
                        //二 创建一个大小和服务器一模一样的文件  目的提前把空间申请出来
                        RandomAccessFile rafAccessFile=new RandomAccessFile("pic.jpg", "rw");
                        rafAccessFile.setLength(length);
                        
                        //(7)算出每个线程下载的大小
                        int blockSize=length/threadCount;
                        
                        //三  计算每个线程下载的开始位置和结束位置
                        for (int i = 0; i < threadCount; i++) {
                            int startIndex=i*blockSize;//每个线程下载的开始位置
                            int endIndex=(i+1)*blockSize-1;
                            //特殊情况就是最后一个线程
                            if(i==threadCount-1){
                                //说明是最后一个线程
                                endIndex=length-1;
                            }
                            
                            System.out.println("线程id:"+i+"理论下载的位置:"+startIndex+"-----"+endIndex);
                            
                            //四 开启线程去服务器下载文件
                            DownLoadThread downLoadThread=new DownLoadThread(startIndex, endIndex, i);
                            downLoadThread.start();
                        }
                        
                    }
                    
                    
                } catch (Exception e) {
                    e.printStackTrace();
                }
                
                
            }
            
            //定义线程去服务器下载文件
            private static class DownLoadThread extends Thread{
                //通过构造方法把每个线程下载的开始位置和结束位置传递进来
                private int startIndex;
                private int endIndex;
                private int threadId;
                public DownLoadThread(int startIndex, int endIndex, int threadId) {
                    this.startIndex = startIndex;
                    this.endIndex = endIndex;
                    this.threadId = threadId;
                }
                
                
                public void run() {
                    //四 实现去服务器下载文件的逻辑
                    try {
                        //(1)创建一个url对象  参数就是网址
                        URL url=new URL(path);
                        //(2)获取HttpURLConnection连接对象
                        HttpURLConnection conn=(HttpURLConnection) url.openConnection();
                        //(3)设置参数  发送get请求
                        conn.setRequestMethod("GET");//默认请求  就是get 要大写
                        //(4)设置连接网络的超时时间
                        conn.setConnectTimeout(5000);
                        
                        //(4.0)如果中间断过  继续上次的位置  继续下载  从文件钟读取上次下载的位置
                        File file=new File(getFilename(path)+threadId+".txt");
                        if(file.exists()&&file.length()>0){
                            FileInputStream fis=new FileInputStream(file);
                            BufferedReader bufr=new BufferedReader(new InputStreamReader(fis));
                            String lastPositionn = bufr.readLine();//读取出来的内容就是上一次下载的位置
                            int lastPosition=Integer.parseInt(lastPositionn);
                            
                            //(4.0.1)要改变一下  startIndex位置
                            startIndex=lastPosition+1;
                            
                            System.out.println("线程id:"+threadId+"真实下载的位置:"+startIndex+"----"+endIndex);
                            
                            fis.close();//关闭流
                        }
                        
                        
                        
                        //(4.1)设置一个请求头Range(作用告诉服务器每个线程下载的开始位置和结束位置)
                        conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
                        
                        //(5)获取服务器返回的状态码
                        int code = conn.getResponseCode();//200代表获取服务器资源全部成功 206请求部分资源  成功
                        if(code==206){
                            //6.创建随机读写文件对象
                            RandomAccessFile raf=new RandomAccessFile("pic.jpg", "rw");
                            //7.每个线程要从自己的位置开始写
                            raf.seek(startIndex);
                            
                            InputStream in=conn.getInputStream();
                            
                            //8.把数据写到文件中
                            int len=-1;
                            byte[] buffer=new byte[1024*1024];//1Mb
                            
                            int total=0;//代表当前线程下载的大小
                            
                            while((len=in.read(buffer))!=-1){
                                raf.write(buffer,0,len);
                                
                                total+=len;
                                //9.实现断点续传  就是把当前线程下载的位置  给存起来  下次再下载的时候  就是按照上次下载的位置继续下载  就可以了
                                int currentThreadPosition=startIndex+total;//比如就存到一个普通的.txt
                                
                                //10.用来存当前线程下载的位置
                                RandomAccessFile raff=new RandomAccessFile(getFilename(path)+threadId+".txt", "rwd");
                                raff.write(String.valueOf(currentThreadPosition).getBytes());
                                raff.close();
                                
                            }
                            raf.close();//关闭流  释放资源
                            System.out.println("线程id:"+threadId+"---下载完毕了");
                            
                            //11.把.txt文件删除  每个线程具体什么时候下载完毕了  我们不知道
                            synchronized (DownLoadThread.class) {
                                runningThread--;
                                if(runningThread==0){
                                    //说明所有的线程都执行完毕了  就把.txt文件删除
                                    for (int i = 0; i < threadCount; i++) {
                                        File deleteFile=new File(getFilename(path)+i+".txt");
                                        deleteFile.delete();
                                    }
                                }
                                
                            }
                            
                        }
                        
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    
                }
                
                
                
            }
            
            //获取文件的名字  "http://192.168.11.73:8080/pic.jpg"
            public static String getFilename(String path){
                int start=path.lastIndexOf("/")+1;
                return path.substring(start);
            }
            
        }
    
    

7.断点续传逻辑移植到Android上
    [1]权限: 
            <uses-permission android:name="android.permission.INTERNET"/>
            <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    [2]activity_main.xml代码如下:
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            tools:context=".MainActivity" >

            <EditText 
                android:id="@+id/et_path"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="http://192.168.10.98:8080/pic.jpg"
                android:hint="请输入下载路径"
                />
            
            <EditText 
                android:id="@+id/et_threadCount"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="请输入线程的数量"       
                />
            
            <Button 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="click"
                android:text="下载"
                />
            
            <LinearLayout 
                android:id="@+id/ll_pb"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                ></LinearLayout>
            
        </LinearLayout>
    [3]item.xml代码如下:
        <?xml version="1.0" encoding="utf-8"?>
        <ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/progressBar1"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
            
    [4]MainActivity代码如下:
        private EditText et_path;
        private EditText et_threadCount;
        private LinearLayout ll_pb_layout;
        private static int runningThread;//代表当前正在运行的线程
        private int threadCount;
        private List<ProgressBar> pbLists;//用来存进度条的引用
        private String path;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            //1.找到我们关心的控件
            et_path = (EditText) findViewById(R.id.et_path);
            et_threadCount = (EditText)findViewById(R.id.et_threadCount);
            ll_pb_layout = (LinearLayout) findViewById(R.id.ll_pb);
            
            //2.添加一个集合 用来存进度条的引用 
            pbLists = new ArrayList<ProgressBar>();
            
        }
        
        //点击按钮实现下载的逻辑
        public void click(View v){
            //2.获取下载的路径
            path = et_path.getText().toString().trim();
            //3.获取线程的数量
            String threadCountt=et_threadCount.getText().toString().trim();
            //3.0先移除进度条  再添加
            ll_pb_layout.removeAllViews();
            
            threadCount = Integer.parseInt(threadCountt);
            pbLists.clear();
            for (int i = 0; i < threadCount; i++) {
                //3.1把定义的item布局转换成一个view对象
                ProgressBar pbView = (ProgressBar) View.inflate(getApplicationContext(), R.layout.item, null);
                
                //3.2把pbView添加到集合钟
                pbLists.add(pbView);
                
                //4.动态的添加进度条
                ll_pb_layout.addView(pbView);
                
            }
            
            //5.开始移植  联网  获取文件长度
            new Thread(){
                public void run() {
                    //一 获取服务器文件的大小  要计算每个线程下载的开始位置和结束位置
                    try {
                        //(1)创建一个url对象  参数就是网址
                        URL url=new URL(path);
                        //(2)获取HttpURLConnection的连接对象
                        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                        //(3)设置参数  发送get请求
                        conn.setRequestMethod("GET");//默认请求  就是get  要大写
                        //(4)设置连接网络的超时时间
                        conn.setConnectTimeout(5000);
                        //(5)获取服务器返回的状态码
                        int code = conn.getResponseCode();//200 代表获取服务器资源全部成功 206请求部分资源
                        if(code==200){
                            //(6)获取服务器文件的大小
                            int length=conn.getContentLength();
                            
                            //(6.1)把线程的数量赋值给正在运行的线程
                            runningThread=threadCount;
                            
                            System.out.println("length:"+length);
                            
                            //二 创建一个大小和服务器一模一样的文件  目的是提前把空间申请出来
                            RandomAccessFile rafAccessFile=new RandomAccessFile(getFilename(path), "rw");
                            rafAccessFile.setLength(length);
                            
                            //(7)算出每个线程下载的大小
                            int blockSize=length/threadCount;
                            
                            //三  计算每个线程下载的开始位置和结束位置
                            for (int i = 0; i < threadCount; i++) {
                                int startIndex=i*blockSize;//每个线程下载的开始位置
                                int endIndex=(i+1)*blockSize-1;
                                //特殊情况  就是最后一个线程
                                if(i==threadCount-1){
                                    //说明是最后一个线程
                                    endIndex=length-1;
                                }
                                
                                System.out.println("线程id:"+i+"理论下载的位置:"+startIndex+"---"+endIndex);
                                
                                
                                //四 开启线程去服务器下载文件
                                DownLoadThread downLoadThread=new DownLoadThread(startIndex, endIndex, i);
                                downLoadThread.start();
                            }
                            
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                };
            }.start();
            
            
            
            
        }
        
        //定义线程去服务器下载文件
        private class DownLoadThread extends Thread{
            //通过构造方法把每个线程下载的开始位置和结束位置传递进来
            private int startIndex;
            private int endIndex;
            private int threadId;
            
            private int PbMaxSize;//代表当前线程下载的最大值
            //如果中断过  获取上次下载的位置
            private int pblastPosition;
            
            public DownLoadThread(int startIndex, int endIndex, int threadId) {
                this.startIndex = startIndex;
                this.endIndex = endIndex;
                this.threadId = threadId;
            }

            @Override
            public void run() {
                //四  实现去服务器下载文件的逻辑
                try {
                    //(0)计算当前进度条的最大值
                    PbMaxSize=endIndex-startIndex;
                    //(1)创建一个url对象  参数就是网址
                    URL url=new URL(path);
                    //(2)获取httpurlconnection连接对象
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    //(3)设置参数  发送get请求
                    conn.setRequestMethod("GET");//默认请求  就是get  要大写
                    //(4)设置连接网络的超时时间
                    conn.setConnectTimeout(5000);
                    
                    //4.0如果中间断过  继续上次的位置  继续下载  从文件钟读取上次下载的位置
                    File file=new File(getFilename(path)+threadId+".txt");
                    if(file.exists()&&file.length()>0){
                        FileInputStream fis = new FileInputStream(file);
                        BufferedReader bufr = new BufferedReader(new InputStreamReader(fis));
                        String lastPositionn = bufr.readLine();//读取出来的内容就是上一次下载的位置
                        int lastPosition=Integer.parseInt(lastPositionn);
                        
                        //4.0给我们定义的进度条位置 赋值
                        pblastPosition=lastPosition-startIndex;
                        
                        //4.0.1 要改变一下  startIndex位置
                        startIndex=lastPosition+1;
                        
                        System.out.println("线程id:"+threadId+"真实下载的位置:"+startIndex+"---"+endIndex);
                        
                        fis.close();//关闭流
                        
                    }
                    
                    //4.1设置一个请求头Range(作用告诉服务器每个线程下载的开始位置和结束位置)
                    conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
                    
                    //(5)获取服务器返回的状态码
                    int code = conn.getResponseCode();//200 代表获取服务器资源钱全部成功 206请求部分资源成功
                    if(code==206){
                        //(6)创建随机读取文件对象
                        RandomAccessFile raf=new RandomAccessFile(getFilename(path), "rw");
                        //(6)每个线程都要从自己的位置开始写
                        raf.seek(startIndex);
                        
                        InputStream in = conn.getInputStream();
                        
                        //(7)把数据写到文件中
                        int len=-1;
                        byte[] buffer=new byte[1024*1024];//1Mb
                        
                        int total=0;//代表当前线程下载的大小
                        
                        while((len=in.read(buffer))!=-1){
                            raf.write(buffer,0,len);
                            
                            total+=len;
                            //8.实现断点续传  就是把当前线程下载的位置  给存起来  下载再下载的时候 就是按照上次下载的位置继续下载  就可以了
                            int currentThreadPosition=startIndex+total;//比如就存到一个普通的.txt文件中
                            
                            //9.用来存当前线程下载的位置
                            RandomAccessFile raff=new RandomAccessFile(getFilename(path)+threadId+".txt", "rw");
                            raff.write(String.valueOf(currentThreadPosition).getBytes());
                            raff.close();
                            
                            //10.设置一下当前进度条的最大值 和 当前进度
                            pbLists.get(threadId).setMax(PbMaxSize);//设置进度条的最大值
                            pbLists.get(threadId).setProgress(pblastPosition+total);//设置当前进度条的当前进度
                        }
                        
                        raf.close();//关闭流  释放资源
                        
                        System.out.println("线程id:"+threadId+"---下载完毕了");
                        
                        //10.把.txt删除  每个线程具体什么时候下载完毕了  我们不知道
                        synchronized (DownLoadThread.class) {
                            runningThread--;
                            if(runningThread==0){
                                //说明所有的线程都执行完毕了  就把.txt文件删除
                                for (int i = 0; i < threadCount; i++) {
                                    File deleteFile=new File(getFilename(path)+i+".txt");
                                    deleteFile.delete();
                                }
                                
                            }
                            
                        }
                        
                        
                    }
                    
                    
                    
                } catch (Exception e) {
                    e.printStackTrace();
                }
                
            }
        }


        //获取文件的名字   "http://192.168.10.98:8080/pic.jpg"
        public String getFilename(String path){
            int start=path.lastIndexOf("/")+1;
            String substring=path.substring(start);
            
            String fileName=Environment.getExternalStorageDirectory().getPath()+"/"+substring;
            return fileName;
        }


8.开源项目实现多线程下载(xutils)
    先把com包  源码包拷贝到当前工程
    //点击按钮实现断点续传下载逻辑
    public void click(View v){
        //[1]获取下载路径
        String path = et_path.getText().toString().trim();
        //[2]创建httputils对象
        HttpUtils http=new HttpUtils();
        //[3]实现断点下载  target下载文件的路径  autoResume是否支持断点续传的逻辑 
        http.download(path, "/mnt/sdcard/xpg.mp3", true,  new RequestCallBack<File>() {
            
            //下载成功
            @Override
            public void onSuccess(ResponseInfo<File> responseInfo) {
                Toast.makeText(getApplicationContext(), "下载成功", 1).show();
                
            }
            
            @Override
            public void onLoading(long total, long current, boolean isUploading) {
                //total 代表总进度  current 代表当前进度
                pb.setMax((int)total);
                pb.setProgress((int)current);
            }
            
            //下载失败的回调
            @Override
            public void onFailure(HttpException error, String msg) {
                // TODO Auto-generated method stub
                
            }
        });
        
    }


9.今日总结
    [1]httpurlconnection用于发送或者接收数据
        get  组拼url地址把数据组拼到url
        post 要自己组拼正文  请求头信息
        
    [2]httpclient 了解
    
    [3]asynchttpclient
    
    [4]多线程下载
        1.获取文件大小
        2.在客户端创建一个大小和服务器一模一样的文件(为了提前申请空间)
        3.算出每个线程下载的开始位置和结束位置
            需要考虑 最后一个线程结束位置  文件长度-1
        4.开启一个线程去实现下载逻辑  httpurlconnection
            设置Range 还要注意服务器返回的状态码不是200,是206
        5.断点的逻辑  就是把当前线程下载的位置给保存起来 RandomAccessFile  直接同步到底层设备
        6.移植到Android  算出进度条的进度  集合存进度条的引用
        7.与进度相关的都可以在子线程直接更新ui
    
    
    


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值