Android开发多线程断点续传下载器

本文介绍了一款基于Android平台的多线程断点续传下载器的实现原理及具体实现过程。利用多线程并发提高下载效率,并通过数据库记录下载进度,支持断点续传功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://blog.youkuaiyun.com/furongkang/article/details/6838521


111
Android开发多线程断点续传下载器
分类: Android 
2011-10-01 23:14 931人阅读 评论(8收藏 举报

使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线、电量不足等情况下,这就需要使用到断点续传功能,下次启动时从记录位置继续下载,可避免重复部分的下载。这里采用数据库来记录下载的进度。

效果图

       

 

断点续传

1.断点续传需要在下载过程中记录每条线程的下载进度

2.每次下载开始之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库

3.在每次向文件中写入数据之后,在数据库中更新下载进度

4.下载完成之后删除数据库中下载记录

Handler传输数据

这个主要用来记录百分比,每下载一部分数据就通知主线程来记录时间

1.主线程中创建的View只能在主线程中修改,其他线程只能通过和主线程通信,在主线程中改变View数据

2.我们使用Handler可以处理这种需求

   主线程中创建Handler,重写handleMessage()方法

   新线程中使用Handler发送消息,主线程即可收到消息,并且执行handleMessage()方法

动态生成新View

可实现多任务下载

1.创建XML文件,将要生成的View配置好

2.获取系统服务LayoutInflater,用来生成新的View

   LayoutInflater inflater 
= (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);

3.使用inflate(int resource, ViewGroup root)方法生成新的View

4.调用当前页面中某个容器的addView,将新创建的View添加进来

示例

进度条样式 download.xml
[html] view plaincopy

    
<?xml version="1.0" encoding="utf-8"?>  
    
<LinearLayout   
        xmlns:android
="http://schemas.android.com/apk/res/android"  
        android:layout_width
="fill_parent"  
        android:layout_height
="wrap_content"  
        
>  
        
<LinearLayout   
            android:orientation
="vertical"  
            android:layout_width
="fill_parent"  
            android:layout_height
="wrap_content"  
            android:layout_weight
="1"  
            
>  
            
<!--进度条样式默认为圆形进度条,水平进度条需要配置style属性,  
            
?android:attr/progressBarStyleHorizontal -->  
            
<ProgressBar  
                android:layout_width
="fill_parent"   
                android:layout_height
="20dp"  
                style
="?android:attr/progressBarStyleHorizontal"  
                
/>  
            
<TextView  
                android:layout_width
="wrap_content"   
                android:layout_height
="wrap_content"  
                android:layout_gravity
="center"  
                android:text
="0%"  
                
/>  
        
</LinearLayout>  
        
<Button  
            android:layout_width
="40dp"  
            android:layout_height
="40dp"  
            android:onClick
="pause"  
            android:text
="||"  
            
/>  
    
</LinearLayout>  

顶部样式 main.xml
[html] view plaincopy

    
<?xml version="1.0" encoding="utf-8"?>  
    
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        android:orientation
="vertical"  
        android:layout_width
="fill_parent"  
        android:layout_height
="fill_parent"  
        android:id
="@+id/root"  
        
>  
        
<TextView    
            android:layout_width
="fill_parent"   
            android:layout_height
="wrap_content"   
            android:text
="请输入下载路径"  
            
/>  
        
<LinearLayout   
            android:layout_width
="fill_parent"  
            android:layout_height
="wrap_content"  
            android:layout_marginBottom
="30dp"  
            
>  
            
<EditText  
                android:id
="@+id/path"  
                android:layout_width
="fill_parent"   
                android:layout_height
="wrap_content"   
                android:singleLine
="true"  
                android:layout_weight
="1"  
                
/>  
            
<Button  
                android:layout_width
="wrap_content"   
                android:layout_height
="wrap_content"   
                android:text
="下载"  
                android:onClick
="download"  
                
/>  
        
</LinearLayout>  
    
</LinearLayout>  
       

MainActivity.java
[java] view plaincopy

    
public class MainActivity extends Activity  
        
private LayoutInflater inflater;  
        
private LinearLayout rootLinearLayout;  
        
private EditText pathEditText;  
      
        @Override  
        
public void onCreate(Bundle savedInstanceState)  
            
super.onCreate(savedInstanceState);  
            setContentView(R.layout.main);  
      
            
//动态生成新View,获取系统服务LayoutInflater,用来生成新的View  
            inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);  
            rootLinearLayout 
= (LinearLayout) findViewById(R.id.root);  
            pathEditText 
= (EditText) findViewById(R.id.path);  
      
            
// 窗体创建之后, 查询数据库是否有未完成任务, 如果有, 创建进度条等组件, 继续下载  
            List<String> list = new InfoDao(this).queryUndone();  
            
for (String path list)  
                createDownload(path);  
         
      
        
  
        
public void download(View view)  
            String path 
= "http://192.168.1.199:8080/14_Web/" + pathEditText.getText().toString();  
            createDownload(path);  
         
      
        
  
        
