Android中实现多线程断点下载

这是我的第一篇博客,写的不是很好请多多包涵

关于多线程断点下载对于新手而言呢可能比较的吃力,因为这个demo包涵的知识点还是比较多的,比较适合刚学Android的新手而言呢是比较好的一个知识串联.


     多线程断点下载主要分为四个步骤

1-->获取要下载文件的大小

2-->在手机本地腾出下载文件资源的空间

3-->把下载的资源文件根据自己的需求分成几段线程进行下载

4-->开始进行下载操作


下面呢我会根据这四部,分别发布不同的代码,以供大家去参考

首先是布局部分,布局的展示使用ListView进行展示,相信大家都会使用,那么我只展示ListView中子条目的布局

<RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <RelativeLayout
            android:id="@+id/down_re"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >

            <com.wangshuai.ImageTools.SmartImageView
                android:id="@+id/down_image"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:layout_marginLeft="30dp"
                android:src="@drawable/ic_launcher" >
            </com.wangshuai.ImageTools.SmartImageView>

            <TextView
                android:id="@+id/down_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginLeft="30dp"
                android:layout_toRightOf="@+id/down_image"
                android:text="@string/hello_world"
                android:textSize="20sp" />
        </RelativeLayout>

        <ProgressBar
            android:id="@+id/down_progress"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/down_re"
            android:layout_marginTop="18dp" />

        <TextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@+id/down_progress"
            android:layout_below="@+id/down_progress"
            android:layout_marginTop="25dp"
            android:text="当前下载的进度:"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/plan"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/down_progress"
            android:layout_marginLeft="20dp"
            android:layout_marginTop="25dp"
            android:layout_toRightOf="@+id/jindu"
            android:text="45"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/symbol"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/down_progress"
            android:layout_marginTop="25dp"
            android:layout_toRightOf="@+id/shuzhi"
            android:text="%"
            android:textSize="20sp" />

        <Button
            android:id="@+id/down_stop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignRight="@+id/down_progress"
            android:layout_below="@+id/down_progress"
            android:layout_marginTop="14dp"
            android:clickable="true"
            android:focusable="false"
            android:focusableInTouchMode="false"
            android:onClick="click2"
            android:text="停止" />

        <Button
            android:id="@+id/down_start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBaseline="@+id/down_stop"
            android:layout_alignBottom="@+id/down_stop"
            android:layout_marginRight="40dp"
            android:layout_toLeftOf="@+id/down_stop"
            android:clickable="true"
            android:focusable="false"
            android:focusableInTouchMode="false"
            android:onClick="click1"
            android:text="开始" />
    </RelativeLayout>

子布局很简单,不多说

两个实体类的创建  

 SoftwareClass这个实体类是展示在ListView中我们能看的到的

public class SoftwareClass implements Serializable{
private int id;    //每个下载软件的编号
private String name;   //软件的名称
private String url;      //软件的下载路径
private int length;    //软件的大小
private String size;
private String imageUrl;
private int finished;   //下载的进度
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public int getFinished() {
return finished;
}
public void setFinished(int finished) {
this.finished = finished;
}
@Override
public String toString() {
return "SoftwareClass [id=" + id + ", name=" + name + ", url=" + url
+ ", length=" + length + ", size=" + size + ", imageUrl="
+ imageUrl + ", finished=" + finished + "]";
}
public SoftwareClass(int id, String name, String url, int length,
String size, String imageUrl, int finished) {
super();
this.id = id;
this.name = name;
this.url = url;
this.length = length;
this.size = size;
this.imageUrl = imageUrl;
this.finished = finished;
}
public SoftwareClass() {
super();
// TODO Auto-generated constructor stub
}
}

注意ThreadClass这个类是线程类,是我们要执行下载操作的类

