布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.ml.wuchengya1510a20180115.MainActivity"> <SeekBar android:id="@+id/seekbar" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/bt" android:text="下载" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/bt_pause" android:text="暂停" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/bt_start" android:text="继续" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/bt_cancel" android:text="取消" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <SeekBar android:id="@+id/seekbar1" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_progress1" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/bt1" android:text="下载" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/bt_pause1" android:text="暂停" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/bt_start1" android:text="继续" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/bt_cancel1" android:text="取消" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <SeekBar android:id="@+id/seekbar2" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_progress2" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/bt2" android:text="下载" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/bt_pause2" android:text="暂停" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/bt_start2" android:text="继续" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/bt_cancel2" android:text="取消" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <SeekBar android:id="@+id/seekbar3" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_progress3" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/bt3" android:text="下载" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/bt_pause3" android:text="暂停" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/bt_start3" android:text="继续" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/bt_cancel3" android:text="取消" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <VideoView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/vv" android:visibility="invisible" /> </LinearLayout>user类:
@Entity public class User { @Id private Long id; private Integer thread_id; private Integer start_pos; private Integer end_pos; private Integer compelete_size; private String url;导入依赖greendao:
app:build.gradle
apply plugin: 'org.greenrobot.greendao'
compile 'org.greenrobot:greendao:3.2.0'
工程
:build.gradle
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.1'
自动生成;
创建app类初始化:
public class App extends Application { public static UserDao userDao; @Override public void onCreate() { super.onCreate(); DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(this, "lenvess.db", null); DaoMaster daoMaster = new DaoMaster(devOpenHelper.getWritableDb()); DaoSession daoSession = daoMaster.newSession(); userDao = daoSession.getUserDao(); } }
public class DownlaodSqlTool { /** * 创建下载的具体信息 */ public void insertInfos(String infos) { User user=new User(); user.setUrl(infos); userDao.insert(user); } /** * 得到下载具体信息 */ public List<DownLoadFile> getInfos(String urlstr) { List<DownLoadFile> list = new ArrayList<DownLoadFile>(); List<User> list1 = userDao.queryBuilder().where(UserDao.Properties.Url.eq(urlstr)).build().list(); return list; } /** * 更新数据库中的下载信息 */ public void updataInfos(int threadId, int compeleteSize, String urlstr) { User user = userDao.queryBuilder() .where(UserDao.Properties.Thread_id.eq(threadId), UserDao.Properties.Url.eq(urlstr)).build().unique(); user.setCompelete_size(compeleteSize); userDao.update(user); } /** * 下载完成后删除数据库中的数据 */ public void delete(String url) { userDao.deleteAll(); } }
public class DownLoadFile { private static final String SP_NAME = "download_file"; private static final String CURR_LENGTH = "curr_length"; private static final int DEFAULT_THREAD_COUNT = 4;//默认下载线程数 //以下为线程状态 private static final String DOWNLOAD_INIT = "1"; private static final String DOWNLOAD_ING = "2"; private static final String DOWNLOAD_PAUSE = "3"; private Context mContext; private String loadUrl;//网络获取的url private String filePath;//下载到本地的path private int threadCount = DEFAULT_THREAD_COUNT;//下载线程数 private int fileLength;//文件总大小 //使用volatile防止多线程不安全 private volatile int currLength;//当前总共下载的大小 private volatile int runningThreadCount;//正在运行的线程数 private Thread[] mThreads; private String stateDownload = DOWNLOAD_INIT;//当前线程状态 private DownLoadListener mDownLoadListener; public void setOnDownLoadListener(DownLoadListener mDownLoadListener) { this.mDownLoadListener = mDownLoadListener; } interface DownLoadListener { //返回当前下载进度的百分比 void getProgress(int progress); void onComplete(); void onFailure(); } public DownLoadFile(Context mContext, String loadUrl, String filePath) { this(mContext, loadUrl, filePath, DEFAULT_THREAD_COUNT, null); } public DownLoadFile(Context mContext, String loadUrl, String filePath, DownLoadListener mDownLoadListener) { this(mContext, loadUrl, filePath, DEFAULT_THREAD_COUNT, mDownLoadListener); } public DownLoadFile(Context mContext, String loadUrl, String filePath, int threadCount) { this(mContext, loadUrl, filePath, threadCount, null); } public DownLoadFile(Context mContext, String loadUrl, String filePath, int threadCount, DownLoadListener mDownLoadListener) { this.mContext = mContext; this.loadUrl = loadUrl; this.filePath = filePath; this.threadCount = threadCount; runningThreadCount = 0; this.mDownLoadListener = mDownLoadListener; } /** * 开始下载 */ protected void downLoad() { //在线程中运行,防止anr new Thread(new Runnable() { @Override public void run() { try { //初始化数据 if (mThreads == null) mThreads = new Thread[threadCount]; //建立连接请求 URL url = new URL(loadUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setRequestMethod("GET"); int code = conn.getResponseCode();//获取返回码 if (code == 200) {//请求成功,根据文件大小开始分多线程下载 fileLength = conn.getContentLength(); //根据文件大小,先创建一个空文件 //“r“——以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。 //“rw“——打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 //“rws“—— 打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 //“rwd“——打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。 RandomAccessFile raf = new RandomAccessFile(filePath, "rwd"); raf.setLength(fileLength); raf.close(); //计算各个线程下载的数据段 int blockLength = fileLength / threadCount; SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); //获取上次取消下载的进度,若没有则返回0 currLength = sp.getInt(CURR_LENGTH, 0); for (int i = 0; i < threadCount; i++) { //开始位置,获取上次取消下载的进度,默认返回i*blockLength,即第i个线程开始下载的位置 int startPosition = sp.getInt(SP_NAME + (i + 1), i * blockLength); //结束位置,-1是为了防止上一个线程和下一个线程重复下载衔接处数据 int endPosition = (i + 1) * blockLength - 1; //将最后一个线程结束位置扩大,防止文件下载不完全,大了不影响,小了文件失效 if ((i + 1) == threadCount) endPosition = endPosition * 2; mThreads[i] = new DownThread(i + 1, startPosition, endPosition); mThreads[i].start(); } } else { handler.sendEmptyMessage(FAILURE); } } catch (Exception e) { e.printStackTrace(); handler.sendEmptyMessage(FAILURE); } } }).start(); } /** * 取消下载 */ protected void cancel() { if (mThreads != null) { //若线程处于等待状态,则while循环处于阻塞状态,无法跳出循环,必须先唤醒线程,才能执行取消任务 if (stateDownload.equals(DOWNLOAD_PAUSE)) onStart(); for (Thread dt : mThreads) { ((DownThread) dt).cancel(); } } } /** * 暂停下载 */ protected void onPause() { if (mThreads != null) stateDownload = DOWNLOAD_PAUSE; } /** * 继续下载 */ protected void onStart() { if (mThreads != null) synchronized (DOWNLOAD_PAUSE) { stateDownload = DOWNLOAD_ING; DOWNLOAD_PAUSE.notifyAll(); } } protected void onDestroy() { if (mThreads != null) mThreads = null; } private class DownThread extends Thread { private boolean isGoOn = true;//是否继续下载 private int threadId; private int startPosition;//开始下载点 private int endPosition;//结束下载点 private int currPosition;//当前线程的下载进度 private DownThread(int threadId, int startPosition, int endPosition) { this.threadId = threadId; this.startPosition = startPosition; currPosition = startPosition; this.endPosition = endPosition; runningThreadCount++; } @Override public void run() { SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); try { URL url = new URL(loadUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition); conn.setConnectTimeout(5000); //若请求头加上Range这个参数,则返回状态码为206,而不是200 if (conn.getResponseCode() == 206) { InputStream is = conn.getInputStream(); RandomAccessFile raf = new RandomAccessFile(filePath, "rwd"); raf.seek(startPosition);//跳到指定位置开始写数据 int len; byte[] buffer = new byte[1024]; while ((len = is.read(buffer)) != -1) { //是否继续下载 if (!isGoOn) break; //回调当前进度 if (mDownLoadListener != null) { currLength += len; int progress = (int) ((float) currLength / (float) fileLength * 100); handler.sendEmptyMessage(progress); } raf.write(buffer, 0, len); //写完后将当前指针后移,为取消下载时保存当前进度做准备 currPosition += len; synchronized (DOWNLOAD_PAUSE) { if (stateDownload.equals(DOWNLOAD_PAUSE)) { DOWNLOAD_PAUSE.wait(); } } } is.close(); raf.close(); //线程计数器-1 runningThreadCount--; //若取消下载,则直接返回 if (!isGoOn) { //此处采用SharedPreferences保存每个线程的当前进度,和三个线程的总下载进度 if (currPosition < endPosition) { sp.edit().putInt(SP_NAME + threadId, currPosition).apply(); sp.edit().putInt(CURR_LENGTH, currLength).apply(); } return; } if (runningThreadCount == 0) { sp.edit().clear().apply(); handler.sendEmptyMessage(SUCCESS); handler.sendEmptyMessage(100); mThreads = null; } } else { sp.edit().clear().apply(); handler.sendEmptyMessage(FAILURE); } } catch (Exception e) { sp.edit().clear().apply(); e.printStackTrace(); handler.sendEmptyMessage(FAILURE); } } public void cancel() { isGoOn = false; } } private final int SUCCESS = 0x00000101; private final int FAILURE = 0x00000102; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { if (mDownLoadListener != null) { if (msg.what == SUCCESS) { mDownLoadListener.onComplete(); } else if (msg.what == FAILURE) { mDownLoadListener.onFailure(); } else { mDownLoadListener.getProgress(msg.what); } } } }; }activity:
public class MainActivity extends AppCompatActivity { @BindView(R.id.seekbar) SeekBar seekbar; @BindView(R.id.tv_progress) TextView tvProgress; @BindView(R.id.bt) Button bt; @BindView(R.id.bt_pause) Button btPause; @BindView(R.id.bt_start) Button btStart; @BindView(R.id.bt_cancel) Button btCancel; @BindView(R.id.seekbar1) SeekBar seekbar1; @BindView(R.id.tv_progress1) TextView tvProgress1; @BindView(R.id.bt1) Button bt1; @BindView(R.id.bt_pause1) Button btPause1; @BindView(R.id.bt_start1) Button btStart1; @BindView(R.id.bt_cancel1) Button btCancel1; @BindView(R.id.seekbar2) SeekBar seekbar2; @BindView(R.id.tv_progress2) TextView tvProgress2; @BindView(R.id.bt2) Button bt2; @BindView(R.id.bt_pause2) Button btPause2; @BindView(R.id.bt_start2) Button btStart2; @BindView(R.id.bt_cancel2) Button btCancel2; @BindView(R.id.seekbar3) SeekBar seekbar3; @BindView(R.id.tv_progress3) TextView tvProgress3; @BindView(R.id.bt3) Button bt3; @BindView(R.id.bt_pause3) Button btPause3; @BindView(R.id.bt_start3) Button btStart3; @BindView(R.id.bt_cancel3) Button btCancel3; @BindView(R.id.vv) VideoView vv; private DownLoadFile downLoadFile; private String loadUrl = "http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4"; private String filePath = Environment.getExternalStorageDirectory() + "/" + "bofang5.mp4"; private String loadUrl1 = "http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4"; private String filePath1 = Environment.getExternalStorageDirectory() + "/" + "bofang1.mp4"; private String loadUrl2 = "http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4"; private String filePath2 = Environment.getExternalStorageDirectory() + "/" + "bofang2.mp4"; private String loadUrl3 = "http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4"; private String filePath3 = Environment.getExternalStorageDirectory() + "/" + "bofang3.mp4"; private DownLoadFile downLoadFile1; private DownLoadFile downLoadFile2; private DownLoadFile downLoadFile3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); final DownlaodSqlTool downlaodSqlTool=new DownlaodSqlTool(); downLoadFile = new DownLoadFile(this, loadUrl, filePath, 4); downLoadFile.setOnDownLoadListener(new DownLoadFile.DownLoadListener() { @Override public void getProgress(int progress) { tvProgress.setText("当前进度 :" + progress + " %"); seekbar.setProgress(progress); } @Override public void onComplete() { Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show(); vv.setVisibility(View.VISIBLE); Uri uri=Uri.parse(filePath); //设置视频控制器 vv.setMediaController(new MediaController(MainActivity.this)); //播放完成回调 vv.setOnCompletionListener(new MyPlayerOnCompletionListener()); //设置视频路径 vv.setVideoURI(uri); //开始播放视频 vv.start(); } @Override public void onFailure() { Toast.makeText(MainActivity.this, "下载失败", Toast.LENGTH_SHORT).show(); } }); bt.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downLoadFile.downLoad(); } }); btPause.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downLoadFile.onPause(); //添加信息 downlaodSqlTool.insertInfos(filePath); } }); btStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downLoadFile.onStart(); } }); downLoadFile1 = new DownLoadFile(this, loadUrl1, filePath1, 4); downLoadFile1.setOnDownLoadListener(new DownLoadFile.DownLoadListener() { @Override public void getProgress(int progress) { tvProgress1.setText("当前进度 :" + progress + " %"); seekbar1.setProgress(progress); } @Override public void onComplete() { Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show(); } @Override public void onFailure() { Toast.makeText(MainActivity.this, "下载失败", Toast.LENGTH_SHORT).show(); } }); bt1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downLoadFile1.downLoad(); } }); btPause1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downLoadFile1.onPause(); downlaodSqlTool.insertInfos(filePath1); } }); btStart1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downLoadFile1.onStart(); } }); downLoadFile2 = new DownLoadFile(this, loadUrl2, filePath2, 4); downLoadFile2.setOnDownLoadListener(new DownLoadFile.DownLoadListener() { @Override public void getProgress(int progress) { tvProgress2.setText("当前进度 :" + progress + " %"); seekbar2.setProgress(progress); } @Override public void onComplete() { Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show(); } @Override public void onFailure() { Toast.makeText(MainActivity.this, "下载失败", Toast.LENGTH_SHORT).show(); } }); bt2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downLoadFile2.downLoad(); } }); btPause2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downLoadFile2.onPause(); //添加greendao downlaodSqlTool.insertInfos(filePath2); } }); btStart2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downLoadFile2.onStart(); } }); downLoadFile3 = new DownLoadFile(this, loadUrl3, filePath3, 4); downLoadFile3.setOnDownLoadListener(new DownLoadFile.DownLoadListener() { @Override public void getProgress(int progress) { tvProgress3.setText("当前进度 :" + progress + " %"); seekbar3.setProgress(progress); } @Override public void onComplete() { Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show(); } @Override public void onFailure() { Toast.makeText(MainActivity.this, "下载失败", Toast.LENGTH_SHORT).show(); } }); bt3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downLoadFile3.downLoad(); } }); btPause3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downLoadFile3.onPause(); downlaodSqlTool.insertInfos(filePath3); } }); btStart3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { downLoadFile3.onStart(); } }); } //防止内存溢出 @Override protected void onDestroy() { downLoadFile.onDestroy(); super.onDestroy(); } //播放完成回调 class MyPlayerOnCompletionListener implements MediaPlayer.OnCompletionListener { @Override public void onCompletion(MediaPlayer mp) { Toast.makeText( MainActivity.this, "播放完成了", Toast.LENGTH_SHORT).show(); } } }