Android多线程断点续传下载的实现

来源:互联网 发布:软件实施项目管理制度 编辑:程序博客网 时间:2024/05/08 17:30
Android多线程续传下载是Android应用中常见的功能。今天我们就来实现这样一个实例。实现效果如图:

结构如下图
功能结构
1)Activity向service传参数:将文件名,文件路径等传入service。
2)service 启动新的线程下载网络文件并存到本地。
3)向本地数据库写入下载的进度,以便确定下次下载的起始点。
4)broadcast 回传进度到Activity。
网络下载的关键点如下:
1)获取网络文件的长度
2)在本地创建文件,设置其长度和网络文件长度相同。
3)从上次下载的位置下载数据,同时保存进度到数据库。
4)将下载进度回传给Activity
开发过程如下:
(一)编写UI和实体类
文件类

public class FileInfo implements Serializable{  private int id;  private String url;  private String fileName;  private int length;  private int finished;  }

线程类

public class ThreadInfo {    private int id;    private String url;    private int start;    private int end;    private int finished;    }

(二)编写service获得Activity传入的参数

public class DownloadService extends Service{    public static final String ACTION_START ="ACTION_START";    public static final String ACTION_STOP="ACTION_STOP";    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        // 获得Activity传来的参数        if(ACTION_START.equals(intent.getAction()))        {            FileInfo fileInfo=(FileInfo)intent.getSerializableExtra("fileInfo");             Log.i("test ","start:"+fileInfo.toString());        }else if(ACTION_STOP.equals(intent.getAction())){            FileInfo fileInfo=(FileInfo)intent.getSerializableExtra("fileInfo");        }        return super.onStartCommand(intent, flags, startId);    }    @Override    public IBinder onBind(Intent arg0) {        // TODO Auto-generated method stub        return null;    }}

(四)在Activity中设置点击传递文件参数

final FileInfo fileInfo=new FileInfo(0, "http://101.4.136.35:9999/fw1.dl.wdjcdn.com/files/third/WanDouJiaSetup_a9.exe",                 "王纪坤大逗比", 0, 0);
mBtStart.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View view) {                // 通过intent,传递参数给service                Intent intent=new Intent(MainActivity.this,DownloadService.class);                intent.setAction(DownloadService.ACTION_START);                intent.putExtra("fileinfo", fileInfo);                startService(intent);               }        });

(五)在service中创建子线程,实现文件的下载
在子线程中,连接网络,获取网络文件长度。在本地目录创建相同长度的文件。并返回本地文件。

class InitThread extends Thread{        private FileInfo mFileInfo=null;        public InitThread(FileInfo mFileInfo)        {            this.mFileInfo=mFileInfo;               }        public void run(){            HttpURLConnection httpUrlConn = null;            RandomAccessFile raf = null;            try {                //1连接网络文件                URL url=new URL(mFileInfo.getUrl());                httpUrlConn=(HttpURLConnection)url.openConnection();                httpUrlConn.setConnectTimeout(3000);                httpUrlConn.setRequestMethod("GET");                int length=-1;                              if(httpUrlConn.getResponseCode()==HttpStatus.SC_OK)                {                    //2获得文件长度                    length=httpUrlConn.getContentLength();                }                if(length<=0)                {                    return ;                }else{                    //3在本地创建文件                    File dir=new File(DOWNLOAD_PATH);                    if(!dir.exists()){                        dir.mkdir();                    }                    File file=new File(dir,mFileInfo.getFileName());                    raf=new RandomAccessFile(file,"rwd");                    raf.setLength(length);                    mFileInfo.setLength(length);                    mHandler.obtainMessage(MSG_INIT,mFileInfo).sendToTarget();                }                //4设置本地文件长度            } catch (Exception e) {                // TODO: handle exception            }finally{                httpUrlConn.disconnect();                try {                    raf.close();                } catch (IOException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }            }        }    }

(六)创建数据库的连接和数据库访问接口
在数据库中保持下载进度
数据库的字段和线程信息相对应

 private static final String SQL_CREATE="create table thread_info(_id integer primary key autoincreament," +            "thread_id integer,url text, start integer, end integer ,finished integer)";

定义数据库接口

public interface ThreadDAO {    /*     *插入线程信息     */   public void insertThread(ThreadInfo threadInfo);   /*    * 删除线程信息    */   public void deleteThread(String url,int thread_id);   /*    * 更新线程的下载进度    */   public void updateThread(String url,int thread_id,int finished);   /*    * 查询文件的线程信息,以集合的形式返回    */   public List<Thread> getThreads(String url);   /*    * 判断线程的信息是否存在    */   public boolean isExists(String url,int thread_id);}

实现数据库接口

public class ThreadDAOImpl implements ThreadDAO{    private DBhelper dbHelper;    public ThreadDAOImpl(Context context){        dbHelper=new DBhelper(context);    }    @Override    public void insertThread(ThreadInfo threadInfo) {        // TODO Auto-generated method stub        SQLiteDatabase db=dbHelper.getWritableDatabase();        db.execSQL("insert into thread_info(thread_id,url,start,end,finished)values(?,?,?,?,?)",                new Object[]{threadInfo.getId(),threadInfo.getUrl(),threadInfo.getStart(),threadInfo.getEnd(),threadInfo.getFinished()});        db.close();    }    @Override    public void deleteThread(String url, int thread_id) {        // TODO Auto-generated method stub        SQLiteDatabase database=dbHelper.getWritableDatabase();        database.execSQL("delete from thread_info where url="+url+"and thread_id="+thread_id);        database.close();    }    @Override    public void updateThread(String url, int thread_id, int finished) {        // TODO Auto-generated method stub        SQLiteDatabase db=dbHelper.getWritableDatabase();        db.execSQL("update thread_info set finished=? where url= ?and thread_id=?", new Object[]{finished,url,thread_id});    }    @Override    public List<ThreadInfo> getThreads(String url) {        // TODO Auto-generated method stub        List<ThreadInfo> list=new ArrayList<ThreadInfo>();        SQLiteDatabase db=dbHelper.getWritableDatabase();        Cursor cursor=db.rawQuery("select * from thread_info where url=?", new String[]{url});        while(cursor.moveToNext()){            ThreadInfo thread=new ThreadInfo();            thread.setId(cursor.getInt(cursor.getColumnIndex("thread_id")));            thread.setUrl(cursor.getString(cursor.getColumnIndex("url")));            thread.setStart(cursor.getInt(cursor.getColumnIndex("start")));            thread.setEnd(cursor.getInt(cursor.getColumnIndex("end")));            thread.setFinished(cursor.getInt(cursor.getColumnIndex("finished")));            list.add(thread);        }        db.close();        return list;    }    @Override    public boolean isExists(String url,int thread_id) {        // TODO Auto-generated method stub        SQLiteDatabase db=dbHelper.getWritableDatabase();        Cursor cursor=db.rawQuery("select * from thread_info where url=? and thread_id=?", new String[]{url,thread_id+""});        boolean exists=cursor.moveToNext();        cursor.close();        db.close();        return exists;    }}

(七)定义下载任务类

public class DownloadTask {   private Context mContext=null;   private FileInfo mFileInfo=null;   private ThreadDAO mDao=null;   private int mFinished=0;   public boolean isPaused=false;   HttpURLConnection conn=null;   RandomAccessFile raf=null;   InputStream input=null;   public DownloadTask(Context context,FileInfo fileInfo)   {       this.mContext=context;       this.mFileInfo=fileInfo;       mDao=new ThreadDAOImpl(mContext);   }   public void download(){       //读取数据库的线程信息       List<ThreadInfo> threadInfos= mDao.getThreads(mFileInfo.getUrl());       ThreadInfo threadInfo=null;       if(threadInfos.size()==0){           //初始化县城信息对象           threadInfo=new ThreadInfo(0, mFileInfo.getUrl(), 0, mFileInfo.getLength(), 0);       }else{           threadInfo=threadInfos.get(0);       }       new DownloadThread(threadInfo).start();   }   /**    * 下载线程    */   class DownloadThread extends Thread{       private ThreadInfo mThreadInfo=null;    public DownloadThread(ThreadInfo mThreadInfo) {        super();        this.mThreadInfo = mThreadInfo;    }    public void run(){        //向数据库插入线程信息        if(!mDao.isExists(mThreadInfo.getUrl(), mThreadInfo.getId())){            mDao.insertThread(mThreadInfo);        }        try {            URL url=new URL(mThreadInfo.getUrl());            conn=(HttpURLConnection) url.openConnection();            conn.setConnectTimeout(3000);            conn.setRequestMethod("GET");            //设置下载位置            int start=mThreadInfo.getStart()+mThreadInfo.getFinished();            conn.setRequestProperty("Range", "bytes="+start+"-"+mThreadInfo.getEnd());            //设置文件写入位置            File file=new File(DownloadService.DOWNLOAD_PATH, mFileInfo.getFileName());            raf=new RandomAccessFile(file, "rwd");            raf.seek(start);            Intent intent=new Intent(DownloadService.ACTION_UPDATE);            mFinished+=mThreadInfo.getFinished();            //开始下载            if(conn.getResponseCode()==HttpStatus.SC_OK){                //读取数据                input=conn.getInputStream();                byte[] buffer=new byte[1024*4];                int len=-1;                long time=System.currentTimeMillis();                while((len=input.read(buffer))!=-1){                    //写入文件                    raf.write(buffer,0,len);                    //把下载进度发送广播给Activity                    mFinished+=len;                    if(System.currentTimeMillis()-time>500){                        time=System.currentTimeMillis();                        intent.putExtra("finished", mFinished*100/mFileInfo.getLength());                        mContext.sendBroadcast(intent);                    }                    //在下载暂停时,保存下载进度                    if(isPaused){                        mDao.updateThread(mThreadInfo.getUrl(), mThreadInfo.getId(), mThreadInfo.getFinished());                        return;                    }                }                //删除线程信息                mDao.deleteThread(mThreadInfo.getUrl(), mThreadInfo.getId());            }        } catch (Exception e) {            // TODO Auto-generated catch block            e.printStackTrace();        }finally{            conn.disconnect();            try {                raf.close();                input.close();            } catch (IOException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }    }   }}
0 0