依赖:
compile 'com.loopj.android:android-async-http:1.4.9'
compile 'com.squareup.retrofit2:retrofit:2.0.1'
compile 'com.google.code.gson:gson:2.8.0'
compile 'com.squareup.okio:okio:1.5.0'
compile 'com.squareup.okhttp3:okhttp:3.2.0'
compile 'io.reactivex:rxandroid:1.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.1'
compile 'com.squareup.retrofit2:converter-gson:2.0.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
compile 'com.github.leifzhang:IjkLib:0.4.3'
权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Model层:
public interface ShowModel {
void getData(OnNetListener listener);
}
import com.bwie.test.mvpthread.Bean.MoveBean;
import com.bwie.test.mvpthread.Utils.Api;
import com.bwie.test.mvpthread.Utils.ApiService;
import com.bwie.test.mvpthread.Utils.OnNetListener;
import com.bwie.test.mvpthread.Utils.RetrofitUtils;
import rx.Observable;
import rx.Observer;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
/**
* Created by 努力努力再努力 on 2017/11/23.
*/
public class ShowModelImpl implements ShowModel {
@Override
public void getData(final OnNetListener listener) {
Observable<MoveBean> observable = RetrofitUtils.getInstance().getApiService(Api.HOST, ApiService.class).getHasParme("vedio");
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<MoveBean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(MoveBean moveBean) {
if (listener!=null){
listener.onSuccess(moveBean);
}
}
});
}
}
Presenter层
public interface ShowPresenter {
void relevance();
}
import com.bwie.test.mvpthread.Bean.MoveBean;
import com.bwie.test.mvpthread.Model.ShowModelImpl;
import com.bwie.test.mvpthread.Utils.OnNetListener;
import com.bwie.test.mvpthread.View.ShowView;
/**
* Created by 努力努力再努力 on 2017/11/23.
*/
public class ShowPresenterImpl implements ShowPresenter,OnNetListener{
ShowView showView;
private final ShowModelImpl showModel;
public ShowPresenterImpl(ShowView showView) {
this.showView = showView;
showModel = new ShowModelImpl();
}
@Override
public void relevance() {
//p跟m关联
showModel.getData(this);
}
@Override
public void onSuccess(MoveBean moveBean) {
//关联view
showView.showData(moveBean);
}
}
View层
public interface ShowView {
void showData(MoveBean moveBean);
}
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.bwie.test.mvpthread.Bean.MoveBean;
import com.bwie.test.mvpthread.Presenter.ShowPresenterImpl;
import com.bwie.test.mvpthread.R;
import tv.danmaku.ijk.media.player.IjkMediaPlayer;
import tv.danmaku.ijk.media.widget.media.AndroidMediaController;
import tv.danmaku.ijk.media.widget.media.IjkVideoView;
public class MainActivity extends AppCompatActivity implements ShowView {
DownLoadFile downLoadFile;
/**
* 开始下载
*/
private TextView tvprogress;
private IjkVideoView id;
private String filePath;
private ProgressBar prb;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
downLoadFile.downLoad();
}
});
findViewById(R.id.bt_pause).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
downLoadFile.onPause();
}
});
findViewById(R.id.bt_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
downLoadFile.onStart();
}
});
findViewById(R.id.bt_cancel).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
downLoadFile.cancel();
}
});
//p关联v
ShowPresenterImpl presenter = new ShowPresenterImpl(this);
//p关联m 做网络请求
presenter.relevance();
}
private void initView() {
tvprogress = (TextView) findViewById(R.id.tv_progress);
prb = (ProgressBar) findViewById(R.id.prb);
id = (IjkVideoView) findViewById(R.id.id);
id.setVisibility(View.GONE);
}
/**
* 获取文件的名称
* @param uri
* @return
*/
private String getFileName(String uri){
return uri.substring(uri.lastIndexOf("/")+1);
}
@Override
public void showData(MoveBean moveBean) {
String vedio_url = moveBean.getData().get(0).getVedio_url();
String name = getFileName(vedio_url);
StringBuffer stringBuffer=new StringBuffer();
filePath = stringBuffer.append("/storage/emulated/0/" + name).toString();
downLoadFile = new DownLoadFile(this,vedio_url, filePath, 3);
downLoadFile.setOnDownLoadListener(new DownLoadFile.DownLoadListener() {
@Override
public void getProgress(int progress) {
tvprogress.setText("当前进度 :"+progress+" %");
prb.setProgress(progress);
}
@Override
public void onComplete() {
id.setVisibility(View.VISIBLE);
Toast.makeText(MainActivity.this,"下载完成",Toast.LENGTH_SHORT).show();
IjkMediaPlayer.loadLibrariesOnce(null);
IjkMediaPlayer.native_profileBegin("libijkplayer.so");
AndroidMediaController controller = new AndroidMediaController(MainActivity.this, false);
id.setMediaController(controller);
id.setVideoURI(Uri.parse(filePath));
id.start();
}
@Override
public void onFailure() {
Toast.makeText(MainActivity.this,"下载失败",Toast.LENGTH_SHORT).show();
}
});
}
}
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Message;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* Created by tianzhao on 2017/2/21 09:25.
* 多线程下载文件
*/
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);
}
}
}
};
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context=".View.MainActivity">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/bt"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="downLoad"/>
<Button
android:id="@+id/bt_start"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="bt_start"/>
<Button
android:id="@+id/bt_pause"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="bt_pause"/>
<Button
android:id="@+id/bt_cancel"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="bt_cancel"/>
</LinearLayout>
<ProgressBar
android:id="@+id/prb"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_progress"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<tv.danmaku.ijk.media.widget.media.IjkVideoView
android:id="@+id/id"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
MVP+断点续传
最新推荐文章于 2024-03-03 00:00:11 发布