Android原生实现多线程断点下载实例代码

各位父老乡亲,我单汉三又回来了,今天为大家带来一个用原生的安卓写的多线程断点下载Demo。

通过本文你可以学习到:

  1. SQLite的基本使用,数据库的增删改查。
  2. Handler的消息处理与更新UI。
  3. Service(主要用于下载)的进阶与使用。
  4. 原生的json文件解析(多层嵌套)。
  5. RandomAccessFile的基本使用,可以将文件分段。
  6. 基于HttpURLConnection的大文件下载。
  7. 上面内容结合,实现多线程,断点下载。

Demo是在TV上运行的,图片显示的问题不要纠结了。

文件下载的Demo已完成,没时间上传与讲解,今天为您展示并讲解一下,纯原生的东西来下载文件,希望可以帮你理解更多安卓比较基础的问题。

我们的思路:建立一个数据库,两个表,一个用来保存网络数据,一个保存本地下载的进度等等。在点击下载按钮的时候启动DownloadService,进行比对之后下载

先看一下Demo的目录结构:

所有的步骤在代码里有非常详细的讲解,一定要看代码(下面是抽取的几个重要的类讲解)!

数据库的建立与DAO

/**
 * Created by Administrator on 2017/3/6 0006.
 */
public class DownLoadDBHelper extends SQLiteOpenHelper {
  /**
   * DownLoadDBHelper用于创建数据库,如果不会使用原生的建库的话
   *
   * 跟随小司机我的脚步来一起练一练
   * 建两个表:
   * download_info表存储下载信息
   * localdownload_info表存储本地下载信息
   * 之后对比两个表进行继续下载等等
   */
  public static String DATABASE_NAME = "downloadFILES.db";
  public static String TABLE_DOWNLOAD_INFO = "download_info";
  public static String TABLE_LOCALDOWNLOAD_INFO = "localdownload_info";
  private static int version = 1;
  public DownLoadDBHelper(Context context) {
    super(context,DATABASE_NAME,null,version);
  }
  @Override
  public void onCreate(SQLiteDatabase db) {
    /*在此进行创建数据库和表格,来一起动手写一遍,就是两个sqlite语句*/
    db.execSQL("create table " + TABLE_DOWNLOAD_INFO + "(" + "id integer PRIMARY KEY AUTOINCREMENT," +
        "thread_id integer," + "start_position integer," + "end_position integer," + " completed_size integer," + "url varchar(100))");
    db.execSQL("create table " + TABLE_LOCALDOWNLOAD_INFO + "(" + "id integer PRIMARY KEY AUTOINCREMENT," + "name varchar(50)," +
        "url varchar(100)," + "completedSize integer," + "fileSize integer," + "status integer)");
  }
  @Override
  public void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion) {
    /*数据库更新升级,检测到版本的变化,发现版本号不一样,就会自动调用onUpgrade函数
    * 新版本号和老版本号都会作为onUpgrade函数的参数传进来,便于开发者知道数据库应该从哪个版本升级到哪个版本。
    * */
    String sql = "drop table if exists " + TABLE_DOWNLOAD_INFO + "";
    String sqlOne = "drop table if exists " + TABLE_LOCALDOWNLOAD_INFO + "";
    db.execSQL(sql);
    db.execSQL(sqlOne);
    onCreate(db);//删除数据库,重新创建。这里只是简单的,并没有添加或者减少数据库中的其他字段
  }
}

DAO对数据库进行增删改查

/**
 * Created by ShanCanCan on 2017/3/6 0006.
 */