private void createDownload(String path)  
            
//获取系统服务LayoutInflater,用来生成新的View  
            LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);  
            LinearLayout linearLayout 
= (LinearLayout) inflater.inflate(R.layout.download, null);  
              
            LinearLayout childLinearLayout 
= (LinearLayout) linearLayout.getChildAt(0);  
            ProgressBar progressBar 
= (ProgressBar) childLinearLayout.getChildAt(0);  
            TextView textView 
= (TextView) childLinearLayout.getChildAt(1);  
            Button button 
= (Button) linearLayout.getChildAt(1);  
      
            
try  
                button.setOnClickListener(
new MyListener(progressBar, textView, path));  
                
//调用当前页面中某个容器的addView,将新创建的View添加进来  
                rootLinearLayout.addView(linearLayout);  
            
catch (Exception e)  
                e.printStackTrace();  
             
         
      
        
private final class MyListener implements OnClickListener  
            
private ProgressBar progressBar;  
            
private TextView textView;  
            
private int fileLen;  
            
private Downloader downloader;  
            
private String name;  
              
            
  
            
public MyListener(ProgressBar progressBar, TextView textView, String path)  
                
this.progressBar = progressBar;  
                
this.textView = textView;  
                name 
= path.substring(path.lastIndexOf("/"+ 1);  
      
                downloader 
= new Downloader(getApplicationContext(), handler);  
                
try  
                    downloader.download(path, 
3);  
                
catch (Exception e)  
                    e.printStackTrace();  
                    Toast.makeText(getApplicationContext(), 
"下载过程中出现异常"0).show();  
                    
throw new RuntimeException(e);  
                 
             
              
            
//Handler传输数据  
            private Handler handler = new Handler()  
                @Override  
                
public void handleMessage(Message msg)  
                    
switch (msg.what)  
                        
case 0 
                            
//获取文件的大小  
                            fileLen = msg.getData().getInt("fileLen");  
                            
//设置进度条最大刻度:setMax()  
                            progressBar.setMax(fileLen);  
                            
break 
                        
case 1 
                            
//获取当前下载的总量  
                            int done = msg.getData().getInt("done");  
                            
//当前进度的百分比  
                            textView.setText(name + "\t" + done * 100 / fileLen + "%");  
                            
//进度条设置当前进度:setProgress()  
                            progressBar.setProgress(done);  
                            
if (done == fileLen)  
                                Toast.makeText(getApplicationContext(), name 
+ " 下载完成"0).show();  
                                
//下载完成后退出进度条  
                                rootLinearLayout.removeView((View) progressBar.getParent().getParent());  
                             
                            
break 
                     
                 
            };  
      
            
  
            
public void onClick(View v)  
                Button pauseButton 
= (Button) v;  
                
if ("||".equals(pauseButton.getText()))  
                    downloader.pause();  
                    pauseButton.setText(
"");  
                
else  
                    downloader.resume();  
                    pauseButton.setText(
"||");  
                 
             
         
     



Downloader.java
[java] view plaincopy

    
public class Downloader  
      
        
private int done;  
        
private InfoDao dao;  
        
private int fileLen;  
        
private Handler handler;  
        
private boolean isPause;  
      
        
public Downloader(Context context, Handler handler)  
            dao 
= new InfoDao(context);  
            
this.handler = handler;  
         
        
  
        
public void download(String path, int thCount) throws Exception  
            URL url 
= new URL(path);  
            HttpURLConnection conn 
= (HttpURLConnection) url.openConnection();  
            
//设置超时时间  
            conn.setConnectTimeout(3000);  
            
