断点下载的原理:
什么叫多线程断点下载?
断点代表下载可以暂停,当下次继续下载的时候可以从之前下载的地方继续下载而不是重新下载。
那么怎么去实现断点下载呢?假设需要从服务器下载一个软件,将这个软件分为三等份分给三个线程下载
需要的类:HttpURLConnection RandomAccessFile随机读取和写入的文件(看成File就是,不过可以指定读和写的位置)
连接服务器返回的状态码:int code=connection.getResponseCode();//一个线程时为200代表连接成功 多个线程时返回206代表连接成功
下载步骤:
1.在android的内存中首先创建一个空文件,空白文件,文件大小和服务器文件的大小一模一样 (获取服务器文件大小的方法:connection.getContentLength();创建一个空白文件的方法:raf.setLength(size); )
2.开启3个线程分别去下载服务器上对应的资源,在这里就会出现一个问题,假设这个文件的大小为10byte,分为三等份怎么分?
第一个线程:从0byte ~ 2byte
第二个线程: 从3byte ~ 5byte
第三个线程:从6byte ~ 10byte 在这里你就会想了,这样不就不是均匀的三等份了吗?因为这里总大小只10byte,当这个文件总大小为100000byte时,最后一个线性多下载一两byte,也就可以忽略了,关系如下:假设文件的大小为 leng byte,分为三个线程下载 每个线性下载 size= (int) leng/3
第一个线程:从0*size ~ 1*size-1
第二个线程: 从1*size ~ 2*size-1
第三个线程:从2*size ~ leng
3.使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,如:
//指定获取服务器数据的范围 "Range", "bytes=100-200"这个为Http的协议
connection.setRequestProperty("Range", "bytes=100-200");//范围,从100byte读取到200byte
4.保存文件,使用RandomAccessFile类指定每条线程从本地的什么位置开始写入数据:
//根据指定的服务器数据的范围下载到本地 通过connection对象获取io流对象
RandomAccessFile raf=new RandomAccessFile(new File("ff.exe"), "wrd");
//本地指定开始下载的位置,然后获取读写流操作就是
raf.seek(50);//指定文件从50byte开始下载,这样就可以实现下载的暂停,需要注意的地方就是需要记录每个线程下载的字节数
如果自己去实现这样的功能,原理就如上所示,不过实现起来代码量也不小,而网上有开源代码框架,我们可以直接去使用,使得更加方便:
本次实现断点下载功能的网址:https://github.com/wyouflf/xUtils 详情可见这个网址
该框架api地址:
//注意下面的地址写死了 需要的两个权限为:
<uses-permission android:name="android.permission.INTERNET" /> 访问网络
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 写外部存储
//断点下载:需要的外包xUtils-2.4.7.jar XUtils2
public class MainActivity extends Activity {
private TextView tv_info;
private EditText et_path;
private Button download;
private Button stop;
private HttpHandler handler=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_info = (TextView) findViewById(R.id.tv_info);
et_path = (EditText) findViewById(R.id.et_path);
download = (Button) findViewById(R.id.download);
stop = (Button) findViewById(R.id.stop);
download.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String path=et_path.getText().toString();
if(TextUtils.isEmpty(path)){
Toast.makeText(getApplicationContext(), "请输入下载地址", 0).show();
return;
}
//单击事件 开始下载
HttpUtils http = new HttpUtils();
handler= http.download("http://192.168.56.1:8080/mp3/a1425885540.exe",//服务器的地址
"/sdcard/test.exe",//下载的文件保存到这个位置,并且可以自己命名
true, // 如果目标文件存在,接着未完成的部分继续下载。服务器不支持RANGE时将从新下载。
true, // 如果从请求返回信息中获取到文件名,下载完成后自动重命名。
new RequestCallBack<File>() {
@Override
public void onStart() {
tv_info.setText("正在连接-conn...");
}
@Override
public void onLoading(long total, long current, boolean isUploading) {
tv_info.setText("正在下载--"+current + "/" + total);//总大小与当前下载的,进行进度更新
}
@Override
public void onSuccess(ResponseInfo<File> responseInfo) {
tv_info.setText("下载成功--下载保存的路径:" + responseInfo.result.getPath());
}
@Override
public void onFailure(HttpException error, String msg) {
tv_info.setText("下载失败:--"+msg);
}
});
}
});
stop.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//停止下载
if(handler!=null){
handler.stop();
}
}
});
}
}
由于上面使用的XUtils2,内部网络请求使用的HttpClient,但是安卓6.0后已经不能使用HttpClient了,所以只能使用XUtils3版本:https://github.com/wyouflf/xUtils3, 它替换HttpClient为UrlConnection
XUtils2中注解的使用见: 反射的使用,注释注解的使用,XUtils注解的使用
XUtils2中加载网络图片BitmapUtils 的使用: 具体可以看XUtils的api
BitmapUtils bitmapUtils = new BitmapUtils(this);
//配置图片的显示配置信息
BitmapDisplayConfig bitmapDisplayConfig = new BitmapDisplayConfig();
//设置图片大小
bitmapDisplayConfig.setBitmapMaxSize(new BitmapSize(200,200));
//缩放动画
ScaleAnimation scaleAnimation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
//设置持续时间
scaleAnimation.setDuration(3000);
//设置重复次数
scaleAnimation.setRepeatCount(2);
//设置重复模式
scaleAnimation.setRepeatMode(Animation.REVERSE);
//保持状态
scaleAnimation.setFillAfter(true);
//设置显示的动画
bitmapDisplayConfig.setAnimation(scaleAnimation);
//bitmapUtils.display(img,"assets/pp.jpg");加载本地图片
//bitmapUtils.display(img,"地址");加载网络图片
//设置显示参数
//bitmapUtils.display(img,imgUrl,bitmapDisplayConfig);
//加载图片后回调监听, 会回传imageview (要显示图片的控件),bitmap 找到的图片 BitmapDisplayConfig配置
bitmapUtils.display(img, imgUrl, new BitmapLoadCallBack<ImageView>() {
@Override
public void onLoadCompleted(ImageView imageView, String s, Bitmap bitmap, BitmapDisplayConfig bitmapDisplayConfig, BitmapLoadFrom bitmapLoadFrom) {
imageView.setImageBitmap(bitmap);
//主线程执行的
}
//加载失败的回调
@Override
public void onLoadFailed(ImageView imageView, String s, Drawable drawable) {
}
});
XUtils2的Get与Post请求: HttpUtils使用
httpUtils仅仅支持安卓6.0以下应用,因为里面使用的都是HttpClient的网络请求方式,而google公司已经将其弃用.
普通Get方法:
//声名httputils工具
HttpUtils httpUtils = new HttpUtils();
//发送请求
httpUtils.send(HttpRequest.HttpMethod.GET, pathGet, new RequestCallBack<String>() {
//*请求成功的回调*//*
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
//获取数据
String string = responseInfo.result;
Log.i("AAA",string);
Log.i("AAA","ThreadName:-" + Thread.currentThread().getName() + "-threadId-" + Thread.currentThread().getId());
}
@Override
public void onFailure(HttpException e, String s) {
}
});
}
post方法:
HttpUtils httpUtils = new HttpUtils();
//post提交的字段:pageNo=1&pageSize=20&serialIds=2143,3404&v=4.0.0
// String pathPost = "http://mrobot.pcauto.com.cn/v2/cms/channels/1?";
RequestParams requestParams = new RequestParams();
//设置post请求参数
requestParams.addBodyParameter("pageNo", "1");
requestParams.addBodyParameter("pageSize", "20");
requestParams.addBodyParameter("serialIds", "2143,3404");
requestParams.addBodyParameter("v", "4.0.0");
/* com.lidroid.xutils.http.client.HttpRequest.HttpMethod method, 请求类型 Get,Post..
@NotNull java.lang.String url, 网络地址
com.lidroid.xutils.http.RequestParams params, 请求参数
com.lidroid.xutils.http.callback.RequestCallBack<T> callBack 请求成功的回调
httpUtils.send()*/
httpUtils.send(HttpRequest.HttpMethod.POST, pathPost, requestParams, new RequestCallBack<String>() {
@Override
public void onSuccess(ResponseInfo<String> responseInfo) {
Log.i("AAA", "onSuccess: " + responseInfo.result);
}
@Override
public void onFailure(HttpException e, String s) {
}
});
}
XUtils2对数据库进行操作:
import com.lidroid.xutils.db.annotation.Check;
import com.lidroid.xutils.db.annotation.Column;
import com.lidroid.xutils.db.annotation.Id;
import com.lidroid.xutils.db.annotation.Table;
/**
* 作为XUtils创建表的实体类
*/
//指定表名
@Table(name = "student")
public class Student {
@Override
public String toString() {
return "Student{" +
"_id=" + _id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
//指定列名
@Column(column = "_id")
//指定是主健,默认自增
@Id(column = "_id")
private int _id;
@Column(column = "name")
private String name;
@Column(column = "age")
//限制条件
@Check("age > 18")
private int age;
@Column(column = "sex")
@Check("sex in ('男','女')")
private String sex;
public int get_id() {
return _id;
}
public void set_id(int _id) {
this._id = _id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
增删改查:
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import com.lidroid.xutils.DbUtils;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.db.sqlite.Selector;
import com.lidroid.xutils.db.sqlite.WhereBuilder;
import com.lidroid.xutils.exception.DbException;
import com.lidroid.xutils.view.annotation.ContentView;
import com.lidroid.xutils.view.annotation.event.OnClick;
import com.my.test.xutils2_dbutils.bean.Student;
import java.util.ArrayList;
import java.util.List;
@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity implements DbUtils.DbUpgradeListener{
private DbUtils dbUtils;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewUtils.inject(this);
/*android.content.Context context,
java.lang.String dbDir, 数据库存放的路径
java.lang.String dbName, 数据库的名字
int dbVersion, 当前数据库的版本
DbUtils.DbUpgradeListener dbUpgradeListene 当数据库升级的时候调用*/
//创建数据库
dbUtils = DbUtils.create(this, getCacheDir().getAbsolutePath(), "dbUtils.db", 1, this);
}
/*更新数据库*/
@OnClick(R.id.btn_updataDb)
public void onUPdataDb(View v){
DbUtils.create(this, getCacheDir().getAbsolutePath(), "dbUtils.db", 2, this);
}
/*跟新数据库的回调*/
@Override
public void onUpgrade(DbUtils dbUtils, int i, int i1) {
//i是oldVersion i1是newVierson
Log.i("AAA", "onUpgrade: i-" + i + "-i1-" + i1 );
}
/*创建表*/
@OnClick(R.id.btn_createTable)
public void onCreateTable(View v){
// create table 表名 (属性1 约束,属性2 约束,...)
// java.lang.Class<?> entityType 数据库的实体模型
try {
dbUtils.createTableIfNotExist(Student.class);
} catch (DbException e) {
e.printStackTrace();
}
}
@OnClick(R.id.btn_insertData)
public void onInsertData(View v){
/* Student student = new Student();
student.setAge(19);
student.setName("hello2");
student.setSex("女");
//插入一条数据
try {
dbUtils.save(student);
} catch (DbException e) {
e.printStackTrace();
}*/
//插入多条数据
ArrayList<Student> studentsList = new ArrayList<>();
Student student;
for (int i = 40; i > 0; i--) {
student = new Student();
student.setAge(10+ i);
student.setName("小乔" + i);
if(i%2 == 0){//男
student.setSex("女");
}else{
student.setSex("男");
}
studentsList.add(student);
}
//放置多条数据,输入数据当第一次出现错误后,后面全部不能插入
try {
dbUtils.saveAll(studentsList);
} catch (DbException e) {
e.printStackTrace();
}
}
/*查询语句*/
@OnClick(R.id.btn_queryData)
public void onQuertyData(View v){
//select 列 from 表名 where age < 20
/* try {
//查询
List<DbModel> dbModelAll = dbUtils.findDbModelAll(DbModelSelector.
from(Student.class). //表示从那个表查询
select("_id", "name", "sex", "age"). //表示查询的字段
where("age", "<", "22")); //表示查询条件
for (int i = 0; i < dbModelAll.size(); i++) {
String name = dbModelAll.get(i).getString("name");
String age = dbModelAll.get(i).getString("age");
String sex = dbModelAll.get(i).getString("sex");
Log.i("tag", "onQuertyData: i -name- " + name + "-age-" + age + "-sex-" + sex);
}
} catch (DbException e) {
e.printStackTrace();
}*/
//查询所有数据
// List<Student> studetList = dbUtils.findAll(Student.class);
// Log.i("tag", "List; " + studetList.toString() );
try {
List<Object> studentList = dbUtils.findAll(Selector.from(Student.class).where("age", "<", "20"));//age小于20
Log.i("tag", "List; " + studentList.toString() );
} catch (DbException e) {
e.printStackTrace();
}
}
/*修改数据*/
@OnClick(R.id.btn_updatadata)
public void onUPDataData(View view){
// Student student = new Student();
try {
//Student student = dbUtils.findFirst(Student.class);
Student student = new Student();
student.setAge(35);
student.setName("孙权**");
student.setSex("男");
//更新数据,数据来源于自己的数据库对象
//dbUtils.update(student,"name","age");
//根据条件来修改
dbUtils.update(student, WhereBuilder.b("age","<","22"),"name","age");
//更新数据集合
//dbUtils.updateAll();
} catch (DbException e) {
e.printStackTrace();
}
}
@OnClick(R.id.btn_deletedata)
public void onDeleteData(View v){
try {
// List<Student> all = dbUtils.findAll(Student.class);
//dbUtils.delete(all.get(8));
dbUtils.delete(Student.class,WhereBuilder.b("age","=","35"));
//删除全部数据
//dbUtils.deleteAll(Student.class);
} catch (DbException e) {
e.printStackTrace();
}
}
}