public class Dao {
  /*
  * DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此。
  * DAO层所定义的接口里的方法都大同小异,这是由我们在DAO层对数据库访问的操作来决定的,
  * 对数据库的操作,我们基本要用到的就是新增,更新,删除,查询等方法。
  * 因而DAO层里面基本上都应该要涵盖这些方法对应的操作。
  * */
  private static Dao dao;
  private static DownLoadDBHelper dbHelper;
  public static final byte[] Lock = new byte[0]; //新建两个字节作为对象锁
  public static final byte[] file_Lock = new byte[0];
  public Dao() {//空构造方法,
  }
  public static synchronized Dao getInstance(Context context) {//本demo用单例模式中的懒汉模式+线程不安全 线程安全的代价是效率变低
    if (dao == null) {
      dao = new Dao();
      dbHelper = new DownLoadDBHelper(context);
    }
    return dao;
  }
  /* public static synchronized Dao getInstance(Context context) {//本demo用单例模式中的懒汉模式+线程安全 线程安全的代价是效率变低,99%情况下不需要同步
    if (dao == null) { //你可以在这两个方法中随便选择一个
      dao = new Dao();
      dbHelper = new DownLoadDBHelper(context);
    }
    return dao;
  }*/
  /***************************************  下方Dao层中对数据库的增、删、改、查  *********************************************************/
  /**
   * 检查本地下载记录,是否下载过
   *
   * @param url
   * @return
   */
  public boolean isExist(String url) {
    SQLiteDatabase database = dbHelper.getReadableDatabase(); //获取本app所创建的数据库
    String sql = "select count(*) from " + TABLE_LOCALDOWNLOAD_INFO + " where url=?"; //查询语句,查询总共有多少条的语句
    Cursor cursor = database.rawQuery(sql,new String[]{url});
    /**
     *
     * @Cursor
     * Cursor 是每行的集合。
     * 使用 moveToFirst() 定位第一行。
     * 你必须知道每一列的名称。
     * 你必须知道每一列的数据类型。
     * Cursor 是一个随机的数据源。
     * 所有的数据都是通过下标取得。
     * Cursor按照我的理解就是一个箭头,指到哪一行就是那一行的集合
     * 比较重要的方法有:close(),moveToFirst(),moveToNext(),moveToLast(),moveToPrevious(),getColumnCount()等。
     *
     * @rawQuery
     * rawQuery是直接使用SQL语句进行查询的,也就是第一个参数字符串,
     * 在字符串内的“?”会被后面的String[]数组逐一对换掉
     * cursor用完之后要关闭,cursor用完之后要关闭,cursor用完之后要关闭。重要的事情说三遍!!!
     *
     * */
    cursor.moveToFirst();
    int count = cursor.getInt(0);
    cursor.close();
    return count > 0;
  }
  /**
   * 是否为首次下载
   *
   * @param url
   * @return
   */
  public boolean isFirstDownload(String url) {
    SQLiteDatabase database = dbHelper.getReadableDatabase();
    String sql = "select count(*) from " + TABLE_DOWNLOAD_INFO + " where url=?";
    Cursor cursor = database.rawQuery(sql,new String[]{url});
    cursor.moveToFirst();
    int count = cursor.getInt(0);
    cursor.close();
    return count == 0;
  }
  /**
   * 保存下载的具体信息 保存所下载的list集合中的数据
   *
   * @param infos
   * @param context
   */
  public void saveInfos(List<DownLoadInfo> infos,Context context) {
    /**
     * 事务(Transaction)是并发控制的单位,是用户定义的一个操作序列。
     * 这些操作要么都做,要么都不做,是一个不可分割的工作单位。
     * 通过事务,SQL Server能将逻辑相关的一组操作绑定在一起,
     * 以便保持数据的完整性。
     *
     * 事务具有四个特征:原子性( Atomicity )、一致性( Consistency )、
     * 隔离性( Isolation )和持续性( Durability )。这四个特性简称为 ACID 特性。
     *
     * */
    synchronized (Lock) {
      SQLiteDatabase database = dbHelper.getWritableDatabase();
      database.beginTransaction();//开启事务
      try {//如果有异常,在这里捕获
        for (DownLoadInfo info : infos) {//for循环将数据存入数据库
          String sql = "insert into " + TABLE_DOWNLOAD_INFO + "(thread_id,start_position,end_position,completed_size,url) values (?,?,?)";
          Object[] bindArgs = {info.getThreadId(),info.getStartPosition(),info.getEndPosition(),info.getCompletedSize(),info.getUrl()};
          database.execSQL(sql,bindArgs);
        }
        database.setTransactionSuccessful();//结束事务
      } catch (SQLException e) {
        e.printStackTrace();
      } finally {
        database.endTransaction();//关闭事务
      }
    }
  }
  /**
   * 得到下载具体信息
   *
   * @param urlstr
   * @return List<DownloadInfo> 一个下载器信息集合器,里面存放了每条线程的下载信息
   */
  public List<DownLoadInfo> getInfos(String urlstr) {
    List<DownLoadInfo> list = new ArrayList<DownLoadInfo>();
    SQLiteDatabase database = dbHelper.getReadableDatabase();
    String sql = "select thread_id,url from " + TABLE_DOWNLOAD_INFO + " where url=?";
    Cursor cursor = database.rawQuery(sql,new String[]{urlstr});
    while (cursor.moveToNext()) {//通过cursor取到下载器信息,循环遍历,得到下载器集合
      DownLoadInfo info = new DownLoadInfo(cursor.getInt(0),cursor.getInt(1),cursor.getInt(2),cursor.getInt(3),cursor.getString(4));
      list.add(info);
    }
    cursor.close();
    return list;
  }
  /**
   * 本地下载列表添加记录,添加本地数据库信息,完成度等等
   *
   * @param fileStatus
   **/
  public void insertFileStatus(FileStatus fileStatus) {
    synchronized (file_Lock) {//异步加开启事务,保证数据的完整性
      SQLiteDatabase database = dbHelper.getWritableDatabase();
      database.beginTransaction();
      try {
        String sql = "insert into " + TABLE_LOCALDOWNLOAD_INFO + " (name,url,completedSize,fileSize,status) values(?,?)";
        Object[] bindArgs = {fileStatus.getName(),fileStatus.getUrl(),fileStatus.getCompletedSize(),fileStatus.getFileSize(),fileStatus.getStatus()};
        database.execSQL(sql,bindArgs);
        database.setTransactionSuccessful();
      } catch (SQLException e) {
        e.printStackTrace();
      } finally {
        database.endTransaction();
      }
    }
  }
  /**
   * @param context
   * @param compeletedSize
   * @param threadId
   * @param urlstr     这里是更新数据库,建议在保存一个表格的时候就对另一个表格数据库进行更新
   */
  public void updataInfos(int threadId,int compeletedSize,String urlstr,Context context) {
    synchronized (Lock) {
      String sql = "update " + TABLE_DOWNLOAD_INFO + "set completed_size = ? where thread_id =? and url=?";
      String localSql = "update " + TABLE_LOCALDOWNLOAD_INFO + "set completedSize = (select sum(completed_size) from " +
          TABLE_DOWNLOAD_INFO + "where url=? group by url ) where url=?";
      Object[] bindArgs = {compeletedSize,threadId,urlstr};
      Object[] localArgs = {urlstr,urlstr};
      SQLiteDatabase database = dbHelper.getWritableDatabase();
      database.beginTransaction();
      try {
        database.execSQL(sql,bindArgs);
        database.execSQL(localSql,localArgs);
        database.setTransactionSuccessful();
      } catch (SQLException e) {
        e.printStackTrace();
      } finally {
        database.endTransaction();
      }
    }
  }
  /**
   * @param url 更新文件的状态,0为正在下载,1为已经下载完成,2为下载出错
   **/
  public void updateFileStatus(String url) {
    synchronized (file_Lock) {
      String sql = "update " + TABLE_LOCALDOWNLOAD_INFO + " set status = ? where url = ?";
      Object[] bindArgs = {1,url};
      SQLiteDatabase database = dbHelper.getWritableDatabase();
      database.beginTransaction();
      try {
        database.execSQL(sql,bindArgs);
        database.setTransactionSuccessful();
      } catch (SQLException e) {
        e.printStackTrace();
      } finally {
        database.endTransaction();
      }
    }
  }
  /**
   * @return List<FileStatus>
   * 取出本地下载列表数据,如在重新进入应用时,要重新把进度之类的设置好
   **/
  public List<FileStatus> getFileStatus() {
    List<FileStatus> list = new ArrayList<FileStatus>();
    SQLiteDatabase database = dbHelper.getReadableDatabase();
    //String sql = "slect * from " + TABLE_LOCALDOWNLOAD_INFO + ""; //不能用,需要哪些条件就在语句中写出哪些条件
    String sql = "select name,status,fileSize from " + TABLE_LOCALDOWNLOAD_INFO + "";
    Cursor cursor = database.rawQuery(sql,null);
    while (cursor.moveToNext()) {
      FileStatus fileState = new FileStatus(cursor.getString(0),cursor.getString(1),cursor.getInt(4));
      list.add(fileState);
    }
    cursor.close();
    return list;
  }
  /**
   * @param url
   * @param completeSize
   * @param status    更新文件的下载状态
   **/
  public void updateFileDownStatus(int completeSize,int status,String url) {
    synchronized (file_Lock) {
      String sql = "update " + TABLE_LOCALDOWNLOAD_INFO + " set completedSize = ?,status = ? where url = ?";
      SQLiteDatabase database = dbHelper.getWritableDatabase();
      database.beginTransaction();
      try {
        Object[] bindArgs = {completeSize,url};
        database.execSQL(sql,bindArgs);
        database.delete(TABLE_DOWNLOAD_INFO,"url = ?",new String[]{url});
        database.setTransactionSuccessful();
      } catch (SQLException e) {
        e.printStackTrace();
      } finally {
        database.endTransaction();
      }
    }
  }
  /**
   * @param url 获取文件名称
   **/
  public String getFileName(String url) {
    String result = "";
    String sql = "select name from " + TABLE_LOCALDOWNLOAD_INFO + " where url = ?";
    SQLiteDatabase database = dbHelper.getReadableDatabase();
    Cursor cursor = database.rawQuery(sql,new String[]{url});
    if (cursor.moveToNext()) {
      result = cursor.getString(0);
    }
    cursor.close();
    return result;
  }
  /**
   * 删除文件之后,要删除下载的数据,一个是用户可以重新下载
   * 另一个是表再次添加一条数据的时候不出现错误
   *
   * @param url
   */
  public void deleteFile(String url) {
    SQLiteDatabase database = dbHelper.getWritableDatabase();
    database.beginTransaction();
    try {
      database.delete(TABLE_DOWNLOAD_INFO," url = ?",new String[]{url});
      database.delete(TABLE_LOCALDOWNLOAD_INFO,new String[]{url});
      database.setTransactionSuccessful();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      database.endTransaction();
    }
  }
  /**
   * 关闭数据库
   *
   * @close
   */
  public void closeDB() {
    dbHelper.close();
  }
}

