安卓客户端的多线程断点下载(SharedPreferences版)

来源:互联网 发布:基尼系数的算法 编辑:程序博客网 时间:2024/04/29 16:46

题记:从百度百科上面我们知道,SharedPreferences是不支持多线程的,但是这次使用SharedPreferences实现了多线程断点下载。点解?


服务器端:

使用的是tomcat服务器,在C:\apache-tomcat-7.0.59\webapps\ROOT目录下存放pp.zip文件(这个文件随便,但是要跟代码中url的path后面的参数对应)开启tomcat服务器,先用浏览器访问下,看浏览器能不能提示下载或者直接在浏览器界面显示。可以的话再执行下面的。

布局文件如下:

<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:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    android:orientation="vertical" >    <TextView        android:id="@+id/tv_content"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:textSize="25dp"        android:text="pp.zip文件,快来下载吧" />      <EditText        android:id="@+id/et_threadtotal"        android:layout_width="wrap_content"        android:hint="请输入线程数量"        android:layout_height="wrap_content" />    <Button         android:onClick="click"        android:id="@+id/btn"        android:layout_margin="10dp"          android:text="点我下载"         android:layout_width="match_parent"        android:layout_height="wrap_content"/>    <ProgressBar        android:id="@+id/pb"        android:max="100"        style="?android:attr/progressBarStyleHorizontal"        android:layout_width="match_parent"        android:layout_height="15dp"      />    <TextView        android:id="@+id/tv_number"        android:layout_margin="5dp"        android:text="显示进度"        android:layout_width="match_parent"        android:layout_height="wrap_content"/></LinearLayout >

MainActivity代码如下:

package com.example.downloader;import java.io.BufferedInputStream;import java.io.File;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.app.Activity;import android.content.SharedPreferences;import android.content.SharedPreferences.Editor;import android.text.TextUtils;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.ProgressBar;import android.widget.TextView;import android.widget.Toast;public class MainActivity extends Activity {    private EditText et;    private static long length;    private static File file;    private static String path;    private static ProgressBar pb;    private static long reallength;    private static SharedPreferences sp;    private static TextView tv_number;    private static long reallengthrecord=0;    private static int number;    private static Button btn;    private static Handler handler=new Handler(){        public void handleMessage(android.os.Message msg) {            switch (msg.what) {            case 0://reallengthrecord是从sp中获取的,让进度条续加,如果sp文件没能获取到这个值,默认就是0;                long data=(Long) msg.obj;                int result=(int)(data*100/length);//进度条的最大值已经在布局文件中设置为100了//这里有个坑,setProgress()和setText()里面的参数一定要匹配,要传字符串的却传int值,错误无提示的哦,只会运行时报Resources$NotFoundException: String resource ID #0x0                pb.setProgress(result);                if(pb.getProgress()==100){//进度到了100%,按钮设置为不可点击                    btn.setEnabled(false);                }                tv_number.setText(result+"%");                break;            default:                break;            }        };    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        et = (EditText)findViewById(R.id.et_threadtotal);        btn = (Button)findViewById(R.id.btn);        pb = (ProgressBar)findViewById(R.id.pb);        tv_number = (TextView)findViewById(R.id.tv_number);//这里路径写死,一访问服务器,就可以获得下载        path = "http://192.168.1.109:8080/pp.zip";        new Thread(){            public void run() {                try {                    URL url=new URL(path);                    HttpURLConnection conn=(HttpURLConnection) url.openConnection();                    conn.setRequestMethod("GET");                    length = conn.getContentLength();//文件名可以从path中获得,访问网络获取文件的大小,第一次的话创建一个与目标文件大小名称一致的空文件                    String file_name=path.substring(path.lastIndexOf("/")+1);                    file = new File(Environment.getExternalStorageDirectory(),file_name);                    if(!file.exists()){                        RandomAccessFile rf=new RandomAccessFile(file, "rwd");                        rf.setLength((long) length);//子线程中土司可以使用Looper.prepare()和Looper.loop();子线程土司的数量不要超过50                        Looper.prepare();                        Toast.makeText(MainActivity.this, "创建文件成功", 0).show();                        Looper.loop();                    }                } catch (Exception e) {                    Looper.prepare();                    Toast.makeText(MainActivity.this, "创建文件失败", 0).show();                    Looper.loop();                }            };        }.start();//使用sp回显,线程数量,如果用户不输入,默认为空,吐司提示需要输入线程//生成的sp文件名是跟下载的文件名是对应的        String str=path.substring(path.lastIndexOf("/")+1,path.lastIndexOf("."));        sp=getSharedPreferences(str, 0);        String threadcount=sp.getString("number","");        int progress_result=sp.getInt("progress_result", 0);        reallengthrecord=sp.getLong("reallength", 0);        tv_number.setText(progress_result+"%");        pb.setProgress(progress_result);        et.setText(threadcount);    }    public void click(View v){//在做下载前,先判断SD卡是否处于挂载,然后再判断sd卡剩余容量,至少给用户留出100M的空间        String status=Environment.getExternalStorageState();        if(Environment.MEDIA_MOUNTED.equals(status)){            long freespace=Environment.getExternalStorageDirectory().getFreeSpace();            if(freespace>(file.length()+1024*1024*100)){                String threadcount=et.getText().toString().trim();                if(TextUtils.isEmpty(threadcount)){                    Toast.makeText(MainActivity.this, "需要输入线程数量呢", 0).show();                    return;                }else{                          number = Integer.parseInt(et.getText().toString().trim());                }//这里每个线程下载数量分配得很好(length/number)+1,这种情况见于(总大小15分4个线程来搞,//每个大小如果按照length/number来算,3,3,3,6,显然不好,应该是4,4,4,3)                long blockSize=length%number==0?length/number:((length/number)+1);                System.out.println(blockSize);                for (int threadId = 0; threadId < number; threadId++) {                    long startIndex = threadId*blockSize;                    long endIndex;                    if(threadId==number-1){                        endIndex = length-1;                    }else{                       endIndex = (threadId+1)*blockSize-1;                    }//启动线程去下载,这样写的好处是让线程ID,起始结束位置跟线程捆绑,使用多线程断点下载必须这样,如果只是多线程下载,可以不用这样                    new DownloadThread(threadId, startIndex, endIndex).start();                }            }else{//内存不足,会有提示                Toast.makeText(getApplicationContext(), "sd卡的内存不足", 0).show();            }        }    }        private static class DownloadThread extends Thread{            private int threadId;            private long  startIndex;            private long  endIndex;            private long curlength;            private File file2;           public DownloadThread(int threadId, long startIndex, long endIndex) {                this.threadId = threadId;                this.startIndex = startIndex;                this.endIndex = endIndex;//this.curlength = startIndex;这行代码的关键作用不解释                this.curlength = startIndex;            }           @Override        public void run() {               System.out.println("第 " + threadId+" 号 线程开始下载了 ,  下载 : " + startIndex+"~"+endIndex);            try{            URL url = new URL(path);            HttpURLConnection conn =(HttpURLConnection) url.openConnection();//判断sp文件是否存在,存在就读取里面保存的每个线程跑了的进度,请求数据以该进度开始,以及设置RandomAccessFile开始写的位置            String str2="/data/data/com.example.downloader/shared_prefs";               file2 = new File(str2,path.substring(path.lastIndexOf("/")+1                                    ,path.lastIndexOf("."))+".xml");            RandomAccessFile rf=new RandomAccessFile(file, "rwd");            if(file2!=null&&file2.length()>0){                long recordlength=sp.getLong(threadId+"curlength",0);                curlength=recordlength;                conn.setRequestProperty("Range", "bytes="+curlength+"-"+endIndex);                System.out.println("bytes="+curlength+"-"+endIndex+"aaaaaaaaa");                rf.seek(curlength);            }else{//sp文件不存在,就从默认的开始位置请求数据                conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);                rf.seek(startIndex);                }                int code = conn.getResponseCode();                System.out.println("code :" + code);//请求的数据Range如果范围越界或者不存在,则请求码为416,206表示请求部分数据成功                if(code==206){                    BufferedInputStream in = new BufferedInputStream(conn.getInputStream());                    int len=0;                    byte[] buf = new byte[1024*1024];                    while((len=in.read(buf))>0){                        rf.write(buf, 0, len);//curlength是该内部类 的成员变量,reallength是MainActivity的成员变量//curlength是该内部类 的成员变量可用于统计每个线程跑的进度//reallength可以记录所有的线程跑得进度,但不能确定是否为实际下载的进度(如果不断点那就是实际下载的进度,断点就不是实际下载的进度)//程序运行一次,reallength就会记录,如果关闭再打开,reallength又重新开始记录,所以需要我们自己给它续加进度,才是文件下载的最最真实的进度                        curlength+=len;                        reallength+=len;//sp文件的生成也是很即时的,在while循环中只要本地文件一写入数据,这里sp文件就可以即时生成,保存数据,数据即时更新(有时生成的sp文件还有bak备份文件)//reallengthrecord是MainActivity的成员变量,这是续加续加续加的问题                    int progress_result=(int) (((reallength+reallengthrecord)*100)/length);                    Editor edit=sp.edit();                    edit.putInt("progress_result",progress_result);                    edit.putLong(threadId+"curlength",curlength);                    edit.putString("number", number+"");                    edit.putLong("reallength",reallength+reallengthrecord);                    edit.commit();//handler需要传入reallength+reallengthrecord(续加,第一次没有这个,    //  就是0,关掉程序再打开界面续传就要加上sp文件里面保存的之前的下载的真实进度)                    Message msg=Message.obtain();                    msg.obj=reallength+reallengthrecord;                    msg.what=0;                    handler.sendMessage(msg);                }//关流是必须的,关流是必须的,关流是必须的,还有rf的模式设置为rwd,不然下载到99%就不动了,那就悲剧了                    in.close();                    rf.close();                }                }catch(Exception e){                    System.out.println("下载失败");                }//线程下载完就该把sp文件给删了                System.out.println("第 " + threadId+"线程下载完了 ...");                file2.delete();          }        }    }

界面:

这里写图片描述

问题1.:我下载1.2G文件的无压力,但是下载2.63G的文件,在创建文件的时候就失败了,难道是随机访问流在创建文件有大小限制?
问题2.:SharedPreferences不支持多线程如何理解,是在哪一种多线程环境下SharedPreferences不支持?

跪求大神指导

源码下载链接:
链接:http://pan.baidu.com/s/1qXSERLU 密码:rw73

1 0
原创粉丝点击