if (conn.getResponseCode() == 200 
                fileLen 
= conn.getContentLength();  
                String name 
= path.substring(path.lastIndexOf("/"+ 1);  
                File file 
= new File(Environment.getExternalStorageDirectory(), name);  
                RandomAccessFile raf 
= new RandomAccessFile(file, "rws");  
                raf.setLength(fileLen);  
                raf.close();  
                  
                
//Handler发送消息,主线程接收消息,获取数据的长度  
                Message msg = new Message();  
                msg.what 
= 0 
                msg.getData().putInt(
"fileLen"fileLen);  
                handler.sendMessage(msg);  
                  
                
//计算每个线程下载的字节数  
                int partLen = (fileLen + thCount - 1/ thCount;  
                
for (int = 0< thCount; i++ 
                    
new DownloadThread(url, file, partLen, i).start();  
            
else  
                
throw new IllegalArgumentException("404 path: " + path);  
             
         
      
        
private final class DownloadThread extends Thread  
            
private URL url;  
            
private File file;  
            
private int partLen;  
            
private int id;  
      
            
public DownloadThread(URL url, File file, int partLen, int id)  
                
this.url = url;  
                
this.file = file;  
                
this.partLen = partLen;  
                
this.id = id;  
             
      
            
  
            
public void run()  
                
// 判断上次是否有未完成任务  
                Info info = dao.query(url.toString(), id);  
                
if (info != null 
                    
// 如果有, 读取当前线程已下载量  
                    done += info.getDone();  
                
else  
                    
// 如果没有, 则创建一个新记录存入  
                    info = new Info(url.toString(), id, 0);  
                    dao.insert(info);  
                 
      
                
int start = id * partLen + info.getDone(); // 开始位置 += 已下载量  
                int end = (id + 1* partLen - 1 
      
                
try  
                    HttpURLConnection conn 
= (HttpURLConnection) url.openConnection();  
                    conn.setReadTimeout(
3000);  
                    
//获取指定位置的数据,Range范围如果超出服务器上数据范围, 会以服务器数据末尾为准  
                    conn.setRequestProperty("Range""bytes=" + start + "-" + end);  
                    RandomAccessFile raf 
= new RandomAccessFile(file, "rws");  
                    raf.seek(start);  
                    
//开始读写数据  
                    InputStream in = conn.getInputStream();  
                    
byte[] buf = new byte[1024 * 10];  
                    
int len;  
                    
while ((len = in.read(buf)) != -1 
                        
if (isPause)  
                            
//使用线程锁锁定该线程  
                            synchronized (dao)  
                                
try  
                                    dao.wait();  
                                
catch (InterruptedException e)  
                                    e.printStackTrace();  
                                 
                             
                         
                        raf.write(buf, 
0len);  
                        done 
+= len;  
                        info.setDone(info.getDone() 
+ len);  
                        
// 记录每个线程已下载的数据量  
                        dao.update(info);   
                        
//新线程中用Handler发送消息,主线程接收消息  
                        Message msg = new Message();  
                        msg.what 
= 1 
                        msg.getData().putInt(
"done"done);  
                        handler.sendMessage(msg);  
                     
                    in.close();  
                    raf.close();  
                    
// 删除下载记录  
                    dao.deleteAll(info.getPath(), fileLen);   
                
catch (IOException e)  
                    e.printStackTrace();  
                 
             
         
      
        
//暂停下载  
        public void pause()  
            isPause 
= true 
         
        
//继续下载  
        public void resume()  
            isPause 
= false 
            
//恢复所有线程  
            synchronized (dao)  
                dao.notifyAll();  
             
         
     

 

 

Dao:

 

DBOpenHelper:
[java] view plaincopy

    
public class DBOpenHelper extends SQLiteOpenHelper  
      
        
public DBOpenHelper(Context context)  
            
super(context, "download.db"null1);  
         
      
        @Override  
        
public void onCreate(SQLiteDatabase db)  
            db.execSQL(
"CREATE TABLE info(path VARCHAR(1024), thid INTEGER, done INTEGER, PRIMARY KEY(path, thid))");  
         
      
        @Override  
        
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)  
         
      
     


InfoDao:
[java] view plaincopy

    
public class InfoDao  
        
private DBOpenHelper helper;  
      
        
public InfoDao(Context context)  
            helper 
= new DBOpenHelper(context);  
         
      
        
public void insert(Info info)  
            SQLiteDatabase db 
= helper.getWritableDatabase();  
            db.execSQL(
"INSERT INTO info(path, thid, done) VALUES(?, ?, ?)"new Object[] info.getPath(), info.getThid(), info.getDone() });  
         
      
        
public void delete(String path, int thid)  
            SQLiteDatabase db 
= helper.getWritableDatabase();  
            db.execSQL(
"DELETE FROM info WHERE path=? AND thid=?"new Object[] path, thid });  
         
      
        
public void update(Info info)  
            SQLiteDatabase db 
= helper.getWritableDatabase();  
            db.execSQL(
"UPDATE info SET done=? WHERE path=? AND thid=?"new Object[] info.getDone(), info.getPath(), info.getThid() });  
         
      
        
public Info query(String path, int thid)  
            SQLiteDatabase db 
= helper.getWritableDatabase();  
            Cursor 
= db.rawQuery("SELECT path, thid, done FROM info WHERE path=? AND thid=?"new String[] path, String.valueOf(thid) });  
            Info info 
= null 
            
if (c.moveToNext())  
                info 
= new Info(c.getString(0), c.getInt(1), c.getInt(2));  
            c.close();  
      
            
return info;  
         
      
        
public void deleteAll(String path, int len)  
            SQLiteDatabase db 
= helper.getWritableDatabase();  
            Cursor 
= db.rawQuery("SELECT SUM(done) FROM info WHERE path=?"new String[] path });  
            
if (c.moveToNext())  
                
int result = c.getInt(0);  
                
if (result == len)  
                    db.execSQL(
"DELETE FROM info WHERE path=? "new Object[] path });  
             
         
      
        
public List<String> queryUndone()  
            SQLiteDatabase db 
= helper.getWritableDatabase();  
            Cursor 
= db.rawQuery("SELECT DISTINCT path FROM info"null);  
            List
<String> pathList = new ArrayList<String>();  
            
while (c.moveToNext())  
                pathList.add(c.getString(
0));  
            c.close();  
            
return pathList;  
         
      
    }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值