public class ThreadClass implements Serializable{
private int id;   //线程的ID
private String url;   //下载文件的路径
private int start;   //每条线程下载的起点位置
private int end;   //每条线程下载的终点位置
private int finished;    //下载的进度
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public int getFinished() {
return finished;
}
public void setFinished(int finished) {
this.finished = finished;
}
@Override
public String toString() {
return "ThreadClass [id=" + id + ", url=" + url + ", start=" + start
+ ", end=" + end + ", finished=" + finished + "]";
}
public ThreadClass(int id, String url, int start, int end, int finished) {
super();
this.id = id;
this.url = url;
this.start = start;
this.end = end;
this.finished = finished;
}
public ThreadClass() {
super();
// TODO Auto-generated constructor stub
}
}



创建数据库方便下载数据的保存,这个数据库必须是单例模式

public class MySQLite extends SQLiteOpenHelper{

public MySQLite(Context context) {
super(context, "Down.db", null, 1);
// TODO Auto-generated constructor stub
}

/*
* 由于一个文件是多条线程,所以该线程管理的数据库设定为单例模式
*/
private static MySQLite SQL=null;
public static MySQLite getSQL(Context context){
if(SQL==null){
synchronized (MySQLite.class) {
if(SQL==null){
SQL=new MySQLite(context);
}
}
}
return SQL;
}


@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL("create table mysql(_id integer primary key autoincrement,id integer,url char(30),start integer,end integer,finished integer)");
}


@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub

}
}


数据库中对应的方法

public class MySQLService {
private MySQLite SQL;
public MySQLService(Context conn){
SQL=MySQLite.getSQL(conn);
}

//获取每个文件的下载线程
public ArrayList<ThreadClass> getList(String url){
ArrayList<ThreadClass> list=new ArrayList<ThreadClass>();
SQLiteDatabase base=SQL.getReadableDatabase();
Cursor cur=base.rawQuery("select * from mysql where url=?", new String[]{url});
if(cur!=null&&cur.getCount()>0){
while(cur.moveToNext()){
ThreadClass thread=new ThreadClass();
String id=cur.getString(cur.getColumnIndex("id"));
String threadurl=cur.getString(cur.getColumnIndex("url"));
String start=cur.getString(cur.getColumnIndex("start"));
String end=cur.getString(cur.getColumnIndex("end"));
String finished=cur.getString(cur.getColumnIndex("finished"));
thread.setId(Integer.parseInt(id));
thread.setUrl(url);
thread.setStart(Integer.parseInt(start));
thread.setEnd(Integer.parseInt(end));
thread.setFinished(Integer.parseInt(finished));
list.add(thread);
}
}
//cur.close();
//base.close();
return list;
}

//添加线程信息
public synchronized  void insertThreadClass(ThreadClass thread){
SQLiteDatabase base=SQL.getWritableDatabase();
base.execSQL("insert into mysql(id,url,start,end,finished) values(?,?,?,?,?)", new Object[]{thread.getId(),thread.getUrl(),thread.getStart(),thread.getEnd(),thread.getFinished()});
//base.close();
}

//删除文件线程
public synchronized  void deleteThreadClass(String url){
SQLiteDatabase base=SQL.getWritableDatabase();
base.execSQL("delete from mysql where url=?", new Object[]{url});
//base.close();
}

//更改下载线程的下载进度
public synchronized  void updataThreadClass(int finished,int id,String url){
SQLiteDatabase base=SQL.getWritableDatabase();
base.execSQL("update mysql set finished=? where id=? and url=?", new Object[]{finished,id,url});
//base.close();
}

}




//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

正题到了

如果要执行下载肯定是要在服务中执行下载操作,在服务中创建一个类继承Thread

//执行下载的第一步:获取文件大小和在本地腾出相应的下载空间