DownloadService 主要代码

@SuppressLint("HandlerLeak")
public class DownloadService extends Service
{
 public IBinder binder = new MyBinder();
 public class MyBinder extends Binder
 {
 public DownloadService getService()
 {
  return DownloadService.this;
 }
 }
 @Override
 public IBinder onBind(Intent intent)
 {
 return binder;
 }
 public static int number = 0;
 // 文件保存地址
 public final String savePath = "/mnt/sdcard/MultiFileDownload/";
 // 存放下载列表的引用
 public static List<FileStatus> list = new ArrayList<FileStatus>();
 public static Map<String,String> localDownList = new HashMap<String,String>();
 // 保存每个文件下载的下载器
 public static Map<String,Downloader> downloaders = new HashMap<String,Downloader>();
 // 每个下载文件完成的长度
 private Map<String,Integer> completeSizes = new HashMap<String,Integer>();
 // 每个下载文件的总长度
 private Map<String,Integer> fileSizes = new HashMap<String,Integer>();
 private Downloader downloader;
 private int threadCount = 5;
 private Dao dao;
 private DownLoadCallback loadCallback;
 
 private FileStatus mFileStatus = null;
 private Handler handler = new Handler()
 {
 @Override
 public void handleMessage(Message msg)
 {
  super.handleMessage(msg);
  if (msg.what == 1)
  {
  String url = (String) msg.obj;
  int length = msg.arg1;
  int completeSize = completeSizes.get(url);
  int fileSize = fileSizes.get(url);
  completeSize += length;
  completeSizes.put(url,completeSize);
  synchronized (list)
  {
   for (int i = 0; i < list.size(); i++)
   {
   FileStatus fileStatus = list.get(i);
   if (fileStatus.getUrl().equals(url))
   {
    if (completeSize == fileStatus.getFileSize())
    {
    System.out.println("-----------下载完成:"+fileStatus.getName()+":"+completeSize+"-----"+fileStatus.getFileSize());
    list.set(i,new FileStatus(fileStatus.getName(),1,completeSize,fileStatus.getFileSize()));
    dao.updateFileDownStatus(completeSize,url);
    }
    else
    {
    list.set(i,fileStatus.getFileSize()));
    }
    mFileStatus = list.get(i);
   }
   }
   
   this.postDelayed(new Runnable()
   {
   @Override
   public void run()
   {
    if (loadCallback != null && mFileStatus != null)
    {
    loadCallback.refreshUI(mFileStatus);
    }
   }
   },1000);
  }
  
  }
 }
 };
 @Override
 public void onCreate()
 {
 super.onCreate();
 dao = Dao.getInstance(this);
 list = dao.getFileStatus();
 for (FileStatus fileStatus : list)
 {
  localDownList.put(fileStatus.getUrl(),fileStatus.getUrl());
 }
 
 Timer timer = new Timer();
 timer.schedule(new TimerTask()
 {
  @Override
  public void run()
  {
  number++;
  }
 },1000);
 }
 public void download(final Button button,final String url,final String name,final Handler mHandler)
 {
 if (dao.isExist(url))
 {
  Toast.makeText(this,"别点了,已经在下载了",Toast.LENGTH_SHORT).show();
  return;
 }
 final String fileName = name + url.substring(url.lastIndexOf("."));
 new Thread(new Runnable()
 {
  @Override
  public void run()
  {
  downloader = downloaders.get(url);
  if (downloader == null)
  {
   downloader = new Downloader(url,savePath,fileName,threadCount,DownloadService.this,handler);
   downloaders.put(url,downloader);
  }
  if (downloader.isDownloading())
   return;
  
  LoadInfo loadInfo = downloader.getDownloaderInfors();
  
  if(loadInfo != null)
  {
   FileStatus fileStatus = new FileStatus(fileName,loadInfo.getComplete(),loadInfo.getFileSize());
   dao.insertFileStatus(fileStatus);
   completeSizes.put(url,loadInfo.getComplete());
   fileSizes.put(url,fileStatus.getFileSize());
   list.add(fileStatus);
   localDownList.put(url,url);
   downloader.download();
   Message msg = new Message();
   msg.what = 1;
   msg.obj = button;
   mHandler.sendMessage(msg);
  }
  else
  {
   Message msg = new Message();
   msg.what = 2;
   msg.obj = button;
   mHandler.sendMessage(msg);
  }
  }
 }).start();
 }
 
 //暂停下载
 public void Pause(Downloader downloader)
 {
 downloader.pause();
 }
 
 //继续下载
 public void reDownload(final Button button,final Handler mHandler)
 {
 new Thread(new Runnable()
 {
  @Override
  public void run()
  {
  String fileName = dao.getFileName(url);
  
  downloader = downloaders.get(url);
  if (downloader == null)
  {
   downloader = new Downloader(url,downloader);
  }
  if (downloader.isDownloading())
   return;
  
  LoadInfo loadInfo = downloader.getDownloaderInfors();
  
  if(loadInfo != null && !fileName.equals(""))
  {
   if(!completeSizes.containsKey(url))
   {
   completeSizes.put(url,loadInfo.getComplete());
   }
   if(!fileSizes.containsKey(url))
   {
   fileSizes.put(url,loadInfo.getFileSize());
   }
   downloader.download();
   Message msg = new Message();
   msg.what = 1;
   msg.obj = button;
   mHandler.sendMessage(msg);
  }
  else
  {
   Message msg = new Message();
   msg.what = 2;
   msg.obj = button;
   mHandler.sendMessage(msg);
  }
  }
 }).start();
 }
 
 public void delete(final String url)
 {
 Downloader down = downloaders.get(url);
 if(down != null)
 {
  down.pause();
 }
 
 handler.postDelayed(new Runnable()
 {
  @Override
  public void run()
  {
  dao.deleteFile(url);
  
  for (int i = 0; i < list.size(); i++)
  {
   FileStatus fileStatus = list.get(i);
   if (fileStatus.getUrl().equals(url))
   {
   list.remove(i);
   }
  }
  
  localDownList.remove(url);
  downloaders.remove(url);
  completeSizes.remove(url);
  fileSizes.remove(url);
  
  if(loadCallback != null)
  {
   loadCallback.deleteFile(url);
  }
  }
 },1000);
 }
 public interface DownLoadCallback
 {
 public void refreshUI(FileStatus fileStatus);
 
 public void deleteFile(String url);
 }
 public void setLoadCallback(DownLoadCallback loadCallback)
 {
 this.loadCallback = loadCallback;
 }
}

