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