什么是断点续传?
断点续传其实正如字面意思,就是在下载的断开点继续开始传输,不用再从头开始。所以理解断点续传的核心后,发现其实和很简单,关键就在于对传输中断点的把握。
原理:
断点续传的关键是断点,所以在制定传输协议的时候要设计好,如上图,我自定义了一个交互协议,每次下载请求都会带上下载的起始点,这样就可以支持从断点下载了,其实HTTP里的断点续传也是这个原理,在HTTP的头里有个可选的字段RANGE,表示下载的范围,下面是我用JAVA语言实现的下载断点续传示例。
首先:设置一个点击事件,检测最新的版本号,返回版本号后,在客服端比较更新即可。
分包:(利用MVP模式,访问接口)为了不遗漏,我会把所有页面代码粘进来
接下来,就从版本检测开始一步一步走:
MainActivity:(抽了基类,基类在下面)
package com.example.version; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.support.v7.app.AlertDialog; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import com.example.version.bean.VersionBean; import com.example.version.inter.IView; import com.example.version.presenter.UpdataPresenter; import com.example.version.presenter.VersionPresenter; import com.example.version.utils.BaseActivity; import com.example.version.utils.FileMd5Utils; import com.example.version.utils.VersionUtils; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import okhttp3.Response; public class MainActivity extends BaseActivity implements IView<VersionBean> { private static final String TAG = "MainActivity"; private VersionPresenter versionPresenter; private Button versionUpdata; private VersionBean versionBean; private UpdataPresenter updataPresenter; private boolean isMust; private File file; private ProgressDialog dialog; private SharedPreferences sp; private long fileLength; private boolean isSaved = false; private String range; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //判断外置存储是否挂载 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { //若挂载 则创建存放位置(SDK中) File externalStorageDirectory = Environment.getExternalStorageDirectory(); //文件的创建地址 String path = externalStorageDirectory.getAbsolutePath() + File.separator + "new.apk"; file = new File(path); //判断文件是否存在 不存在则创建 if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } } //创建一个getSharedPreferences对象,等会存文件大小的值 sp = getSharedPreferences("version", MODE_PRIVATE); //得到当前文件的长度 fileLength = sp.getLong("lenght", 0); //创建P层,得到最新版本的信息 versionPresenter = new VersionPresenter(this); versionPresenter.netVersion(); //创建下载apk的P层 updataPresenter = new UpdataPresenter(new IView<Response>() { @Override public void success(Response response) { //定义一个变量接受当前文件的大小 long contentLength = 0; //判断fileLength的值,如果是第一次就存值,第二次就直接赋值 if (fileLength == 0) { contentLength = response.body().contentLength(); sp.edit().putLong("lenght", contentLength).commit(); isSaved = false; Log.e(TAG, "successqqqqq: "+contentLength ); } else { Log.e(TAG, "successqqqqq: "+contentLength ); isSaved = true; contentLength = fileLength; } //得到文件的输出流 InputStream inputStream = response.body().byteStream(); //调用存储的方法 inputToFile(contentLength, inputStream); } @Override public void error(Throwable t) { Log.e(TAG, "error: " + t.getMessage()); } }); //创建一个ProgressDialog,下载进度条。 dialog = new ProgressDialog(MainActivity.this); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); dialog.setMessage("下载进度"); } private void inputToFile(long contentLength, InputStream inputStream) { // 创建一个byte数组 byte[] bytes = new byte[2048]; // 定义一个长度 int len = 0; // 得到文件的长度 long sum = file.length(); try { // 创建一个RandomAccessFile,随机储存的类 RandomAccessFile raf = new RandomAccessFile(file, "rw"); // 移动到当前已经下载文件的大小位置 raf.seek(sum); // 循环遍历得到输出流的值 while ((len = inputStream.read(bytes)) != -1) { // 把流的大小写入到RandomAccessFile raf.write(bytes, 0, len); sum += len; // 移动到当前已经下载文件的大小位置 raf.seek(sum); // 把下载进度转变成progress值 int i = (int) (sum * 100 / contentLength); dialog.setProgress(i); // 当进度下载到100时 if (i > 99) { // 关流 inputStream.close(); // 进度弹框消失 dialog.dismiss(); // 比较md5,并下载值 isMd5(file); break; } } } catch (Exception e) { e.printStackTrace(); } } /** * 把文件转换成md5值,并于访问网络得到下载信息中的md5值,进行比较 * @param file */ private void isMd5(File file) { String fileMD5 = FileMd5Utils.getFileMD5(file); Log.e(TAG, "fileMD5: " + fileMD5); String md5 = versionBean.getData().getMd5(); Log.e(TAG, "md5: " + md5 ); if (fileMD5.equalsIgnoreCase(md5)) { // 调用系统安装的方法 installApk(file); } else { Toast.makeText(this, "下载版本有误", Toast.LENGTH_SHORT).show(); } } /** * 系统安装的方法 * @param file */ private void installApk(File file) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivityForResult(intent, 12345); System.exit(0); } @Override protected void init() { versionUpdata = findViewById(R.id.version_updata); versionUpdata.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { range = ""; if (isSaved) { range = "bytes=" + file.length() + "-" + fileLength; } else { range = "bytes=" + file.length() + "-"; } // 获得当前版本 int versionCode = VersionUtils.getVersionCode(MainActivity.this); // 判断当前版本号与得到的版本号,判断是否需要更新 if (versionCode < versionBean.getData().getLastVersion()) { // 弹框提示用户 AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setTitle("更新").setMessage("检测到新的版本"); //判断当前是否需要强制更新 if (versionCode < versionBean.getData().getLastVersion()) { isMust = false; //用户点击立即更新的时候,去下载apk文件 builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dia, int which) { dialog.show(); //下载apk文件 updataPresenter.netUpdataVersion(versionBean.getData().getUrl(), range); } //点击外面退出应用 }).setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { finish(); } }); } else { isMust = true; //不是强制更新版本,显示更新或者取消即可 builder.setPositiveButton("更新", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dia, int which) { dialog.show(); //下载apk文件 updataPresenter.netUpdataVersion(versionBean.getData().getUrl(), range); } }).setPositiveButton("取消", null); } AlertDialog adialog = builder.create(); //禁用返回键 adialog.setCanceledOnTouchOutside(isMust); adialog.show(); } } }); } @Override public int getView() { return R.layout.activity_main; } @Override public void success(VersionBean versionBean) { this.versionBean = versionBean; } @Override public void error(Throwable t) { Log.e(TAG, "error: " + t.getMessage()); } }
Activity基类:
package com.example.version.utils; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getView()); init(); } protected abstract void init(); public abstract int getView(); }