? ? ? app內(nèi)更新版本是每個(gè)app基本必備的技能,但一般來說實(shí)現(xiàn)更新下載就已經(jīng)很ok了,但是當(dāng)你下載更新的時(shí)候,突然之間網(wǎng)絡(luò)不太好,下載中斷怎么辦? 再次下載時(shí)還要重新開始下?
如果這個(gè)apk有點(diǎn)大的話,或者網(wǎng)絡(luò)環(huán)境不好的話,有可能很長(zhǎng)時(shí)間下載不下來,安裝不上,那是很坑的一件事;
所以為了防止這種情況,斷點(diǎn)續(xù)傳就能體現(xiàn)出它的價(jià)值了,網(wǎng)絡(luò)環(huán)境不太好,重新下載?不存在的;
上次下載到什么位置,這次就從什么位置開始繼續(xù)下載...?
下載完成之后用戶手抖了點(diǎn)擊個(gè)返回...涼涼...? ?是不是還要重新下載一次才能繼續(xù)安裝?
NO NO NO, 只要你不結(jié)束后臺(tái)程序,那么沒問題,只要文件還在就可以直接安裝。完全不需要你重新下載...
/**
* 從服務(wù)器下載最新更新文件
*
* @return
* @throws Exception
*/
private void downloadFile() {
? ? if (StringUtils.isEmpty(downURL)) {
? ? ? ? LogUtils.e("----url is null");
? ? ? ? return;
? ? }
? ? long contentLength = getContentLength();
? ? InputStream inputStream = null;
? ? HttpURLConnection connection;
? ? try {
? ? ? ? URL url = new URL(downURL);
? ? ? ? connection = (HttpURLConnection) url.openConnection();
? ? ? ? connection.setRequestMethod("GET");
? ? ? ? connection.setConnectTimeout(8000);
? ? ? ? connection.setReadTimeout(8000);
? ? ? ? totalSize = mSPUtils.getLong(TOTALSIZE);
? ? ? ? downSize = mSPUtils.getLong(DOWN_SIZE);
? ? ? ? /**
? ? ? ? * 下載進(jìn)度與總大小相同或小于1 說明下載已完成或者第一次下載
? ? ? ? */
? ? ? ? if (downSize == totalSize || downSize < 1) {
? ? ? ? ? ? downSize = 0;
? ? ? ? }
? ? ? ? /**
? ? ? ? * 判斷下載的文件大小跟上次下載的文件大小 是否 相同
? ? ? ? * 如果相同:則下載的是一個(gè)文件,可以直接從上次下載的地方繼續(xù)下載 (斷點(diǎn)續(xù)傳)
? ? ? ? * 不相同: 則下載的不是一個(gè)文件,需要從新開始下載
? ? ? ? */
? ? ? ? if (totalSize != contentLength || totalSize < 1) {
? ? ? ? ? ? downSize = 0;
? ? ? ? ? ? totalSize = contentLength;
? ? ? ? }
? ? ? ? mSPUtils.put(TOTALSIZE, contentLength);
? ? ? ? //設(shè)置文件請(qǐng)求的位置 請(qǐng)求頭格式 鍵值對(duì) key : value range: bytes = 開始的位置 - 結(jié)束的位置
? ? ? ? connection.setRequestProperty("Range", "bytes=" + downSize + "-" + totalSize);
? ? ? ? mDownloadDialog.getmProgressBar().setMax((int) totalSize);
? ? ? ? mDownloadDialog.getmProgressBar().setProgress((int) downSize);
? ? ? ? // 如果以地址最后的文件命名,那么刪除的時(shí)候就取這個(gè)名字刪除
? ? ? ? String appName = downURL.substring(downURL.lastIndexOf("/"));
? ? ? ? filePath = SD_FOLDER + appName;
? ? ? ? File file = new File(filePath);
? ? ? ? if (!file.getParentFile().exists()) {//判斷文件目錄是否存在 ?
? ? ? ? ? ? file.getParentFile().mkdirs();
? ? ? ? }
? ? ? ? mAccessFile = new RandomAccessFile(file, "rw");
? ? ? ? mAccessFile.seek(downSize);
? ? ? ? inputStream = connection.getInputStream();
? ? ? ? byte[] buffer = new byte[1024];
? ? ? ? int len;
? ? ? ? while ((len = inputStream.read(buffer)) != -1 && isDownLoder) {
? ? ? ? ? ? mAccessFile.write(buffer, 0, len);
? ? ? ? ? ? downSize += len;
? ? ? ? ? ? // 獲取當(dāng)前下載量
? ? ? ? ? ? mHandler.sendEmptyMessage(DOWNLOAD_PROGRESS);
? ? ? ? }
? ? ? ? mHandler.sendMessage(mHandler.obtainMessage(DOWNLOAD_SUCCESS, file));
? ? } catch (IOException e) {
? ? ? ? e.printStackTrace();
? ? ? ? if (isNetworkConnected(mContext)) {
? ? ? ? ? ? if (isConnect) {
? ? ? ? ? ? ? ? mHandler.sendEmptyMessage(DOWNLOAD_CONNECT);
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? mHandler.sendEmptyMessage(DOWNLOAD_FAIL);
? ? ? ? ? ? }
? ? ? ? } else {
? ? ? ? ? ? mHandler.sendEmptyMessage(DOWNLOAD_FAIL);
? ? ? ? }
? ? } finally {
? ? ? ? try {
? ? ? ? ? ? if (mAccessFile != null) {
? ? ? ? ? ? ? ? mAccessFile.close();
? ? ? ? ? ? }
? ? ? ? ? ? if (inputStream != null) {
? ? ? ? ? ? ? ? inputStream.close();
? ? ? ? ? ? }
? ? ? ? } catch (IOException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
??有個(gè)坑需要注意一下:
RandomAccessFile 的兩個(gè)參數(shù) 我用的是文件和“rw”,后面的這個(gè)參數(shù)可讀寫,rwd也可以
但第一個(gè)參數(shù)是文件,創(chuàng)建文件時(shí),不能只給路徑,不然實(shí)例RandomAccessFile 會(huì)報(bào)錯(cuò),當(dāng)初一直報(bào)錯(cuò),一直爽到不報(bào)錯(cuò)為止
不能在setRequestProperty方法前調(diào)用getContentLength,不然給報(bào)錯(cuò):連接不能設(shè)置頭信息
所以你還得先獲取大小,然后才能比較文件大小,需要判斷下載的文件是否跟上一次下載的文件大小相同,如果相同說明是同一個(gè)文件,可以斷點(diǎn)續(xù)傳,從上次的位置開始下載,如果文件大小不一樣,你就要從頭開始下載了,不然下載的都不是同一個(gè)文件,那文件損壞當(dāng)然安裝不了。
所以你得用到保存了, 我借鑒網(wǎng)上的例子大多數(shù)用的是數(shù)據(jù)庫(kù),我看的有點(diǎn)難受,然后我就用了一個(gè)工具類保存了上次下載的位置和文件的總大小
當(dāng)下載的時(shí)候在判斷下載的位置是否等于總大小,等于那就是下載完成了? 小于1 那說明第一次下載
應(yīng)該能看懂的? 就這么點(diǎn)代碼
還有提示一下后臺(tái)得配置一下
connection.setRequestProperty("Range", "bytes=" + downSize + "-" + totalSize);
后臺(tái)返回的網(wǎng)址得支持?jǐn)帱c(diǎn)續(xù)傳;
我當(dāng)初后臺(tái)沒有配置,坑死,調(diào)式半天這個(gè)位置
獲取文件的總大小
?
/**
* 文件的總大小
*
* @return 文件的總大小
*/
private int getContentLength() {
? ? HttpURLConnection connection = null;
? ? try {
? ? ? ? URL url = new URL(downURL);
? ? ? ? connection = (HttpURLConnection) url.openConnection();
? ? ? ? connection.setConnectTimeout(5000);
? ? ? ? connection.setReadTimeout(5000);
? ? ? ? if (connection.getResponseCode() == 200) {
? ? ? ? ? ? return connection.getContentLength();
? ? ? ? }
? ? } catch (IOException e) {
? ? ? ? e.printStackTrace();
? ? } finally {
? ? ? ? try { //釋放資源
? ? ? ? ? ? if (connection != null) {
? ? ? ? ? ? ? ? connection.disconnect();
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
? ? return 0;
}
?
開始與暫停
?
//控制開始與下載
public void start() {
? ? downSize = SPStaticUtils.getInt(DOWN_SIZE);
? ? totalSize = SPStaticUtils.getInt(TOTALSIZE);
? ? // 如果下載完文件返回之后不用重新下載 直接安裝(結(jié)束后臺(tái)除外)
? ? if (mFile != null && downSize == totalSize){
? ? ? ? installApk(mContext, mFile);
? ? ? ? return;
? ? }
? ? isDownLoder = true;
? ? mProgressDialog.show();
? ? if (mDownLoadThread == null) {
? ? ? ? mDownLoadThread = new DownLoadThread();
? ? }
? ? mThreadPool.execute(mDownLoadThread);
}
//控制暫停下載
public void stop() {
? ? isDownLoder = false;
? ? SPStaticUtils.put(DOWN_SIZE, downSize);
? ? mProgressDialog.dismiss();
? ? if (mDownLoadThread != null) {
? ? ? ? mThreadPool.remove(mDownLoadThread);
? ? ? ? mDownLoadThread = null;
? ? }
}
?
?
/**
* 注意:自己配置fileprovider
*
* 安裝apk
*/
public static void installApk(Context context, File file) {
? ? if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
? ? ? ? if (!isHasInstallPermissionWithO(context)) {
? ? ? ? ? ? startInstallPermissionSettingActivity(context);
? ? ? ? ? ? return;
? ? ? ? }
? ? }
? ? Intent intent = new Intent(Intent.ACTION_VIEW);
? ? Uri data;
? ? // 判斷版本大于等于7.0
? ? if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
? ? ? ? // "com.demo.download.fileprovider"即是在清單文件中配置的authorities
? ? ? ? data = FileProvider.getUriForFile(context, "com.demo.download.fileprovider", file);
? ? ? ? // 給目標(biāo)應(yīng)用一個(gè)臨時(shí)授權(quán)
? ? ? ? intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
? ? } else {
? ? ? ? data = Uri.fromFile(file);
? ? }
? ? intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// 防止打不開應(yīng)用
? ? intent.setDataAndType(data, "application/vnd.android.package-archive");
? ? context.startActivity(intent);
}
@RequiresApi(api = Build.VERSION_CODES.O)
public static boolean isHasInstallPermissionWithO(Context context) {
? ? if (context == null) return false;
? ? return context.getPackageManager().canRequestPackageInstalls();
}
/**
* 開啟設(shè)置安裝未知來源應(yīng)用權(quán)限界面
*
* @param context
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private static void startInstallPermissionSettingActivity(Context context) {
? ? if (context == null) return;
? ? Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
? ? ((Activity) context).startActivityForResult(intent, REQUEST_CODE_APP_INSTALL);
}
/**
* byte(字節(jié))根據(jù)長(zhǎng)度轉(zhuǎn)成kb(千字節(jié))和mb(兆字節(jié))
*
* @param bytes
* @return
*/
private static String bytes2kb(long bytes) {
? ? BigDecimal filesize = new BigDecimal(bytes);
? ? BigDecimal megabyte = new BigDecimal(1024 * 1024);
? ? float returnValue = filesize.divide(megabyte, 2, BigDecimal.ROUND_UP).floatValue();
? ? if (returnValue > 1)
? ? ? ? return (returnValue + "MB");
? ? BigDecimal kilobyte = new BigDecimal(1024);
? ? returnValue = filesize.divide(kilobyte, 2, BigDecimal.ROUND_UP).floatValue();
? ? return (returnValue + "KB");
}
?
上面是下載類
下面是activity中安裝回調(diào)
當(dāng)然你下載時(shí)調(diào)用很方便只需要一行代碼即可:
AppInnerDownLoder.getInstance(this).setDownUrl(path).start();
只需要傳Context 上下文,下載的url
?
@Override
public void onClick(View v) {
? ? switch (v.getId()) {
? ? ? ? case R.id.start:
? ? ? ? ? ? AppInnerDownLoder.getInstance(this).setDownUrl(path).start();
? ? ? ? ? ? break;
? ? ? ? case R.id.stop:
? ? ? ? ? ? AppInnerDownLoder.getInstance(this).stop();
? ? ? ? ? ? break;
? ? }
}
?
@Override
? ? protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
? ? ? ? super.onActivityResult(requestCode, resultCode, data);
? ? ? ? // 8.0以上版本安裝apk 獲取未知來源為true才會(huì)繼續(xù)下載安裝
? ? ? ? if (requestCode == AppInnerDownLoder.REQUEST_CODE_APP_INSTALL) {
//? ? ? ? ? ? if (StringUtils.isEmpty(path)) return;
? ? ? ? ? ? if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
? ? ? ? ? ? ? ? if (AppInnerDownLoder.isHasInstallPermissionWithO(this)) {
? ? ? ? ? ? ? ? ? ? if (AppInnerDownLoder.getInstance(this).getmFile() != null) {
? ? ? ? ? ? ? ? ? ? ? ? AppInnerDownLoder.installApk(this, AppInnerDownLoder.getInstance(this).getmFile());
? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? AppInnerDownLoder.getInstance(this).setDownUrl(path).start();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? }
下載鏈接:https://github.com/xiaobinAndroid421726260/Android_DownloadFile_Demo2019_6_11