public class MyService extends Service{
public static String START_ACTION="START_ACTION";  //开始启动标签动作
public static String STOP_ACTION="STOP_ACTION";  //开始启动标签动作
public static String UPDATA_ACTION="UPDATA_ACTION";  //下载进度的动作标签
public static String FINISHED_ACTION="FINISHED_ACTION";  //下载完成的动作标签
private String StringHttp;  //IP地址

//下载文件的管理集合
private Map<Integer,MyDownTask> maplist=new HashMap<Integer,MyDownTask>();
//文件下载的文件夹
public static String DOWN_FILE=Environment.getExternalStorageDirectory().getAbsolutePath()+"/SoftwareClassDownLoad/";

private Handler hand=new Handler(){
public void handleMessage(android.os.Message msg) {
SoftwareClass SS=(SoftwareClass) msg.obj;
MyDownTask ww=new MyDownTask(SS,MyService.this,StringHttp);
ww.downLoad();
//往下载集合添加下载的任务信息
maplist.put(SS.getId(), ww);
};
};

@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}

//开启服务
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
if("START_ACTION".equals(intent.getAction())){
SoftwareClass soft=(SoftwareClass) intent.getSerializableExtra("class");
StringHttp=intent.getStringExtra("StringHttp");
Log.i("RRRRRRRRRRRRRRRRRRRR",soft.toString());
DownLoadTask TT=new DownLoadTask(soft,StringHttp);
MyDownTask.sExecutorService.execute(TT);
}else if("STOP_ACTION".equals(intent.getAction())){
SoftwareClass soft=(SoftwareClass) intent.getSerializableExtra("class");
Log.i("RRRRRRRRRRRRRRRRRRRR",soft.toString());
MyDownTask ddd=maplist.get(soft.getId());
if(ddd!=null){
ddd.isDown=true;
}
}

return super.onStartCommand(intent, flags, startId);
}


//执行下载的第一步:获取文件大小和在本地腾出相应的下载空间
public class DownLoadTask extends Thread{
private SoftwareClass soft;
private String IP;  //IP地址
public DownLoadTask(SoftwareClass soft,String IP){
this.soft=soft;
this.IP=IP;
}
public void run(){
HttpURLConnection http=null;
RandomAccessFile access=null;
try {
/*
* 第一步获取下载文件的大小
*/
URL url=new URL(IP+soft.getUrl());
http=(HttpURLConnection) url.openConnection();
http.setRequestMethod("GET");
http.setReadTimeout(5000);

int length=-1;
int code=http.getResponseCode();
if(code==200){
length=http.getContentLength();
}
if(length<=0){
return;
}
soft.setLength(length);
/*
* 第二部在本地为下载的文件腾出空间
*/
File file=new File(DOWN_FILE);
if(!file.exists()){
file.mkdir();
}
//下载的文件路径
File downFile=new File(file,soft.getName());
access=new RandomAccessFile(downFile, "rwd");
access.setLength(length);
//把含有文件大小的SoftwareClass通过handler发送到handleMessage
Message msg=Message.obtain();
msg.obj=soft;
hand.sendMessage(msg);

} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
http.disconnect();   //断开连接
access.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}


}
注意:这里一定要在finally中执行http.disconnect()和access.close()否则下载时会造成下载的卡顿

对于文件下载接受到的资源保存到手机是用RandomAccessFile这个类,这个类的功能能够把下载的资源实时写入到指定的文件夹中,并且在下载暂停时,如果要接着下载,那么就要用到这个类,用到的方法

.setLength   该方法是在本地腾出下载文件的资源空间

        .write   该方法是把资源写入到指定位置跟OutPutStream的write方法一样

        .seek   该方法是暂停后能够在暂停后的位置接着下载

他的很多方法可以查看JAVA的jdk文档,这里我只用到了这三个方法

以上是下载步骤的前两步

看下后两步

后两步的执行操作,是放在了一个自定义的类中