下载工具类DownLoadUtil

/**
 * Created by ShanCanCan on 2017/3/7 0007.
 */
public class DownLoadUtil {
  /**
   * 此类的主要功能
   * 1、检查是否下载
   * 2、下载文件,文件的下载采用httpurlconnection
   */
  private String downPath;// 下载路径
  private String savePath;// 保存路径
  private String fileName;// 文件名称
  private int threadCount;// 线程数
  private Handler mHandler;
  private Dao dao;
  private Context context;
  private int fileSize;// 文件大小
  private int range;
  private List<DownLoadInfo> infos;// 存放下载信息类的集合
  private int state = INIT;
  private static final int INIT = 1;// 定义三种下载的状态:初始化状态,正在下载状态,暂停状态
  private static final int DOWNLOADING = 2;
  private static final int PAUSE = 3;
  /**
   * 构造方法,获取dao的对象
   *
   * @param downPath
   * @param savePath
   * @param fileName
   * @param threadCount
   * @param context
   * @param mHandler
   */
  public DownLoadUtil(String downPath,String savePath,String fileName,int threadCount,Handler mHandler,Context context) {
    this.downPath = downPath;
    this.savePath = savePath;
    this.fileName = fileName;
    this.threadCount = threadCount;
    this.mHandler = mHandler;
    this.context = context;
    dao = Dao.getInstance(context);
  }
  /**
   * 判断是否PAUSE
   **/
  public boolean isPause() {
    return state == PAUSE;
  }
  /**
   * 判断是否DOWNLOADING
   */
  public boolean isDownloading() {
    return state == DOWNLOADING;
  }
  /**
   * @param url 判断是否是第一次下载,利用dao查询数据库中是否有下载这个地址的记录
   */
  private boolean isFirst(String url) {
    return dao.isFirstDownload(url);
  }
  /**
   * 获取要下载的东西
   */
  public LoadItemInfo getDownloadInfos() {
    if (isFirst(downPath)) {
      if (initFirst()) {//如果是第一次下载的话,要进行初始化,1.获得下载文件的长度 2.创建文件,设置文件的大小
        range = this.fileSize / this.threadCount;
        infos = new ArrayList<DownLoadInfo>();
        //这里就是启动多线程下载,看出来了吗?配合RandomAccessFile。每一个DownLoadInfo就是RandomAccessFile文件的一部分
        for (int i = 0; i < this.threadCount - 1; i++) {
          DownLoadInfo info = new DownLoadInfo(i,i * range,(i + 1) * range - 1,downPath);
          infos.add(info);
        }
        DownLoadInfo info = new DownLoadInfo(this.threadCount - 1,(this.threadCount - 1) * range,this.fileSize,downPath);
        infos.add(info);
        dao.saveInfos(infos,this.context);
        //(String urlDownload,int completePercent,int fileSize)
        LoadItemInfo loadInfo = new LoadItemInfo(this.downPath,this.fileSize);
        return loadInfo;
      } else {
        return null;
      }
    } else {
      //不是第一次下载,我们应该怎么做呢?从数据库里面取回来
      infos = dao.getInfos(this.downPath);
      if (infos != null && infos.size() > 0) {
        int size = 0;
        int completeSize = 0;
        for (DownLoadInfo info : infos) {
          completeSize += info.getCompletedSize();
          size += info.getEndPosition() - info.getStartPosition() + this.threadCount - 1;
        }
        LoadItemInfo loadInfo = new LoadItemInfo(this.downPath,size);
        return loadInfo;
      } else {
        return null;
      }
    }
  }
  // 设置暂停
  public void pause() {
    state = PAUSE;
  }
  // 重置下载状态,将下载状态设置为init初始化状态
  public void reset() {
    state = INIT;
  }
  /**
   * 基本上,RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream结合起来,再加上它自己的一些方法,
   * 比如定位用的getFilePointer( ),在文件里移动用的seek( ),以及判断文件大小的length( )、skipBytes()跳过多少字节数。
   * 此外,它的构造函数还要一个表示以只读方式("r"),还是以读写方式("rw")打开文件的参数 (和C的fopen( )一模一样)。它不支持只写文件。
   */
  private boolean initFirst() {
    boolean result = true;
    HttpURLConnection conn = null;
    RandomAccessFile randomFile = null;
    URL url = null;
    try {
      url = new URL(downPath);
      conn = (HttpURLConnection) url.openConnection();
      conn.setConnectTimeout(5 * 1000);
      conn.setRequestMethod("GET");
      // 如果http返回的代码是200或者206则为连接成功
      if (conn.getResponseCode() == 200 || conn.getResponseCode() == 206) //状态码(206),表示服务器已经执行完部分对资源的GET请求
      {
        fileSize = conn.getContentLength();// 得到文件的大小
        if (fileSize <= 0) {
          //("网络故障,无法获取文件大小");
          return false;
        }
        File dir = new File(savePath);
        // 如果文件目录不存在,则创建
        if (!dir.exists()) {
          if (dir.mkdirs()) {
            //("mkdirs success.");
          }
        }
        File file = new File(this.savePath,this.fileName);
        randomFile = new RandomAccessFile(file,"rwd");
        randomFile.setLength(fileSize);// 设置保存文件的大小
        randomFile.close();
        conn.disconnect();
      }
    } catch (Exception e) {
      e.printStackTrace();
      result = false;
    } finally {
      if (randomFile != null) {
        try {
          randomFile.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      if (conn != null) {
        conn.disconnect();
      }
    }
    return result;
  }
  /**
   * 下面的这个方法就是开启多线程进行下载了数据了
   */
  public void downLoad() {
    if (infos != null) {
      if (state == DOWNLOADING) {
        return;
      }
      state = DOWNLOADING;// 把状态设置为正在下载
      for (DownLoadInfo info : infos) {//为什么说我们是多线程呢?因为我们分别用新线程去下载刚才分割好的一个RandomAccessFile文件
        new DownLoadThread(info.getThreadId(),info.getUrl(),this.context).start();
      }
    }
  }
  /**
   * 现在要创建线程用来下载了,这里采用内部类
   */
  public class DownLoadThread extends Thread {
    private int threadId;
    private int startPostion;
    private int endPostion;
    private int compeletedSize;
    private String url;
    private Context context;
    public static final int PROGRESS = 1;
    public DownLoadThread(int threadId,int startPostion,int endPostion,String url,Context context) {//构造方法,传入特定的参数
      this.threadId = threadId;
      this.startPostion = startPostion;
      this.endPostion = endPostion;
      this.compeletedSize = compeletedSize;
      this.url = url;
      this.context = context;
    }
    //开始下载
    @Override
    public void run() {
      HttpURLConnection conn = null;
      RandomAccessFile randomAccessFile = null;
      InputStream inStream = null;
      File file = new File(savePath,fileName);
      URL url = null;
      try {
        url = new URL(this.url);
        conn = (HttpURLConnection) url.openConnection();
        constructConnection(conn);
        if (conn.getResponseCode() == 200 || conn.getResponseCode() == 206) {
          randomAccessFile = new RandomAccessFile(file,"rwd");
          randomAccessFile.seek(this.startPostion + this.compeletedSize);//RandomAccessFile移动指针,到需要下载的块
          inStream = conn.getInputStream();
          byte buffer[] = new byte[4096];//这个4096为么子呢?我也不知道,就是看阿里的人下载apk的时候都用4096,我也用
          int length = 0;
          while ((length = inStream.read(buffer,buffer.length)) != -1) {
            randomAccessFile.write(buffer,length);
            compeletedSize += length;
            // 更新数据库中的下载信息
            dao.updataInfos(threadId,compeletedSize,this.url,this.context);
            // 用消息将下载信息传给进度条,对进度条进行更新
            Message message = Message.obtain();
            message.what = PROGRESS;
            message.obj = this.url;
            message.arg1 = length;
            mHandler.sendMessage(message);// 给DownloadService发送消息
            if (state == PAUSE) {
              //("-----pause-----");
              return;
            }
          }
          // ("------------线程:" + this.threadId + "下载完成");
        }
      } catch (IOException e) {
        e.printStackTrace();
        //("-----下载异常-----"); 这里下载异常我就不处理了,你可以发一条重新下载的消息
      } finally {//用完只后流要关闭,不然容易造成资源抢占,内存泄漏
        try {
          if (inStream != null) {
            inStream.close();
          }
          if (randomAccessFile != null) {
            randomAccessFile.close();
          }
          if (conn != null) {
            conn.disconnect();
          }
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    /**
     * 构建请求连接时的参数 返回开始下载的位置
     *
     * @param conn
     */
    private void constructConnection(HttpURLConnection conn) throws IOException {
      conn.setConnectTimeout(5 * 1000);// 设置连接超时5秒
      conn.setRequestMethod("GET");// GET方式提交,如果你是用post请求必须添加 conn.setDoOutput(true); conn.setDoInput(true);
      conn.setRequestProperty(
          "Accept","image/gif,image/jpeg,image/pjpeg,application/x-shockwave-flash,application/xaml+xml,application/vnd.ms-xpsdocument,application/x-ms-xbap,application/x-ms-application,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword,*/*");
      conn.setRequestProperty("Accept-Language","zh-CN");
      conn.setRequestProperty("Referer",this.url);
      conn.setRequestProperty("Charset","UTF-8");
      int startPositionNew = this.startPostion + this.compeletedSize;
      // 设置获取实体数据的范围
      conn.setRequestProperty("Range","bytes=" + startPositionNew + "-" + this.endPostion);
      conn.setRequestProperty(
          "User-Agent","Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
      conn.setRequestProperty("Connection","Keep-Alive");
      conn.connect();
    }
  }
}

Github地址:https://github.com/Shanlovana/DownLoadFiles/

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


更新Android SDK到3.0版本时,遇到Failed to rename directory E:\android\tools to E:\android\temp\ToolPackage.old01问题,导致无法更新,出现该问题的原因是由于3.0版本与较早的sdk版本之间文件结构有冲突,解决
Android 如何解决dialog弹出时无法捕捉Activity的back事件 在一些情况下,我们需要捕捉back键事件,然后在捕捉到的事件里写入我们需要进行的处理,通常可以采用下面三种办法捕捉到back事件: 1)重写onKeyDown或者onKeyUp方法 2)重写onBackPressed方
Android实现自定义带文字和图片的Button 在Android开发中经常会需要用到带文字和图片的button,下面来讲解一下常用的实现办法。一.用系统自带的Button实现 最简单的一种办法就是利用系统自带的Button来实现,这种方式代码量最小。在Button的属性中有一个是drawable
Android中的&quot;Unable to start activity ComponentInfo&quot;的错误 最近在做一款音乐播放器的时候,然后在调试的过程中发现一直报这个错误&quot;Unable to start activity ComponentInfo&quot;,从字面
Android 关于长按back键退出应用程序的实现最近在做一个Android上的应用,碰到一个问题就是如何实现长按back键退出应用程序。在网上查找了很多资料,发现几乎没有这样的实现,大部分在处理时是双击back键来退出应用程序。参考了一下双击back键退出应用程序的代码,网上主流的一种方法是下面
android自带的时间选择器只能精确到分,但是对于某些应用要求选择的时间精确到秒级,此时只有自定义去实现这样的时间选择器了。下面介绍一个可以精确到秒级的时间选择器。 先上效果图: 下面是工程目录: 这个控件我也是用的别人的,好像是一个老外写的,com.wheel中的WheelView是滑动控件的主
Android平台下利用zxing实现二维码开发 现在走在大街小巷都能看到二维码,而且最近由于项目需要,所以研究了下二维码开发的东西,开源的二维码扫描库主要有zxing和zbar,zbar在iPos平台上应用比较成熟,而在Android平台上主流还是用zxing库,因此这里主要讲述如何利用zxing
Android ListView的item背景色设置以及item点击无响应等相关问题 在Android开发中,listview控件是非常常用的控件,在大多数情况下,大家都会改掉listview的item默认的外观,下面讲解以下在使用listview时最常见的几个问题。1.如何改变item的背景色和按
如何向Android模拟器中导入含有中文名称的文件在进行Android开发的时候,如果需要向Android模拟器中导入文件进行测试,通过DDMS下手动导入或者在命令行下通过adb push命令是无法导入含有中文文件名的文件的。后来发现借用其他工具可以向模拟器中导入中文名称的文件,这个工具就是Ultr
Windows 下搭建Android开发环境一.下载并安装JDK版本要求JDK1.6+,下载JDK成功后进行安装,安装好后进行环境变量的配置【我的电脑】-——&gt;【属性】——&gt;【高级】 ——&gt;【环境变量】——&gt;【系统变量】中点击【新建】:变量名:CLASSPATH变量值:……
如何利用PopupWindow实现弹出菜单并解决焦点获取以及与软键盘冲突问题 在android中有时候可能要实现一个底部弹出菜单,此时可以考虑用PopupWindow来实现。下面就来介绍一下如何使用PopupWindow实现一个弹出窗。 主Activity代码:public void onCreat
解决Android中的ERROR: the user data image is used by another emulator. aborting的方法 今天调试代码的时候,突然出现这个错误,折腾了很久没有解决。最后在google上找到了大家给出的两种解决方案,下面给出这两种方法的链接博客:ht
AdvserView.java package com.earen.viewflipper; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory;
ImageView的scaleType的属性有好几种,分别是matrix(默认)、center、centerCrop、centerInside、fitCenter、fitEnd、fitStart、fitXY。 |值|说明| |:--:|:--| |center|保持原图的大小,显示在ImageVie
文章浏览阅读8.8k次,点赞9次,收藏20次。本文操作环境:win10/Android studio 3.21.环境配置 在SDK Tools里选择 CMAKE/LLDB/NDK点击OK 安装这些插件. 2.创建CMakeLists.txt文件 在Project 目录下,右键app,点击新建File文件,命名为CMakeLists.txt点击OK,创建完毕! 3.配置文件 在CMa..._link c++ project with gradle
文章浏览阅读1.2w次,点赞15次,收藏69次。实现目的:由mainActivity界面跳转到otherActivity界面1.写好两个layout文件,activity_main.xml和otherxml.xmlactivity_main.xml&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;RelativeLayout ="http://schemas..._android studio 界面跳转
文章浏览阅读3.8w次。前言:最近在找Android上的全局代理软件来用,然后发现了这两款神作,都是外国的软件,而且都是开源的软件,因此把源码下载了下来,给有需要研究代理这方面的童鞋看看。不得不说,国外的开源精神十分浓,大家相互使用当前基础的开源软件,然后组合成一个更大更强的大开源软件。好吧,废话不多说,下面简单介绍一下这两款开源项目。一、ProxyDroid:ProxyDroid功能比较强大,用到的技术也比较多,源码也_proxydroid
文章浏览阅读2.5w次,点赞17次,收藏6次。创建项目后,运行项目时Gradle Build 窗口却显示错误:程序包R不存在通常情况下是不会出现这个错误的。我是怎么遇到这个错误的呢?第一次创建项目,company Domain我使用的是:aven.com,但是创建过程在卡在了Building 'Calculator' Gradle Project info这个过程中,于是我选择了“Cancel”第二次创建项目,我还是使用相同的项目名称和项目路_r不存在
文章浏览阅读8.9w次,点赞4次,收藏43次。前言:在Android上使用系统自带的代理,限制灰常大,仅支持系统自带的浏览器。这样像QQ、飞信、微博等这些单独的App都不能使用系统的代理。如何让所有软件都能正常代理呢?ProxyDroid这个软件能帮你解决!使用方法及步骤如下:一、推荐从Google Play下载ProxyDroid,目前最新版本是v2.6.6。二、对ProxyDroid进行配置(基本配置:) (1) Auto S_proxydroid使用教程
文章浏览阅读1.1w次,点赞4次,收藏17次。Android Studio提供了一个很实用的工具Android设备监视器(Android device monitor),该监视器中最常用的一个工具就是DDMS(Dalvik Debug Monitor Service),是 Android 开发环境中的Dalvik虚拟机调试监控服务。可以进行的操作有:为测试设备截屏,查看特定进程中正在运行的线程以及堆栈信息、Logcat、广播状态信息、模拟电话_安卓摄像头调试工具