diff --git a/README.md b/README.md index a71cff1..cb7a7c8 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,21 +12,23 @@ [![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] 支持通知栏提示内容和过程全部可配置 - [x] 支持Android Q(10) +- [x] 支持取消下载 - [x] 支持使用OkHttpClient下载 ### [AndroidX version](https://github.com/jenly1314/AppUpdater/tree/androidx) @@ -41,7 +44,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版 com.king.app app-updater - 1.0.7 + 1.0.8 pom @@ -49,7 +52,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版 com.king.app app-dialog - 1.0.7 + 1.0.8 pom ``` @@ -58,25 +61,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 - + ``` @@ -108,7 +111,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(); @@ -142,6 +144,10 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版 ## 版本记录 +#### v1.0.8:2020-1-2 +* 支持MD5校验 +* 对外提供获取Dialog方法 + #### v1.0.7:2019-12-18 * 优化细节 @@ -152,7 +158,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 @@ -186,7 +192,7 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版 CSDN: jenly121 - 博客园: jenly + CNBlog: jenly Github: jenly1314 diff --git a/app-dialog/src/main/java/com/king/app/dialog/AppDialog.java b/app-dialog/src/main/java/com/king/app/dialog/AppDialog.java index 8329476..2dba6e5 100644 --- a/app-dialog/src/main/java/com/king/app/dialog/AppDialog.java +++ b/app-dialog/src/main/java/com/king/app/dialog/AppDialog.java @@ -228,10 +228,106 @@ public enum AppDialog { */ public void showDialog(Context context, View contentView, @StyleRes int resId, float widthRatio,final boolean isCancel){ dismissDialog(); - mDialog = new Dialog(context,resId); - mDialog.setContentView(contentView); - mDialog.setCanceledOnTouchOutside(false); - mDialog.setOnKeyListener(new DialogInterface.OnKeyListener() { + mDialog = createDialog(context,contentView,resId,widthRatio,isCancel); + mDialog.show(); + } + + /** + * 设置弹框窗口配置 + * @param context + * @param dialog + * @param widthRatio + */ + private void setDialogWindow(Context context,Dialog dialog,float widthRatio){ + Window window = dialog.getWindow(); + WindowManager.LayoutParams lp = window.getAttributes(); + lp.width = (int)(context.getResources().getDisplayMetrics().widthPixels * widthRatio); + window.setAttributes(lp); + } + + /** + * 创建弹框 + * @param context + * @param config 弹框配置 {@link AppDialogConfig} + */ + public Dialog createDialog(Context context,AppDialogConfig config){ + return createDialog(context,config,true); + } + + /** + * 创建弹框 + * @param context + * @param config 弹框配置 {@link AppDialogConfig} + * @param isCancel 是否可取消(默认为true,false则拦截back键) + */ + public Dialog createDialog(Context context,AppDialogConfig config,boolean isCancel){ + return createDialog(context,createAppDialogView(context,config),R.style.app_dialog,DEFAULT_WIDTH_RATIO,isCancel); + } + + /** + * 创建弹框 + * @param context + * @param contentView 弹框内容视图 + */ + public Dialog createDialog(Context context,View contentView){ + return createDialog(context,contentView,DEFAULT_WIDTH_RATIO); + } + + /** + * 创建弹框 + * @param context + * @param contentView 弹框内容视图 + * @param isCancel 是否可取消(默认为true,false则拦截back键) + */ + public Dialog createDialog(Context context,View contentView,boolean isCancel){ + return createDialog(context,contentView,R.style.app_dialog,DEFAULT_WIDTH_RATIO,isCancel); + } + + /** + * 创建弹框 + * @param context + * @param contentView 弹框内容视图 + * @param widthRatio 宽度比例,根据屏幕宽度计算得来 + */ + public Dialog createDialog(Context context,View contentView,float widthRatio){ + return createDialog(context,contentView,widthRatio,true); + } + + /** + * 创建弹框 + * @param context + * @param contentView 弹框内容视图 + * @param widthRatio 宽度比例,根据屏幕宽度计算得来 + * @param isCancel 是否可取消(默认为true,false则拦截back键) + */ + public Dialog createDialog(Context context,View contentView,float widthRatio,boolean isCancel){ + return createDialog(context,contentView,R.style.app_dialog,widthRatio,isCancel); + } + + /** + * 创建弹框 + * @param context + * @param contentView 弹框内容视图 + * @param resId Dialog样式 + * @param widthRatio 宽度比例,根据屏幕宽度计算得来 + */ + public Dialog createDialog(Context context, View contentView, @StyleRes int resId, float widthRatio){ + return createDialog(context,contentView,resId,widthRatio,true); + } + + /** + * 创建弹框 + * @param context + * @param contentView 弹框内容视图 + * @param resId Dialog样式 + * @param widthRatio 宽度比例,根据屏幕宽度计算得来 + * @param isCancel 是否可取消(默认为true,false则拦截back键) + */ + public Dialog createDialog(Context context, View contentView, @StyleRes int resId, float widthRatio,final boolean isCancel){ + Dialog dialog = new Dialog(context,resId); + dialog.setContentView(contentView); + dialog.setCanceledOnTouchOutside(false); + dialog.setOnKeyListener(new DialogInterface.OnKeyListener() { @Override public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { if(keyCode == KeyEvent.KEYCODE_BACK){ @@ -244,23 +340,21 @@ public enum AppDialog { } }); - setDialogWindow(context,mDialog,widthRatio); - mDialog.show(); + setDialogWindow(context,dialog,widthRatio); + return dialog; } - private void setDialogWindow(Context context,Dialog dialog,float widthRatio){ - Window window = dialog.getWindow(); - WindowManager.LayoutParams lp = window.getAttributes(); - lp.width = (int)(context.getResources().getDisplayMetrics().widthPixels * widthRatio); - window.setAttributes(lp); + public Dialog getDialog(){ + return mDialog; } public void dismissDialog(){ dismissDialog(mDialog); + mDialog = null; } - private void dismissDialog(Dialog dialog){ - if(dialog!=null){ + public void dismissDialog(Dialog dialog){ + if(dialog != null){ dialog.dismiss(); } } 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 76c4535..9e34716 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 @@ -89,7 +89,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."); } @@ -165,10 +164,11 @@ public class AppUpdater { } /** - * 设置保存的路径 + * 设置保存的路径,(建议使用默认,不做设置) * @param path 下载保存的文件路径 * @return */ + @Deprecated public Builder setPath(String path){ mConfig.setPath(path); return this; @@ -306,7 +306,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 @@ -316,6 +318,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 2c23423..42a1737 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 @@ -96,6 +96,8 @@ public class UpdateConfig implements Parcelable { */ private boolean isDeleteCancelFile = true; + private String apkMD5; + public UpdateConfig() { @@ -233,6 +235,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); @@ -292,6 +303,7 @@ public class UpdateConfig implements Parcelable { } dest.writeByte(this.isDeleteCancelFile ? (byte) 1 : (byte) 0); + dest.writeString(this.apkMD5); } protected UpdateConfig(Parcel in) { @@ -319,6 +331,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() { 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 125e124..3938bf9 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 @@ -130,29 +130,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 8fa4be8..38d3c14 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 @@ -11,10 +11,16 @@ import android.net.Uri; import android.os.Build; import android.support.v4.content.FileProvider; 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; /** * @author Jenly Jenly @@ -190,6 +196,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 diff --git a/app/release/app-release.apk b/app/release/app-release.apk index 0f294df..8d34c65 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 9a9599b..11541d8 100644 --- a/app/release/output.json +++ b/app/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":11,"versionName":"1.0.7","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":13,"versionName":"1.0.8","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 1b442d3..f470916 100644 --- a/app/src/main/java/com/king/appupdater/MainActivity.java +++ b/app/src/main/java/com/king/appupdater/MainActivity.java @@ -222,8 +222,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 afb2e76..fcc6368 100644 --- a/versions.gradle +++ b/versions.gradle @@ -1,7 +1,7 @@ //App def app_version = [:] -app_version.versionCode = 11 //androidx 12 -app_version.versionName = "1.0.7" +app_version.versionCode = 13 //androidx 14 +app_version.versionName = "1.0.8" ext.app_version = app_version //build version