public class MyDownTask {
private SoftwareClass soft;  //要下载的文件
private MySQLService sqlservice;  //线程数据库方法
private String IP;  //IP地址
private int mfinished=0;    //下载进度
public boolean isDown;   //下载暂停标签
private ArrayList<Task> taskList;  //管理下载线程

//线程池
public static ExecutorService sExecutorService=Executors.newCachedThreadPool();

private Context conn;
public MyDownTask(SoftwareClass soft,Context conn,String IP){
this.soft=soft;
this.conn=conn;
sqlservice=new MySQLService(conn);
this.IP=IP;
}

/*
* 第三步  为每条下载线程分配下载的区域范围
*/
public void downLoad(){
isDown=false;

ArrayList<ThreadClass> threadList=sqlservice.getList(IP+soft.getUrl());
if(threadList.size()==0){
int resources=soft.getLength()/3;
for(int i=0;i<3;i++){
int start=i*resources;
int end=(i+1)*resources-1;
ThreadClass threadclass=new ThreadClass(i,IP+soft.getUrl(),start,end,0);
if(i==2){


threadclass.setEnd(soft.getLength());
}


//把新添加的下载文件的下载线程信息添加到数据库中
sqlservice.insertThreadClass(threadclass);
threadList.add(threadclass);
}
}
//管理下载任务的集合
taskList=new ArrayList<Task>();
for(ThreadClass thread:threadList){
Task TT=new Task(thread);
MyDownTask.sExecutorService.execute(TT);   //用线程池启动线程
taskList.add(TT);
}
}

//判断每个文件下载是否完成
public synchronized void checkAllThreadFinished(){
boolean isAllFinished=true;
for(int i=0;i<taskList.size();i++){
if(taskList.get(i).isFinished==false){
isAllFinished=false;
break;
}
}
//如果下载完成就发送广播通知
if(isAllFinished){
sqlservice.deleteThreadClass(IP+soft.getUrl());
Intent finishIntent=new Intent(MyService.FINISHED_ACTION);
finishIntent.putExtra("name", soft.getName());   //下载文件的名称
finishIntent.putExtra("id", soft.getId());   //判断那个文件下载
conn.sendBroadcast(finishIntent);  //发送广播
}
}




//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.
/*
* 第四步  执行下载操作
*/
//下载操作就是对每条下载线程的下载资源的操作
public class Task extends Thread{
private ThreadClass threadclass;
private boolean isFinished=false;   //确认每条线程的下载任务是否完成


public Task(ThreadClass threadclass){
this.threadclass=threadclass;
}
//操作步骤
public void run(){
HttpURLConnection http=null;
RandomAccessFile access=null;
InputStream in=null;
try {
URL url=new URL(threadclass.getUrl());
http=(HttpURLConnection) url.openConnection();
http.setRequestMethod("GET");
http.setReadTimeout(5000);

int start=threadclass.getStart()+threadclass.getFinished();  //如果之前下载过就把该线程的开始位置和下载的资源相加
http.setRequestProperty("Range", "bytes="+start+"-"+threadclass.getEnd());
File file=new File(MyService.DOWN_FILE,soft.getName());  //下载文件的路径
access=new RandomAccessFile(file, "rwd");  //为暂停后的下载作标签
access.seek(start);   

mfinished=mfinished+threadclass.getFinished();   //之前若下载过,把之前的起源加起来
Intent intent2=new Intent(MyService.UPDATA_ACTION);  //用于下载进度更新数据发送


int code=http.getResponseCode();
if(code==206){
in=http.getInputStream();
int len=-1;
byte[] arr=new byte[1024*4];
long time=System.currentTimeMillis();  //当前的系统时间
while((len=in.read(arr))!=-1){
access.write(arr, 0, len);


threadclass.setFinished(threadclass.getFinished()+len);  //每条下载线程进度的更新
mfinished=mfinished+len;   //总的进度下载
//每隔1秒发送进度
if(System.currentTimeMillis()-time>1000){
time=System.currentTimeMillis();

intent2.putExtra("finished", mfinished*100/soft.getLength());  //进度更新
intent2.putExtra("id", soft.getId());   //判断那个文件下载
conn.sendBroadcast(intent2);   //发送广播
}

//如果标签为true直接把下载循环暂停
if(isDown){
sqlservice.updataThreadClass(threadclass.getFinished(), threadclass.getId(), threadclass.getUrl());
return;
}
}
//如果该线程下载完毕那么该标签就变成true
isFinished=true;
//监听每条线程下载资源是否完成
checkAllThreadFinished();
}
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
http.disconnect();    //断开连接
access.close();
in.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

}

}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值