最后是文件下载~~~
先给你一个盖帽 DownLaodActivity 类 重点代码以上
private void initResource(){
//DbDownUtil 新来的一个下载类
dbUtil= DbDownUtil.getInstance();
listData=dbUtil.queryDownAll();
/*adapter数据填充的,其实也是最终会从数据库提出来的*/
if(listData.isEmpty()){
//这里模拟的一个 数据集合下载用的地址
String[] downUrl=new String[]{"http://www.izaodao.com/app/izaodao_app.apk",
"http://download.fir.im/v2/app/install/572eec6fe75e2d7a05000008?download_token=572bcb03dad2eed7c758670fd23b5ac4"};
for (int i = 0; i < downUrl.length; i++) {
File outputFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"test"+i + ".apk");
//又一个心类,看参数,就应该知道,应该是一个 记录下载文件信息的类
DownInfo apkApi=new DownInfo(downUrl[i]);
apkApi.setId(i);
apkApi.setState(DownState.START);
apkApi.setSavePath(outputFile.getAbsolutePath());
dbUtil.save(apkApi);
}
// 最后来个填充。。。然后。。。一个列表出来了
listData=dbUtil.queryDownAll();
}
}
/*加载控件 其实就是想,证明我上面说的 ,listData加数据的*/
private void initWidget(){
EasyRecyclerView recyclerView=(EasyRecyclerView)findViewById(R.id.rv);
DownAdapter adapter=new DownAdapter(this);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
adapter.addAll(listData);
}
DbDownUtil主要代码
// 看过帖子1的,,是不是非常眼熟,,这个类,没错,就是一个文件操作类。操作数据库的。
public class DbDownUtil {
private static DbDownUtil db;
private final static String dbName = "tests_db";
private DaoMaster.DevOpenHelper openHelper;
private Context context;
.......都是增删改查的文件不重要,就是下载数据记录起来的。
DownInfo 下载文件信息 看见内容我就不用解释了吧GeenDao的操作实体对象类 @Transient 不存储在数据库中
@Entity
public class DownInfo{
@Id
private long id;
/*存储位置*/
private String savePath;
/*文件总长度*/
private long countLength;
/*下载长度*/
private long readLength;
/*下载唯一的HttpService 其实url被观察者接口 重点-----重点*/
@Transient
private HttpDownService service;
/*回调监听 重点-----重点*/
@Transient
private HttpDownOnNextListener listener;
/*超时设置*/
private int connectonTime=6;
/*state状态数据库保存*/
private int stateInte;
/*url*/
private String url;
以上都是文件记录,并没有下载的方法,下载事件其实是在列表总。
DownAdapter->DownHolder(Holder我不介绍了吧,事件都在他这里)
//要看文件先看头 很明显 这里有“单击事件”
public class DownHolder extends BaseViewHolder<DownInfo> implements View.OnClickListener{
private TextView tvMsg;
//进度条
private NumberProgressBar progressBar;
//下载的文件信息
private DownInfo apkApi;
//真正的下载处理方法
private HttpDownManager manager;
//重点这这个方法 因为他一直在用 manager。。。
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_rx_down:
if(apkApi.getState()!= DownState.FINISH){
manager.startDown(apkApi);
}
break;
case R.id.btn_rx_pause:
manager.pause(apkApi);
break;
}
}
/*下载回调 不用多解释了吧,最终的控制界面的*/
HttpDownOnNextListener<DownInfo> httpProgressOnNextListener=new HttpDownOnNextListener<DownInfo>() {
@Override
public void onNext(DownInfo baseDownEntity) {
tvMsg.setText("提示:下载完成");
Toast.makeText(getContext(),baseDownEntity.getSavePath(),Toast.LENGTH_SHORT).show();
}
@Override
public void onStart() {
tvMsg.setText("提示:开始下载");
}
@Override
public void onComplete() {
tvMsg.setText("提示:下载结束");
}
@Override
public void onError(Throwable e) {
super.onError(e);
tvMsg.setText("失败:"+e.toString());
}
@Override
public void onPuase() {
super.onPuase();
tvMsg.setText("提示:暂停");
}
@Override
public void onStop() {
super.onStop();
}
@Override
public void updateProgress(long readLength, long countLength) {
tvMsg.setText("提示:下载中");
progressBar.setMax((int) countLength);
progressBar.setProgress((int) readLength);
}
};
HttpDownManager 下载的操作方法
/*记录下载数据 set集合信息*/
private Set<DownInfo> downInfos;
/*回调sub队列*/
private HashMap<String,ProgressDownSubscriber> subMap;
/*单利对象*/
private volatile static HttpDownManager INSTANCE;
/*数据库类*/
private DbDownUtil db;
// 重点代码。。 再来一次,下载配置。。。
/**
* 开始下载
*/
public void startDown(final DownInfo info){
/*正在下载不处理*/
if(info==null||subMap.get(info.getUrl())!=null){
subMap.get(info.getUrl()).setDownInfo(info);
return;
}
/*添加回调处理类 这个类就是一个观察者类,,最终的解释*/
ProgressDownSubscriber subscriber=new ProgressDownSubscriber(info);
/*记录回调sub*/
subMap.put(info.getUrl(),subscriber);
/*获取service,多次请求公用一个sercie*/
HttpDownService httpService;
// 如果这个下载链接在原有的类表中已经存在了。把下载接口取出来。
if(downInfos.contains(info)){
httpService=info.getService();
}else{
//创建一个下载拦截器 这个比较重要,进度,,就靠他了。
DownloadInterceptor interceptor = new DownloadInterceptor(subscriber);
OkHttpClient.Builder builder = new OkHttpClient.Builder();
//手动创建一个OkHttpClient并设置超时时间
builder.connectTimeout(info.getConnectonTime(), TimeUnit.SECONDS);
builder.addInterceptor(interceptor);
// 链接OKhttp和rxjava的功臣
Retrofit retrofit = new Retrofit.Builder()
.client(builder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(AppUtil.getBasUrl(info.getUrl()))
.build();
httpService= retrofit.create(HttpDownService.class);
info.setService(httpService);
//把新建的对象,,放到类表里 一方便下次使用
downInfos.add(info);
}
/*得到rx对象-上一次下载的状态*/
httpService.download("bytes=" + info.getReadLength() + "-",info.getUrl())
/*指定线程*/
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
/*失败后的retry配置*/
.retryWhen(new RetryWhenNetworkException())
/*读取下载写入文件*/
.map(new Func1<ResponseBody, DownInfo>() {
@Override
public DownInfo call(ResponseBody responseBody) {
try {
AppUtil.writeCache(responseBody,new File(info.getSavePath()),info);
} catch (IOException e) {
/*失败抛出异常*/
throw new HttpTimeException(e.getMessage());
}
return info;
}
})
/*回调线程*/
.observeOn(AndroidSchedulers.mainThread())
/*数据回调*/
.subscribe(subscriber);
}
ProgressDownSubscriber
// 一个观察者,而且还有监听 其实这个监听就是Downinfo中的监听,在adapter中实现的。
public class ProgressDownSubscriber<T> extends Subscriber<T> implements DownloadProgressListener
//弱引用结果回调
private SoftReference<HttpDownOnNextListener> mSubscriberOnNextListener;
/*下载数据*/
private DownInfo downInfo;
// 其实是把监听接口转移出去 给DownloadProgressListener。
@Override
public void update(long read, long count, boolean done) {
if(downInfo.getCountLength()>count){
read=downInfo.getCountLength()-count+read;
}else{
downInfo.setCountLength(count);
}
downInfo.setReadLength(read);
if (mSubscriberOnNextListener.get() != null) {
/*接受进度消息,造成UI阻塞,如果不需要显示进度可去掉实现逻辑,减少压力*/
rx.Observable.just(read).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Long>() {
@Override
public void call(Long aLong) {
/*如果暂停或者停止状态延迟,不需要继续发送回调,影响显示*/
if(downInfo.getState()==DownState.PAUSE||downInfo.getState()==DownState.STOP)return;
downInfo.setState(DownState.DOWN);
mSubscriberOnNextListener.get().updateProgress(aLong,downInfo.getCountLength());
}
});
}
}
// 解释一下两者的关系,先会执行onNext接受数据,在会执行onCompleted 表示结束,可以在所以在前面的函数回调中可以看到的是 这两个方法都是一样内容显示
public void onCompleted()
public void onNext(T t)
...内容其实都是围绕,,观察者方法写的。
DownloadInterceptor
public class DownloadInterceptor implements Interceptor {
private DownloadProgressListener listener;
public DownloadInterceptor(DownloadProgressListener listener) {
this.listener = listener;
}
@Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
//刚才说的那个接口转换的过程在这里,是body要用
return originalResponse.newBuilder()
.body(new DownloadResponseBody(originalResponse.body(), listener))
.build();
}
}
DownloadResponseBody 核心代码
其实就是一个流文件的写入
private Source source(Source source) {
return new ForwardingSource(source) {
long totalBytesRead = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
// read() returns the number of bytes read, or -1 if this source is exhausted.
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
if (null != progressListener) {
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
}
return bytesRead;
}
};
}
AppUtil 真正的读写文件在这里,,HttpDownManager中的使用了它前面的都是数据
/**
* 写入文件
* @param file
* @param info
* @throws IOException
*/
public static void writeCache(ResponseBody responseBody, File file, DownInfo info) throws IOException{
if (!file.getParentFile().exists())
file.getParentFile().mkdirs();
long allLength;
if (info.getCountLength()==0){
allLength=responseBody.contentLength();
}else{
allLength=info.getCountLength();
}
FileChannel channelOut = null;
// 可以控制文件指针的类 其实就是可以断点续传的原因
RandomAccessFile randomAccessFile = null;
randomAccessFile = new RandomAccessFile(file, "rwd");
channelOut = randomAccessFile.getChannel();
//java大文件读写操作 用这个,效率高。百度的。。
MappedByteBuffer mappedBuffer = channelOut.map(FileChannel.MapMode.READ_WRITE,
info.getReadLength(),allLength-info.getReadLength());
byte[] buffer = new byte[1024*8];
int len;
int record = 0;
while ((len = responseBody.byteStream().read(buffer)) != -1) {
mappedBuffer.put(buffer, 0, len);
record += len;
}
responseBody.byteStream().close();
if (channelOut != null) {
channelOut.close();
}
if (randomAccessFile != null) {
randomAccessFile.close();
}
}
全部分析完毕,简单,解释一下,文件拦截器是为了下载做的一个格式化。被监听着的时候就已经开始文件读写操作了。然后把最终的控制进度条的操作放在监听着中。
rxjava 被监听者做文件格式处理,监听者做最终的界面修改·~~
整合的整个类库已经全部搞定了。之后我想分析一下~OKGo类库,看着原作者分装的也是棒棒的