支持MD5校验

pull/27/head 1.0.9-androidx
jenly1314 5 years ago
parent 636e202e2f
commit 8e657d1ecb
  1. 35
      README.md
  2. 19
      app-updater/src/main/java/com/king/app/updater/AppUpdater.java
  3. 16
      app-updater/src/main/java/com/king/app/updater/UpdateConfig.java
  4. 57
      app-updater/src/main/java/com/king/app/updater/http/HttpManager.java
  5. 21
      app-updater/src/main/java/com/king/app/updater/service/DownloadService.java
  6. 45
      app-updater/src/main/java/com/king/app/updater/util/AppUtils.java
  7. BIN
      app/release/app-release.apk
  8. 2
      app/release/output.json
  9. 5
      app/src/main/java/com/king/appupdater/MainActivity.java
  10. 4
      versions.gradle

@ -3,6 +3,7 @@
![Image](app/src/main/ic_launcher-web.png) ![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) [![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) [![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) [![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) [![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/) [![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) [![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-dialog。
> 下载更新和弹框提示分开,是因为这本来就是两个逻辑。完全独立开来能有效的解耦。 > 下载更新和弹框提示分开,是因为这本来就是两个逻辑。完全独立开来能有效的解耦。
* app-updater 主要负责后台下载更新App,无需担心下载时各种配置相关的细节,一键傻瓜式升级。 * app-updater 主要负责后台下载更新App,无需担心下载时各种配置相关的细节,一键傻瓜式升级。
* app-dialog 主要是提供常用的Dialog和DialogFragment,简化弹框提示,样式支持高度自定义。 * app-dialog 主要是提供常用的Dialog和DialogFragment,简化弹框提示,布局样式支持自定义。
> app-updater + app-dialog 配合使用,谁用谁知道。 > app-updater + app-dialog 配合使用,谁用谁知道。
## 功能介绍 ## 功能介绍
- [x] 专注于App更新一键傻瓜式升级 - [x] 专注于App更新一键傻瓜式升级
- [x] 支持下载监听 - [x] 够轻量,体积小
- [x] 支持监听下载过程
- [x] 支持下载失败,重新下载 - [x] 支持下载失败,重新下载
- [x] 支持下载优先取本地缓存 - [x] 支持下载优先取本地缓存
- [x] 支持通知栏提示内容和过程全部可配置 - [x] 支持通知栏提示内容和过程全部可配置
- [x] 支持Android Q(10) - [x] 支持Android Q(10)
- [x] 支持取消下载
- [x] 支持使用OkHttpClient下载 - [x] 支持使用OkHttpClient下载
## Gif 展示 ## Gif 展示
![Image](GIF.gif) ![Image](GIF.gif)
@ -40,7 +42,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
<dependency> <dependency>
<groupId>com.king.app</groupId> <groupId>com.king.app</groupId>
<artifactId>app-updater</artifactId> <artifactId>app-updater</artifactId>
<version>1.0.7</version> <version>1.0.8</version>
<type>pom</type> <type>pom</type>
</dependency> </dependency>
@ -48,7 +50,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
<dependency> <dependency>
<groupId>com.king.app</groupId> <groupId>com.king.app</groupId>
<artifactId>app-dialog</artifactId> <artifactId>app-dialog</artifactId>
<version>1.0.7</version> <version>1.0.8</version>
<type>pom</type> <type>pom</type>
</dependency> </dependency>
``` ```
@ -57,25 +59,25 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
//----------AndroidX 版本 //----------AndroidX 版本
//app-updater //app-updater
implementation 'com.king.app:app-updater:1.0.7-androidx' implementation 'com.king.app:app-updater:1.0.8-androidx'
//app-dialog //app-dialog
implementation 'com.king.app:app-dialog:1.0.7-androidx' implementation 'com.king.app:app-dialog:1.0.8-androidx'
//----------Android Support 版本 //----------Android Support 版本
//app-updater //app-updater
implementation 'com.king.app:app-updater:1.0.7' implementation 'com.king.app:app-updater:1.0.8'
//app-dialog //app-dialog
implementation 'com.king.app:app-dialog:1.0.7' implementation 'com.king.app:app-dialog:1.0.8'
``` ```
### Lvy: ### Lvy:
```lvy ```lvy
//app-updater //app-updater
<dependency org='com.king.app' name='app-dialog' rev='1.0.7'> <dependency org='com.king.app' name='app-dialog' rev='1.0.8'>
<artifact name='$AID' ext='pom'></artifact> <artifact name='$AID' ext='pom'></artifact>
</dependency> </dependency>
//app-dialog //app-dialog
<dependency org='com.king.app' name='app-dialog' rev='1.0.7'> <dependency org='com.king.app' name='app-dialog' rev='1.0.8'>
<artifact name='$AID' ext='pom'></artifact> <artifact name='$AID' ext='pom'></artifact>
</dependency> </dependency>
``` ```
@ -107,7 +109,6 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
public void onClick(View v) { public void onClick(View v) {
new AppUpdater.Builder() new AppUpdater.Builder()
.serUrl(mUrl) .serUrl(mUrl)
.setFilename("AppUpdater.apk")
.build(getContext()) .build(getContext())
.start(); .start();
AppDialog.INSTANCE.dismissDialog(); 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 #### v1.0.7:2019-12-18
* 优化细节 * 优化细节
@ -151,7 +156,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
#### v1.0.5:2019-9-4 #### 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 #### v1.0.3:2019-5-9
@ -185,7 +190,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
CSDN: <a title="CSDN博客" href="http://blog.csdn.net/jenly121" target="_blank">jenly121</a> CSDN: <a title="CSDN博客" href="http://blog.csdn.net/jenly121" target="_blank">jenly121</a>
博客园: <a title="博客园" href="https://www.cnblogs.com/jenly" target="_blank">jenly</a> CNBlog: <a title="博客园" href="https://www.cnblogs.com/jenly" target="_blank">jenly</a>
Github: <a title="Github开源项目" href="https://github.com/jenly1314" target="_blank">jenly1314</a> Github: <a title="Github开源项目" href="https://github.com/jenly1314" target="_blank">jenly1314</a>

@ -90,7 +90,6 @@ public class AppUpdater {
PermissionUtils.verifyReadAndWritePermissions((Activity) mContext,Constants.RE_CODE_STORAGE_PERMISSION); PermissionUtils.verifyReadAndWritePermissions((Activity) mContext,Constants.RE_CODE_STORAGE_PERMISSION);
} }
if(mConfig.isShowNotification() && !PermissionUtils.isNotificationEnabled(mContext)){ if(mConfig.isShowNotification() && !PermissionUtils.isNotificationEnabled(mContext)){
Log.w(Constants.TAG,"Notification permission not enabled."); Log.w(Constants.TAG,"Notification permission not enabled.");
} }
@ -166,10 +165,11 @@ public class AppUpdater {
} }
/** /**
* 设置保存的路径 * 设置保存的路径建议使用默认不做设置
* @param path 下载保存的文件路径 * @param path 下载保存的文件路径
* @return * @return
*/ */
@Deprecated
public Builder setPath(String path){ public Builder setPath(String path){
mConfig.setPath(path); mConfig.setPath(path);
return this; 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 * @param versionCode 为null表示不处理默认不存在则下载存在则重新下载不为null时表示会优先校验本地是否存在已下载版本号为versionCode的APK
* 如果存在则不会重新下载(AppUpdater会自动校验packageName一致性)直接取本地APK反之重新下载 * 如果存在则不会重新下载(AppUpdater会自动校验packageName一致性)直接取本地APK反之重新下载
* @return * @return
@ -317,6 +319,17 @@ public class AppUpdater {
return this; 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 * @param key

@ -97,6 +97,11 @@ public class UpdateConfig implements Parcelable {
*/ */
private boolean isDeleteCancelFile = true; private boolean isDeleteCancelFile = true;
/**
* APK文件的MD5
*/
private String apkMD5;
public UpdateConfig() { public UpdateConfig() {
@ -234,6 +239,15 @@ public class UpdateConfig implements Parcelable {
return mRequestProperty; return mRequestProperty;
} }
public void setApkMD5(String md5){
this.apkMD5 = md5;
}
public String getApkMD5(){
return apkMD5;
}
public void addHeader(String key, String value){ public void addHeader(String key, String value){
initRequestProperty(); initRequestProperty();
mRequestProperty.put(key,value); mRequestProperty.put(key,value);
@ -293,6 +307,7 @@ public class UpdateConfig implements Parcelable {
} }
dest.writeByte(this.isDeleteCancelFile ? (byte) 1 : (byte) 0); dest.writeByte(this.isDeleteCancelFile ? (byte) 1 : (byte) 0);
dest.writeString(this.apkMD5);
} }
protected UpdateConfig(Parcel in) { protected UpdateConfig(Parcel in) {
@ -320,6 +335,7 @@ public class UpdateConfig implements Parcelable {
this.mRequestProperty.put(key, value); this.mRequestProperty.put(key, value);
} }
this.isDeleteCancelFile = in.readByte() != 0; this.isDeleteCancelFile = in.readByte() != 0;
this.apkMD5 = in.readString();
} }
public static final Creator<UpdateConfig> CREATOR = new Creator<UpdateConfig>() { public static final Creator<UpdateConfig> CREATOR = new Creator<UpdateConfig>() {

@ -25,6 +25,9 @@ import androidx.annotation.Nullable;
*/ */
public class HttpManager implements IHttpManager { 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 static final int DEFAULT_TIME_OUT = 20000;
private int mTimeout; private int mTimeout;
@ -92,12 +95,7 @@ public class HttpManager implements IHttpManager {
} }
@Override private File download(String url) throws Exception{
protected File doInBackground(Void... voids) {
try {
HttpsURLConnection.setDefaultSSLSocketFactory(SSLSocketFactoryUtils.createSSLSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(SSLSocketFactoryUtils.createTrustAllHostnameVerifier());
HttpURLConnection connect = (HttpURLConnection)new URL(url).openConnection(); HttpURLConnection connect = (HttpURLConnection)new URL(url).openConnection();
connect.setRequestMethod("GET"); connect.setRequestMethod("GET");
connect.setRequestProperty("Accept-Encoding", "identity"); connect.setRequestProperty("Accept-Encoding", "identity");
@ -112,37 +110,38 @@ public class HttpManager implements IHttpManager {
} }
connect.connect(); connect.connect();
int responseCode = connect.getResponseCode();
Log.d(Constants.TAG,"Content-Type:" + connect.getContentType());
if(responseCode == HttpURLConnection.HTTP_OK){
Log.d(Constants.TAG,"Content-Type:" + connect.getContentType());
int responseCode = connect.getResponseCode();
switch (responseCode){
case HttpURLConnection.HTTP_OK: {
InputStream is = connect.getInputStream(); InputStream is = connect.getInputStream();
long length = connect.getContentLength(); 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(); length = connect.getContentLengthLong();
} }
Log.d(Constants.TAG,"contentLength:" + length); Log.d(Constants.TAG, "contentLength:" + length);
long progress = 0; long progress = 0;
byte[] buffer = new byte[8192]; byte[] buffer = new byte[8192];
int len; int len;
File file = new File(path,filename); File file = new File(path, filename);
FileOutputStream fos = new FileOutputStream(file); FileOutputStream fos = new FileOutputStream(file);
while ((len = is.read(buffer)) != -1){ while ((len = is.read(buffer)) != -1) {
if(isCancel){ if (isCancel) {
cancel(true); cancel(true);
break; break;
} }
fos.write(buffer,0,len); fos.write(buffer, 0, len);
progress += len; progress += len;
//更新进度 //更新进度
if(length>0){ if (length > 0) {
publishProgress(progress,length); publishProgress(progress, length);
} }
} }
@ -153,10 +152,31 @@ public class HttpManager implements IHttpManager {
connect.disconnect(); connect.disconnect();
return file; return file;
}else {//连接失败 }
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)); 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) { } catch (Exception e) {
this.exception = e; this.exception = e;
e.printStackTrace(); e.printStackTrace();
@ -197,7 +217,6 @@ public class HttpManager implements IHttpManager {
} }
} }
@Override @Override
protected void onCancelled() { protected void onCancelled() {
super.onCancelled(); super.onCancelled();

@ -131,10 +131,22 @@ public class DownloadService extends Service {
mFile = new File(path,filename); mFile = new File(path,filename);
if(mFile.exists()){//文件是否存在 if(mFile.exists()){//文件是否存在
Integer versionCode = config.getVersionCode(); 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{ try{
if(AppUtils.apkExists(getContext(),versionCode,mFile)){ isExistApk = AppUtils.apkExists(getContext(),versionCode,mFile);
}catch (Exception e){
Log.w(Constants.TAG,e);
}
}
if(isExistApk){
//本地已经存在要下载的APK //本地已经存在要下载的APK
Log.d(Constants.TAG,"CacheFile:" + mFile); Log.d(Constants.TAG,"CacheFile:" + mFile);
if(config.isInstallApk()){ if(config.isInstallApk()){
@ -150,10 +162,7 @@ public class DownloadService extends Service {
stopService(); stopService();
return; return;
} }
}catch (Exception e){
Log.w(Constants.TAG,e);
}
}
//删除旧文件 //删除旧文件
mFile.delete(); mFile.delete();
} }

@ -10,10 +10,16 @@ import android.content.res.AssetFileDescriptor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import com.king.app.updater.constant.Constants;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
@ -192,6 +198,45 @@ public final class AppUtils {
return false; 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 * @param descriptor

Binary file not shown.

@ -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":{}}] [{"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":{}}]

@ -223,8 +223,9 @@ public class MainActivity extends AppCompatActivity {
mAppUpdater = new AppUpdater.Builder() mAppUpdater = new AppUpdater.Builder()
.serUrl(mUrl) .serUrl(mUrl)
// .setPath(Environment.getExternalStorageDirectory() + "/.AppUpdater")//如果适配Android Q,则Environment.getExternalStorageDirectory()将废弃 // .setPath(Environment.getExternalStorageDirectory() + "/.AppUpdater")//如果适配Android Q,则Environment.getExternalStorageDirectory()将废弃
.setPath(getExternalFilesDir(Constants.DEFAULT_DIR).getAbsolutePath())//自定义路径 // .setPath(getExternalFilesDir(Constants.DEFAULT_DIR).getAbsolutePath())//自定义路径,推荐使用默认
.setVersionCode(BuildConfig.VERSION_CODE)//设置versionCode之后,新版本相同的apk只下载一次,优先取本地缓存。 // .setApkMD5("3df5b1c1d2bbd01b4a7ddb3f2722ccca")//支持MD5校验,如果缓存APK的MD5与此MD5相同,则直接取本地缓存安装,推荐使用MD5校验的方式
.setVersionCode(BuildConfig.VERSION_CODE)//支持versionCode校验,设置versionCode之后,新版本versionCode相同的apk只下载一次,优先取本地缓存,推荐使用MD5校验的方式
.setFilename("AppUpdater.apk") .setFilename("AppUpdater.apk")
.setVibrate(true) .setVibrate(true)
.build(getContext()); .build(getContext());

@ -1,7 +1,7 @@
//App //App
def app_version = [:] def app_version = [:]
app_version.versionCode = 12 //androidx 12 app_version.versionCode = 14 //androidx 14
app_version.versionName = "1.0.7-androidx" app_version.versionName = "1.0.8-androidx"
ext.app_version = app_version ext.app_version = app_version
//build version //build version

Loading…
Cancel
Save