diff --git a/README.md b/README.md
index 2993b69..5615307 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
![Image](app/src/main/ic_launcher-web.png)
[![Download](https://img.shields.io/badge/download-App-blue.svg)](https://raw.githubusercontent.com/jenly1314/AppUpdater/master/app/release/app-release.apk)
+[![JCenter](https://img.shields.io/badge/JCenter-1.0.8-46C018.svg)](https://bintray.com/beta/#/jenly/maven/app-updater)
[![JitPack](https://jitpack.io/v/jenly1314/AppUpdater.svg)](https://jitpack.io/#jenly1314/AppUpdater)
[![CI](https://travis-ci.org/jenly1314/AppUpdater.svg?branch=master)](https://travis-ci.org/jenly1314/AppUpdater)
[![CircleCI](https://circleci.com/gh/jenly1314/AppUpdater.svg?style=svg)](https://circleci.com/gh/jenly1314/AppUpdater)
@@ -11,24 +12,25 @@
[![Blog](https://img.shields.io/badge/blog-Jenly-9933CC.svg)](https://jenly1314.github.io/)
[![QQGroup](https://img.shields.io/badge/QQGroup-20867961-blue.svg)](http://shang.qq.com/wpa/qunwpa?idkey=8fcc6a2f88552ea44b1411582c94fd124f7bb3ec227e2a400dbbfaad3dc2f5ad)
-AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版本升级的开源库。(无需担心通知栏适配;无需担心重复点击下载;无需担心App安装等问题;这些AppUpdater都已帮您处理好。)
+AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版本升级的轻量开源库。(无需担心通知栏适配;无需担心重复点击下载;无需担心App安装等问题;这些AppUpdater都已帮您处理好。)
核心库主要包括app-updater和app-dialog。
> 下载更新和弹框提示分开,是因为这本来就是两个逻辑。完全独立开来能有效的解耦。
* app-updater 主要负责后台下载更新App,无需担心下载时各种配置相关的细节,一键傻瓜式升级。
-* app-dialog 主要是提供常用的Dialog和DialogFragment,简化弹框提示,样式支持高度自定义。
+* app-dialog 主要是提供常用的Dialog和DialogFragment,简化弹框提示,布局样式支持自定义。
> app-updater + app-dialog 配合使用,谁用谁知道。
## 功能介绍
- [x] 专注于App更新一键傻瓜式升级
-- [x] 支持下载监听
+- [x] 够轻量,体积小
+- [x] 支持监听下载过程
- [x] 支持下载失败,重新下载
- [x] 支持下载优先取本地缓存
- [x] 支持通知栏提示内容和过程全部可配置
- [x] 支持Android Q(10)
+- [x] 支持取消下载
- [x] 支持使用OkHttpClient下载
-
## Gif 展示
![Image](GIF.gif)
@@ -40,7 +42,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
com.king.app
app-updater
- 1.0.7
+ 1.0.8
pom
@@ -48,7 +50,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
com.king.app
app-dialog
- 1.0.7
+ 1.0.8
pom
```
@@ -57,25 +59,25 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
//----------AndroidX 版本
//app-updater
- implementation 'com.king.app:app-updater:1.0.7-androidx'
+ implementation 'com.king.app:app-updater:1.0.8-androidx'
//app-dialog
- implementation 'com.king.app:app-dialog:1.0.7-androidx'
+ implementation 'com.king.app:app-dialog:1.0.8-androidx'
//----------Android Support 版本
//app-updater
- implementation 'com.king.app:app-updater:1.0.7'
+ implementation 'com.king.app:app-updater:1.0.8'
//app-dialog
- implementation 'com.king.app:app-dialog:1.0.7'
+ implementation 'com.king.app:app-dialog:1.0.8'
```
### Lvy:
```lvy
//app-updater
-
+
//app-dialog
-
+
```
@@ -107,7 +109,6 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
public void onClick(View v) {
new AppUpdater.Builder()
.serUrl(mUrl)
- .setFilename("AppUpdater.apk")
.build(getContext())
.start();
AppDialog.INSTANCE.dismissDialog();
@@ -141,6 +142,10 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
## 版本记录
+#### v1.0.8:2020-1-2
+* 支持MD5校验
+* 对外提供获取Dialog方法
+
#### v1.0.7:2019-12-18
* 优化细节
@@ -151,7 +156,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
#### v1.0.5:2019-9-4
* 支持取消下载
-#### v1.0.4:2019-6-4 [支持AndroidX版本](https://github.com/jenly1314/AppUpdater/tree/androidx)
+#### v1.0.4:2019-6-4 [开始支持AndroidX版本](https://github.com/jenly1314/AppUpdater/tree/androidx)
* 支持添加请求头
#### v1.0.3:2019-5-9
@@ -185,7 +190,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
CSDN: jenly121
- 博客园: jenly
+ CNBlog: jenly
Github: jenly1314
diff --git a/app-updater/src/main/java/com/king/app/updater/AppUpdater.java b/app-updater/src/main/java/com/king/app/updater/AppUpdater.java
index 4d02fb6..f854dec 100644
--- a/app-updater/src/main/java/com/king/app/updater/AppUpdater.java
+++ b/app-updater/src/main/java/com/king/app/updater/AppUpdater.java
@@ -90,7 +90,6 @@ public class AppUpdater {
PermissionUtils.verifyReadAndWritePermissions((Activity) mContext,Constants.RE_CODE_STORAGE_PERMISSION);
}
-
if(mConfig.isShowNotification() && !PermissionUtils.isNotificationEnabled(mContext)){
Log.w(Constants.TAG,"Notification permission not enabled.");
}
@@ -166,10 +165,11 @@ public class AppUpdater {
}
/**
- * 设置保存的路径
+ * 设置保存的路径,(建议使用默认,不做设置)
* @param path 下载保存的文件路径
* @return
*/
+ @Deprecated
public Builder setPath(String path){
mConfig.setPath(path);
return this;
@@ -307,7 +307,9 @@ public class AppUpdater {
}
/**
- * 设置要下载APK的versionCode
+ * 设置要下载APK的versionCode,用于优先取缓存时通过versionCode校验APK文件是否一致。
+ * 缓存校验目前支持两种方式,一种是通过versionCode校验,即{@link #setVersionCode(Integer)};一种是文件MD5校验,即{@link #setApkMD5(String)}。推荐使用MD5校验方式
+ * 如果两种方式都设置了,则只校验MD5
* @param versionCode 为null表示不处理,默认不存在则下载,存在则重新下载。不为null时,表示会优先校验本地是否存在已下载版本号为versionCode的APK。
* 如果存在则不会重新下载(AppUpdater会自动校验packageName一致性),直接取本地APK,反之重新下载。
* @return
@@ -317,6 +319,17 @@ public class AppUpdater {
return this;
}
+ /**
+ * 设置APK文件的MD5,用于优先取缓存时通过MD5校验文件APK是否一致。
+ * 缓存校验目前支持两种方式,一种是通过versionCode校验,即{@link #setVersionCode(Integer)};一种是文件MD5校验,即{@link #setApkMD5(String)}。推荐使用MD5校验方式
+ * 如果两种方式都设置了,则只校验MD5
+ * @param md5 为null表示不处理,如果设置了MD5,则缓存APK的MD5相同时,只下载一次,优先取本地缓存
+ * @return
+ */
+ public Builder setApkMD5(String md5) {
+ mConfig.setApkMD5(md5);
+ return this;
+ }
/**
* 请求头添加参数
* @param key
diff --git a/app-updater/src/main/java/com/king/app/updater/UpdateConfig.java b/app-updater/src/main/java/com/king/app/updater/UpdateConfig.java
index ccda1f4..3568a43 100644
--- a/app-updater/src/main/java/com/king/app/updater/UpdateConfig.java
+++ b/app-updater/src/main/java/com/king/app/updater/UpdateConfig.java
@@ -97,6 +97,11 @@ public class UpdateConfig implements Parcelable {
*/
private boolean isDeleteCancelFile = true;
+ /**
+ * APK文件的MD5
+ */
+ private String apkMD5;
+
public UpdateConfig() {
@@ -234,6 +239,15 @@ public class UpdateConfig implements Parcelable {
return mRequestProperty;
}
+
+ public void setApkMD5(String md5){
+ this.apkMD5 = md5;
+ }
+
+ public String getApkMD5(){
+ return apkMD5;
+ }
+
public void addHeader(String key, String value){
initRequestProperty();
mRequestProperty.put(key,value);
@@ -293,6 +307,7 @@ public class UpdateConfig implements Parcelable {
}
dest.writeByte(this.isDeleteCancelFile ? (byte) 1 : (byte) 0);
+ dest.writeString(this.apkMD5);
}
protected UpdateConfig(Parcel in) {
@@ -320,6 +335,7 @@ public class UpdateConfig implements Parcelable {
this.mRequestProperty.put(key, value);
}
this.isDeleteCancelFile = in.readByte() != 0;
+ this.apkMD5 = in.readString();
}
public static final Creator CREATOR = new Creator() {
@@ -333,4 +349,4 @@ public class UpdateConfig implements Parcelable {
return new UpdateConfig[size];
}
};
-}
+}
\ No newline at end of file
diff --git a/app-updater/src/main/java/com/king/app/updater/http/HttpManager.java b/app-updater/src/main/java/com/king/app/updater/http/HttpManager.java
index 45133eb..de52483 100644
--- a/app-updater/src/main/java/com/king/app/updater/http/HttpManager.java
+++ b/app-updater/src/main/java/com/king/app/updater/http/HttpManager.java
@@ -25,6 +25,9 @@ import androidx.annotation.Nullable;
*/
public class HttpManager implements IHttpManager {
+ private static final int HTTP_TEMP_REDIRECT = 307;
+ private static final int HTTP_PERM_REDIRECT = 308;
+
private static final int DEFAULT_TIME_OUT = 20000;
private int mTimeout;
@@ -92,57 +95,53 @@ public class HttpManager implements IHttpManager {
}
- @Override
- protected File doInBackground(Void... voids) {
-
- try {
- HttpsURLConnection.setDefaultSSLSocketFactory(SSLSocketFactoryUtils.createSSLSocketFactory());
- HttpsURLConnection.setDefaultHostnameVerifier(SSLSocketFactoryUtils.createTrustAllHostnameVerifier());
- HttpURLConnection connect = (HttpURLConnection)new URL(url).openConnection();
- connect.setRequestMethod("GET");
- connect.setRequestProperty("Accept-Encoding", "identity");
+ private File download(String url) throws Exception{
+ HttpURLConnection connect = (HttpURLConnection)new URL(url).openConnection();
+ connect.setRequestMethod("GET");
+ connect.setRequestProperty("Accept-Encoding", "identity");
- connect.setReadTimeout(mTimeout);
- connect.setConnectTimeout(mTimeout);
+ connect.setReadTimeout(mTimeout);
+ connect.setConnectTimeout(mTimeout);
- if(requestProperty!=null){
- for(Map.Entry entry : requestProperty.entrySet()){
- connect.setRequestProperty(entry.getKey(),entry.getValue());
- }
+ if(requestProperty!=null){
+ for(Map.Entry entry : requestProperty.entrySet()){
+ connect.setRequestProperty(entry.getKey(),entry.getValue());
}
+ }
- connect.connect();
- int responseCode = connect.getResponseCode();
- Log.d(Constants.TAG,"Content-Type:" + connect.getContentType());
- if(responseCode == HttpURLConnection.HTTP_OK){
+ connect.connect();
+ Log.d(Constants.TAG,"Content-Type:" + connect.getContentType());
+ int responseCode = connect.getResponseCode();
+ switch (responseCode){
+ case HttpURLConnection.HTTP_OK: {
InputStream is = connect.getInputStream();
long length = connect.getContentLength();
- if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
length = connect.getContentLengthLong();
}
- Log.d(Constants.TAG,"contentLength:" + length);
+ Log.d(Constants.TAG, "contentLength:" + length);
long progress = 0;
byte[] buffer = new byte[8192];
int len;
- File file = new File(path,filename);
+ File file = new File(path, filename);
FileOutputStream fos = new FileOutputStream(file);
- while ((len = is.read(buffer)) != -1){
- if(isCancel){
+ while ((len = is.read(buffer)) != -1) {
+ if (isCancel) {
cancel(true);
break;
}
- fos.write(buffer,0,len);
+ fos.write(buffer, 0, len);
progress += len;
//更新进度
- if(length>0){
- publishProgress(progress,length);
+ if (length > 0) {
+ publishProgress(progress, length);
}
}
@@ -153,10 +152,31 @@ public class HttpManager implements IHttpManager {
connect.disconnect();
return file;
- }else {//连接失败
- throw new ConnectException(String.format("responseCode = %d",responseCode));
}
+ case HttpURLConnection.HTTP_MULT_CHOICE:
+ case HttpURLConnection.HTTP_MOVED_PERM:
+ case HttpURLConnection.HTTP_MOVED_TEMP:
+ case HttpURLConnection.HTTP_SEE_OTHER:
+ case HTTP_TEMP_REDIRECT:
+ case HTTP_PERM_REDIRECT: {//重定向
+ String redirectUrl = connect.getHeaderField("Location");
+ Log.d(Constants.TAG,"redirectUrl = " + redirectUrl);
+ connect.disconnect();
+ return download(redirectUrl);
+ }
+ default://连接失败
+ throw new ConnectException(String.format("responseCode = %d",responseCode));
+
+ }
+ }
+
+ @Override
+ protected File doInBackground(Void... voids) {
+ try{
+ HttpsURLConnection.setDefaultSSLSocketFactory(SSLSocketFactoryUtils.createSSLSocketFactory());
+ HttpsURLConnection.setDefaultHostnameVerifier(SSLSocketFactoryUtils.createTrustAllHostnameVerifier());
+ return download(url);
} catch (Exception e) {
this.exception = e;
e.printStackTrace();
@@ -197,7 +217,6 @@ public class HttpManager implements IHttpManager {
}
}
-
@Override
protected void onCancelled() {
super.onCancelled();
diff --git a/app-updater/src/main/java/com/king/app/updater/service/DownloadService.java b/app-updater/src/main/java/com/king/app/updater/service/DownloadService.java
index 5a6dd6d..fdd3a50 100644
--- a/app-updater/src/main/java/com/king/app/updater/service/DownloadService.java
+++ b/app-updater/src/main/java/com/king/app/updater/service/DownloadService.java
@@ -131,29 +131,38 @@ public class DownloadService extends Service {
mFile = new File(path,filename);
if(mFile.exists()){//文件是否存在
+
Integer versionCode = config.getVersionCode();
- if(versionCode!=null){
+ String apkMD5 = config.getApkMD5();
+ //是否存在相同的apk
+ boolean isExistApk = false;
+ if(!TextUtils.isEmpty(apkMD5)){//如果存在MD5,则优先校验MD5
+ isExistApk = AppUtils.checkFileMD5(mFile,apkMD5);
+ }else if(versionCode!=null){//如果存在versionCode,则校验versionCode
try{
- if(AppUtils.apkExists(getContext(),versionCode,mFile)){
- //本地已经存在要下载的APK
- Log.d(Constants.TAG,"CacheFile:" + mFile);
- if(config.isInstallApk()){
- String authority = config.getAuthority();
- if(TextUtils.isEmpty(authority)){//如果为空则默认
- authority = getContext().getPackageName() + Constants.DEFAULT_FILE_PROVIDER;
- }
- AppUtils.installApk(getContext(),mFile,authority);
- }
- if(callback!=null){
- callback.onFinish(mFile);
- }
- stopService();
- return;
- }
+ isExistApk = AppUtils.apkExists(getContext(),versionCode,mFile);
}catch (Exception e){
Log.w(Constants.TAG,e);
}
}
+
+ if(isExistApk){
+ //本地已经存在要下载的APK
+ Log.d(Constants.TAG,"CacheFile:" + mFile);
+ if(config.isInstallApk()){
+ String authority = config.getAuthority();
+ if(TextUtils.isEmpty(authority)){//如果为空则默认
+ authority = getContext().getPackageName() + Constants.DEFAULT_FILE_PROVIDER;
+ }
+ AppUtils.installApk(getContext(),mFile,authority);
+ }
+ if(callback!=null){
+ callback.onFinish(mFile);
+ }
+ stopService();
+ return;
+ }
+
//删除旧文件
mFile.delete();
}
diff --git a/app-updater/src/main/java/com/king/app/updater/util/AppUtils.java b/app-updater/src/main/java/com/king/app/updater/util/AppUtils.java
index d0a6d81..2551936 100644
--- a/app-updater/src/main/java/com/king/app/updater/util/AppUtils.java
+++ b/app-updater/src/main/java/com/king/app/updater/util/AppUtils.java
@@ -10,10 +10,16 @@ import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
+import android.util.Log;
+
+import com.king.app.updater.constant.Constants;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
import androidx.core.content.FileProvider;
@@ -192,6 +198,45 @@ public final class AppUtils {
return false;
}
+ /**
+ * 校验文件MD5
+ * @param file
+ * @param md5
+ * @return
+ */
+ public static boolean checkFileMD5(File file,String md5){
+ String fileMD5 = getFileMD5(file);
+ Log.d(Constants.TAG,"FileMD5:"+ fileMD5);
+ if(!TextUtils.isEmpty(md5)){
+ return md5.equalsIgnoreCase(fileMD5);
+ }
+
+ return false;
+ }
+
+ /**
+ * 获取文件MD5
+ * @param file
+ * @return
+ */
+ public static String getFileMD5(File file){
+ try {
+ FileInputStream fis = new FileInputStream(file);
+ MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+ byte[] buffer = new byte[1024];
+ int length;
+ while ((length = fis.read(buffer)) != -1){
+ messageDigest.update(buffer,0,length);
+ }
+ BigInteger bigInteger = new BigInteger(1,messageDigest.digest());
+ return bigInteger.toString(16);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
/**
* 关闭
* @param descriptor
@@ -206,4 +251,4 @@ public final class AppUtils {
}
}
-}
+}
\ No newline at end of file
diff --git a/app/release/app-release.apk b/app/release/app-release.apk
index 7ef4fe3..1333505 100644
Binary files a/app/release/app-release.apk and b/app/release/app-release.apk differ
diff --git a/app/release/output.json b/app/release/output.json
index 747e74b..c7ce66b 100644
--- a/app/release/output.json
+++ b/app/release/output.json
@@ -1 +1 @@
-[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":12,"versionName":"1.0.7-androidx","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
\ No newline at end of file
+[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":14,"versionName":"1.0.8-androidx","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
\ No newline at end of file
diff --git a/app/src/main/java/com/king/appupdater/MainActivity.java b/app/src/main/java/com/king/appupdater/MainActivity.java
index 386ddb7..5142992 100644
--- a/app/src/main/java/com/king/appupdater/MainActivity.java
+++ b/app/src/main/java/com/king/appupdater/MainActivity.java
@@ -223,8 +223,9 @@ public class MainActivity extends AppCompatActivity {
mAppUpdater = new AppUpdater.Builder()
.serUrl(mUrl)
// .setPath(Environment.getExternalStorageDirectory() + "/.AppUpdater")//如果适配Android Q,则Environment.getExternalStorageDirectory()将废弃
- .setPath(getExternalFilesDir(Constants.DEFAULT_DIR).getAbsolutePath())//自定义路径
- .setVersionCode(BuildConfig.VERSION_CODE)//设置versionCode之后,新版本相同的apk只下载一次,优先取本地缓存。
+// .setPath(getExternalFilesDir(Constants.DEFAULT_DIR).getAbsolutePath())//自定义路径,推荐使用默认
+// .setApkMD5("3df5b1c1d2bbd01b4a7ddb3f2722ccca")//支持MD5校验,如果缓存APK的MD5与此MD5相同,则直接取本地缓存安装,推荐使用MD5校验的方式
+ .setVersionCode(BuildConfig.VERSION_CODE)//支持versionCode校验,设置versionCode之后,新版本versionCode相同的apk只下载一次,优先取本地缓存,推荐使用MD5校验的方式
.setFilename("AppUpdater.apk")
.setVibrate(true)
.build(getContext());
diff --git a/versions.gradle b/versions.gradle
index 2b49549..2a39a12 100644
--- a/versions.gradle
+++ b/versions.gradle
@@ -1,7 +1,7 @@
//App
def app_version = [:]
-app_version.versionCode = 12 //androidx 12
-app_version.versionName = "1.0.7-androidx"
+app_version.versionCode = 14 //androidx 14
+app_version.versionName = "1.0.8-androidx"
ext.app_version = app_version
//build version