diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index ca8f1d3..30a3210 100644 Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ diff --git a/.idea/caches/gradle_models.ser b/.idea/caches/gradle_models.ser index a4e89d4..530cf43 100644 Binary files a/.idea/caches/gradle_models.ser and b/.idea/caches/gradle_models.ser differ diff --git a/README.md b/README.md index 24dee71..1f340b1 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,14 @@ + **已适配Android 6.0,Android 7.0,Android 8.0,Android 9.0。** + **提供强制更新,不更新则无法使用APP,同时可以根据后台返回受影响的版本号,可控制多个版本同时被强制更新。** + **通知栏图片自定义** ++ **新增文件MD5校验,防止安装文件被恶意替换(2019-10-22 18:48:39添加)** + +### 新版本说明 ++ 2019-10-22 18:53:42更新新版,版本号为:v2.0.1 + + 新版新增文件的MD5校验 + + 新版新增对文件下载进度的监听 + + 新版新增对文件MD5校验结果的回调 + + DEMO中提供了获取文件MD5检验码的工具页面,也提供了加密工具类Md5Utils ### 博客地址 @@ -51,42 +59,54 @@ dependencies { implementation 'com.github.MZCretin:AutoUpdateProject:latest_vers **Step 3.** Init it in BaseApplication or MainActivity before using it.And then register BaseApplication in AndroidManifest(Don't forget it). ```java -//更新库配置 -UpdateConfig updateConfig = new UpdateConfig() - .setDebug(true)//是否是Debug模式 - .setBaseUrl("http://www.cretinzp.com/system/versioninfo")//当dataSourceType为DATA_SOURCE_TYPE_URL时,配置此接口用于获取更新信息 - .setMethodType(TypeConfig.METHOD_GET)//当dataSourceType为DATA_SOURCE_TYPE_URL时,设置请求的方法 - .setDataSourceType(TypeConfig.DATA_SOURCE_TYPE_URL)//设置获取更新信息的方式 - .setShowNotification(true)//配置更新的过程中是否在通知栏显示进度 - .setNotificationIconRes(R.mipmap.download_icon)//配置通知栏显示的图标 - .setUiThemeType(TypeConfig.UI_THEME_AUTO)//配置UI的样式,一种有12种样式可供选择 - .setRequestHeaders(null)//当dataSourceType为DATA_SOURCE_TYPE_URL时,设置请求的请求头 - .setRequestParams(null)//当dataSourceType为DATA_SOURCE_TYPE_URL时,设置请求的请求参数 - .setCustomActivityClass(CustomActivity.class)//如果你选择的UI样式为TypeConfig.UI_THEME_CUSTOM,那么你需要自定义一个Activity继承自RootActivity,并参照demo实现功能,在此处填写自定义Activity的class - .setModelClass(new UpdateModel()); -AppUpdateUtils.init(this, updateConfig); + //更新库配置 + UpdateConfig updateConfig = new UpdateConfig() + .setDebug(true)//是否是Debug模式 + .setBaseUrl("http://www.cretinzp.com/system/versioninfo")//当dataSourceType为DATA_SOURCE_TYPE_URL时,配置此接口用于获取更新信息 + .setMethodType(TypeConfig.METHOD_GET)//当dataSourceType为DATA_SOURCE_TYPE_URL时,设置请求的方法 + .setDataSourceType(TypeConfig.DATA_SOURCE_TYPE_URL)//设置获取更新信息的方式 + .setShowNotification(true)//配置更新的过程中是否在通知栏显示进度 + .setNotificationIconRes(R.mipmap.download_icon)//配置通知栏显示的图标 + .setUiThemeType(TypeConfig.UI_THEME_AUTO)//配置UI的样式,一种有12种样式可供选择 + .setRequestHeaders(null)//当dataSourceType为DATA_SOURCE_TYPE_URL时,设置请求的请求头 + .setRequestParams(null)//当dataSourceType为DATA_SOURCE_TYPE_URL时,设置请求的请求参数 + .setCustomActivityClass(CustomActivity.class)//如果你选择的UI样式为TypeConfig.UI_THEME_CUSTOM,那么你需要自定义一个Activity继承自RootActivity,并参照demo实现功能,在此处填写自定义Activity的class + .setNeedFileMD5Check(false)//是否需要进行文件的MD5检验,如果开启需要提供文件本身正确的MD5校验码,DEMO中提供了获取文件MD5检验码的工具页面,也提供了加密工具类Md5Utils + .setModelClass(new UpdateModel()); + AppUpdateUtils.init(this, updateConfig); ``` **Step 4.** Start using it wherever you want as below with 3 ways. ```java -//有三种方式实现app更新,您可选其中一种方式来进行,推荐使用第三种方式! - -//第一种方式,使用JSON字符串,让sdk自主解析并实现功能 -String jsonData = "{\"versionCode\": 25,\"isForceUpdate\": 1,\"preBaselineCode\": 24,\"versionName\": \"v2.3.1\",\"downurl\": \"http://jokesimg.cretinzp.com/apk/app-release_231_jiagu_sign.apk\",\"updateLog\": \"1、优化细节和体验,更加稳定\n2、引入大量优质用户\r\n3、修复已知bug\n4、风格修改\",\"size\": \"31338250\",\"hasAffectCodes\": \"1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24\"}"; -AppUpdateUtils.getInstance().checkUpdate(jsonData); - -//第二种方式,使用MODEL方式,组装好对应的MODEL,传入sdk中 -DownloadInfo info = new DownloadInfo().setApkUrl("http://jokesimg.cretinzp.com/apk/app-release_231_jiagu_sign.apk") - .setFileSize(31338250) - .setProdVersionCode(25) - .setProdVersionName("2.3.1") - .setForceUpdateFlag(listModel.isForceUpdate() ? 1 : 0) - .setUpdateLog("1、优化细节和体验,更加稳定\n2、引入大量优质用户\r\n3、修复已知bug\n4、风格修改"); -AppUpdateUtils.getInstance().checkUpdate(info); - -//第三种方式,在初始化的时候配置接口地址,sdk自主请求+解析实现功能(推荐) -AppUpdateUtils.getInstance().checkUpdate(); + //有三种方式实现app更新,您可选其中一种方式来进行,推荐使用第三种方式! + //新增下载进度和MD5检测结果的回调监听 2019-10-22 18:51:19 + + //第一种方式,使用JSON字符串,让sdk自主解析并实现功能 + String jsonData = "{\"versionCode\": 25,\"isForceUpdate\": 1,\"preBaselineCode\": 24,\"versionName\": \"v2.3.1\",\"downurl\": \"http://jokesimg.cretinzp.com/apk/app-release_231_jiagu_sign.apk\",\"updateLog\": \"1、优化细节和体验,更加稳定\n2、引入大量优质用户\r\n3、修复已知bug\n4、风格修改\",\"size\": \"31338250\",\"hasAffectCodes\": \"1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24\"}"; + AppUpdateUtils.getInstance() + .addMd5CheckListener(...) + .addAppDownloadListener(...) + .checkUpdate(jsonData); + + //第二种方式,使用MODEL方式,组装好对应的MODEL,传入sdk中 + DownloadInfo info = new DownloadInfo().setApkUrl("http://jokesimg.cretinzp.com/apk/app-release_231_jiagu_sign.apk") + .setFileSize(31338250) + .setProdVersionCode(25) + .setProdVersionName("2.3.1") + .setMd5Check("68919BF998C29DA3F5BD2C0346281AC0") + .setForceUpdateFlag(listModel.isForceUpdate() ? 1 : 0) + .setUpdateLog("1、优化细节和体验,更加稳定\n2、引入大量优质用户\r\n3、修复已知bug\n4、风格修改"); + AppUpdateUtils.getInstance() + .addMd5CheckListener(...) + .addAppDownloadListener(...) + .checkUpdate(info); + + //第三种方式,在初始化的时候配置接口地址,sdk自主请求+解析实现功能(推荐) + AppUpdateUtils.getInstance() + .addMd5CheckListener(...) + .addAppDownloadListener(...) + .checkUpdate(); ``` diff --git a/app/build.gradle b/app/build.gradle index a8803a1..77803b5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,20 +28,8 @@ dependencies { implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.blankj:utilcode:1.25.9' - //使用jirpack - implementation 'com.github.MZCretin:AutoUpdateProject:v2.0.0' + //使用jitpack +// implementation 'com.github.MZCretin:AutoUpdateProject:v2.0.0' //使用本地库依赖 - // implementation project(':cretinautoupdatelibrary') - - - - - - - - - - - - + implementation project(':cretinautoupdatelibrary') } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 05f8059..9f2f44d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,9 +14,12 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> + + android:theme="@style/DialogActivityTheme" /> diff --git a/app/src/main/java/com/cretin/www/autoupdateproject/CustomActivity.java b/app/src/main/java/com/cretin/www/autoupdateproject/CustomActivity.java index a893b5f..c4f4005 100644 --- a/app/src/main/java/com/cretin/www/autoupdateproject/CustomActivity.java +++ b/app/src/main/java/com/cretin/www/autoupdateproject/CustomActivity.java @@ -8,6 +8,7 @@ import com.cretin.www.cretinautoupdatelibrary.activity.RootActivity; import com.cretin.www.cretinautoupdatelibrary.activity.UpdateType2Activity; import com.cretin.www.cretinautoupdatelibrary.interfaces.AppDownloadListener; +import com.cretin.www.cretinautoupdatelibrary.interfaces.MD5CheckListener; import com.cretin.www.cretinautoupdatelibrary.utils.ResUtils; /** @@ -15,8 +16,9 @@ * 你需要注意: * 1、Activity需要继承RootActivity才有效,并实现需要实现的方法 * 2、你需要在你的AndroidManifest.xml中为这个自定义的Activity设置 - * @style/DialogActivityTheme 这个对话框的主题; - * 当然这只是建议,你只要撸出来的页面好看,咋地都行 + * + * @style/DialogActivityTheme 这个对话框的主题; + * 当然这只是建议,你只要撸出来的页面好看,咋地都行 */ public class CustomActivity extends RootActivity { private TextView tvInfo; @@ -105,4 +107,24 @@ public void pause() { } }; } + + /** + * 如果需要知道文件MD5校验结果就重写此方法 + * + * @return + */ + @Override + public MD5CheckListener obtainMD5CheckListener() { + return new MD5CheckListener() { + @Override + public void fileMd5CheckFail(String originMD5, String localMD5) { + + } + + @Override + public void fileMd5CheckSuccess() { + + } + }; + } } diff --git a/app/src/main/java/com/cretin/www/autoupdateproject/MD5HelperActivity.java b/app/src/main/java/com/cretin/www/autoupdateproject/MD5HelperActivity.java new file mode 100644 index 0000000..36d9b7a --- /dev/null +++ b/app/src/main/java/com/cretin/www/autoupdateproject/MD5HelperActivity.java @@ -0,0 +1,122 @@ +package com.cretin.www.autoupdateproject; + +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.view.View; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import com.cretin.www.autoupdateproject.utils.Md5Utils; +import com.liulishuo.filedownloader.BaseDownloadTask; +import com.liulishuo.filedownloader.FileDownloadListener; +import com.liulishuo.filedownloader.FileDownloader; + +import java.io.File; + +import static com.cretin.www.cretinautoupdatelibrary.activity.RootActivity.permission; +import static com.cretin.www.cretinautoupdatelibrary.utils.AppUtils.getAppLocalPath; + +public class MD5HelperActivity extends AppCompatActivity { + private TextView tvDownload; + private EditText edContent; + private TextView tvDetails; + private boolean isDownloading; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_md5_helper); + + tvDownload = findViewById(R.id.tv_download); + tvDetails = findViewById(R.id.tv_details); + edContent = findViewById(R.id.ed_content); + + tvDownload.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (isDownloading) { + return; + } + if (TextUtils.isEmpty(edContent.getText().toString().trim())) { + Toast.makeText(MD5HelperActivity.this, "请输入文件下载地址", Toast.LENGTH_SHORT).show(); + return; + } + if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.M)) { + downloadFile(edContent.getText().toString().trim()); + } else { + //下载权限 + int writePermission = ContextCompat.checkSelfPermission(MD5HelperActivity.this, permission); + if (writePermission == PackageManager.PERMISSION_GRANTED) { + //拥有权限则直接下载 + downloadFile(edContent.getText().toString().trim()); + } else { + // 申请权限 + ActivityCompat.requestPermissions(MD5HelperActivity.this, new String[]{permission}, 1000); + } + } + } + }); + } + + //下载文件 + private void downloadFile(String appUrl) { + FileDownloader.setup(this); + + final String downloadUpdateApkFilePath = getAppLocalPath("test"); + + BaseDownloadTask downloadTask = FileDownloader.getImpl().create(appUrl) + .setPath(downloadUpdateApkFilePath); + downloadTask + .addHeader("Accept-Encoding", "identity") + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36") + .setListener(new FileDownloadListener() { + @Override + protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) { + isDownloading = true; + } + + @Override + protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) { + tvDownload.setText("已下载:" + (soFarBytes * 100l / totalBytes) + "%"); + } + + @Override + protected void completed(BaseDownloadTask task) { + isDownloading = false; + tvDownload.setText("下载完成(点击可重新下载)"); + //获取校验码 + try { + String fileMD5String = Md5Utils.getFileMD5(new File(downloadUpdateApkFilePath)); + tvDetails.setText(fileMD5String); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) { + isDownloading = false; + tvDownload.setText("下载出错(点击可重新下载)"); + } + + @Override + protected void error(BaseDownloadTask task, Throwable e) { + isDownloading = false; + tvDownload.setText("下载出错(点击可重新下载)"); + } + + @Override + protected void warn(BaseDownloadTask task) { + + } + }) + .setAutoRetryTimes(3) + .start(); + } +} diff --git a/app/src/main/java/com/cretin/www/autoupdateproject/MainActivity.java b/app/src/main/java/com/cretin/www/autoupdateproject/MainActivity.java index fa3d6ef..38f75f3 100644 --- a/app/src/main/java/com/cretin/www/autoupdateproject/MainActivity.java +++ b/app/src/main/java/com/cretin/www/autoupdateproject/MainActivity.java @@ -52,6 +52,7 @@ private void obtainData() { ListModel listModel = new ListModel(); listModel.setForceUpdate(false); listModel.setUiTypeValue(300 + i); + listModel.setCheckFileMD5(true); listModel.setSourceTypeVaule(TypeConfig.DATA_SOURCE_TYPE_MODEL); list.add(listModel); } @@ -87,6 +88,10 @@ public boolean onOptionsItemSelected(MenuItem item) { AppUpdateUtils.getInstance().checkUpdate(recyclerviewAdapter.jsonDataUnForce); Toast.makeText(this, "为了展示是真的可以自定义UI的,我写了个很丑的页面", Toast.LENGTH_SHORT).show(); break; + case R.id.action_04: + //获取文件MD5检验码工具 + startActivity(new Intent(this, MD5HelperActivity.class)); + break; } return super.onOptionsItemSelected(item); } diff --git a/app/src/main/java/com/cretin/www/autoupdateproject/adapter/RecyclerviewAdapter.java b/app/src/main/java/com/cretin/www/autoupdateproject/adapter/RecyclerviewAdapter.java index b346cdb..5812530 100644 --- a/app/src/main/java/com/cretin/www/autoupdateproject/adapter/RecyclerviewAdapter.java +++ b/app/src/main/java/com/cretin/www/autoupdateproject/adapter/RecyclerviewAdapter.java @@ -14,6 +14,7 @@ import com.blankj.utilcode.util.SpanUtils; import com.cretin.www.autoupdateproject.R; import com.cretin.www.autoupdateproject.model.ListModel; +import com.cretin.www.cretinautoupdatelibrary.interfaces.MD5CheckListener; import com.cretin.www.cretinautoupdatelibrary.model.DownloadInfo; import com.cretin.www.cretinautoupdatelibrary.model.TypeConfig; import com.cretin.www.cretinautoupdatelibrary.utils.AppUpdateUtils; @@ -44,11 +45,16 @@ public void onBindViewHolder(@NonNull ViewHolder holder, final int position) { SpannableStringBuilder spannableStringBuilder = new SpanUtils().append("UI样式类型:") .append(getUiTypeDesc(data.get(position).getUiTypeValue()) + "\n") .setForegroundColor(ResUtils.getColor(R.color.colorAccent)) - .append("强制更新:") - .append((data.get(position).getSourceTypeVaule() == TypeConfig.DATA_SOURCE_TYPE_URL ? "根据接口返回,当前是开启" : data.get(position).isForceUpdate() ? "开启" : "关闭") + " \n") + .append("是否开启强制更新:") + .append((data.get(position).getSourceTypeVaule() == TypeConfig.DATA_SOURCE_TYPE_URL ? + "根据接口返回,当前是开启" : data.get(position).isForceUpdate() ? "开启" : "关闭") + " \n") .setForegroundColor(ResUtils.getColor(R.color.colorAccent)) .append("数据加载方式:") - .append(getSourceType(data.get(position).getSourceTypeVaule())) + .append(getSourceType(data.get(position).getSourceTypeVaule()) + "\n") + .setForegroundColor(ResUtils.getColor(R.color.colorAccent)) + .append("是否开启文件MD5检验:") + .append((data.get(position).getSourceTypeVaule() != TypeConfig.DATA_SOURCE_TYPE_MODEL ? + "根据接口返回或者配置的JSON,当前是关闭" : data.get(position).isCheckFileMD5() ? "开启" : "关闭") + "") .setForegroundColor(ResUtils.getColor(R.color.colorAccent)) .create(); holder.tv_theme.setText(spannableStringBuilder); @@ -86,10 +92,13 @@ public void onClick(View v) { private void sourceTypeChange(ListModel listModel) { if (listModel.getSourceTypeVaule() == TypeConfig.DATA_SOURCE_TYPE_MODEL) { listModel.setSourceTypeVaule(TypeConfig.DATA_SOURCE_TYPE_JSON); + listModel.setCheckFileMD5(false); } else if (listModel.getSourceTypeVaule() == TypeConfig.DATA_SOURCE_TYPE_JSON) { listModel.setSourceTypeVaule(TypeConfig.DATA_SOURCE_TYPE_URL); + listModel.setCheckFileMD5(false); } else { listModel.setSourceTypeVaule(TypeConfig.DATA_SOURCE_TYPE_MODEL); + listModel.setCheckFileMD5(true); } notifyDataSetChanged(); Toast.makeText(context, "数据来源切换成功", Toast.LENGTH_SHORT).show(); @@ -112,6 +121,8 @@ private void update(ListModel listModel) { //JSON String jsonData = listModel.isForceUpdate() ? jsonDataForce : jsonDataUnForce; AppUpdateUtils.getInstance().getUpdateConfig().setUiThemeType(listModel.getUiTypeValue()); + //因为接口未使用文件加密校验 所以在这里关闭 + AppUpdateUtils.getInstance().getUpdateConfig().setNeedFileMD5Check(false); AppUpdateUtils.getInstance().getUpdateConfig().setDataSourceType(listModel.getSourceTypeVaule()); AppUpdateUtils.getInstance().checkUpdate(jsonData); } else if (listModel.getSourceTypeVaule() == TypeConfig.DATA_SOURCE_TYPE_MODEL) { @@ -119,14 +130,33 @@ private void update(ListModel listModel) { .setFileSize(31338250) .setProdVersionCode(25) .setProdVersionName("2.3.1") + .setMd5Check("68919BF998C29DA3F5BD2C0346281AC0") .setForceUpdateFlag(listModel.isForceUpdate() ? 1 : 0) .setUpdateLog("1、优化细节和体验,更加稳定\n2、引入大量优质用户\r\n3、修复已知bug\n4、风格修改"); AppUpdateUtils.getInstance().getUpdateConfig().setUiThemeType(listModel.getUiTypeValue()); + //打开文件MD5校验 + AppUpdateUtils.getInstance().getUpdateConfig().setNeedFileMD5Check(true); AppUpdateUtils.getInstance().getUpdateConfig().setDataSourceType(listModel.getSourceTypeVaule()); - AppUpdateUtils.getInstance().checkUpdate(info); + + //因为这里打开了MD5的校验 我在这里添加一个MD5检验监听监听 + AppUpdateUtils.getInstance() + .addMd5CheckListener(new MD5CheckListener() { + @Override + public void fileMd5CheckFail(String originMD5, String localMD5) { + Toast.makeText(context, "检验失败 ---> originMD5:" + originMD5 + " localMD5:" + localMD5, Toast.LENGTH_SHORT).show(); + } + + @Override + public void fileMd5CheckSuccess() { + Toast.makeText(context, "文件MD5校验成功", Toast.LENGTH_SHORT).show(); + } + }) + .checkUpdate(info); } else if (listModel.getSourceTypeVaule() == TypeConfig.DATA_SOURCE_TYPE_URL) { //请求 AppUpdateUtils.getInstance().getUpdateConfig().setUiThemeType(listModel.getUiTypeValue()); + //因为接口未使用文件加密校验 所以在这里关闭 + AppUpdateUtils.getInstance().getUpdateConfig().setNeedFileMD5Check(false); AppUpdateUtils.getInstance().getUpdateConfig().setDataSourceType(listModel.getSourceTypeVaule()); AppUpdateUtils.getInstance().checkUpdate(); } diff --git a/app/src/main/java/com/cretin/www/autoupdateproject/app/BaseApp.java b/app/src/main/java/com/cretin/www/autoupdateproject/app/BaseApp.java index 8129921..5eeaa77 100644 --- a/app/src/main/java/com/cretin/www/autoupdateproject/app/BaseApp.java +++ b/app/src/main/java/com/cretin/www/autoupdateproject/app/BaseApp.java @@ -30,6 +30,7 @@ public void onCreate() { .setRequestHeaders(null)//当dataSourceType为DATA_SOURCE_TYPE_URL时,设置请求的请求头 .setRequestParams(null)//当dataSourceType为DATA_SOURCE_TYPE_URL时,设置请求的请求参数 .setCustomActivityClass(CustomActivity.class)//如果你选择的UI样式为TypeConfig.UI_THEME_CUSTOM,那么你需要自定义一个Activity继承自RootActivity,并参照demo实现功能,在此处填写自定义Activity的class + .setNeedFileMD5Check(false)//是否需要进行文件的MD5检验,如果开启需要提供文件本身正确的MD5校验码,DEMO中提供了获取文件MD5检验码的工具页面,也提供了加密工具类Md5Utils .setModelClass(new UpdateModel()); AppUpdateUtils.init(this, updateConfig); } diff --git a/app/src/main/java/com/cretin/www/autoupdateproject/model/ListModel.java b/app/src/main/java/com/cretin/www/autoupdateproject/model/ListModel.java index 6d327eb..7644e90 100644 --- a/app/src/main/java/com/cretin/www/autoupdateproject/model/ListModel.java +++ b/app/src/main/java/com/cretin/www/autoupdateproject/model/ListModel.java @@ -10,6 +10,15 @@ public class ListModel { private boolean isForceUpdate; private int uiTypeValue; private int sourceTypeVaule; + private boolean isCheckFileMD5; + + public boolean isCheckFileMD5() { + return isCheckFileMD5; + } + + public void setCheckFileMD5(boolean checkFileMD5) { + isCheckFileMD5 = checkFileMD5; + } public int getUiTypeValue() { return uiTypeValue; diff --git a/app/src/main/java/com/cretin/www/autoupdateproject/model/UpdateModel.java b/app/src/main/java/com/cretin/www/autoupdateproject/model/UpdateModel.java index f1db032..422c586 100644 --- a/app/src/main/java/com/cretin/www/autoupdateproject/model/UpdateModel.java +++ b/app/src/main/java/com/cretin/www/autoupdateproject/model/UpdateModel.java @@ -149,4 +149,9 @@ public String getAppApkSize() { public String getAppHasAffectCodes() { return getHasAffectCodes(); } + + @Override + public String getFileMd5Check() { + return null; + } } diff --git a/app/src/main/java/com/cretin/www/autoupdateproject/utils/Md5Utils.java b/app/src/main/java/com/cretin/www/autoupdateproject/utils/Md5Utils.java new file mode 100644 index 0000000..46ece0d --- /dev/null +++ b/app/src/main/java/com/cretin/www/autoupdateproject/utils/Md5Utils.java @@ -0,0 +1,67 @@ +package com.cretin.www.autoupdateproject.utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.MessageDigest; + +/** + * MD5加密工具类 + * + */ +public final class Md5Utils { + + private Md5Utils() { + throw new UnsupportedOperationException("cannot be instantiated"); + } + + /** + * 获取文件的MD5值 + * + * @param file + * @return + */ + public static String getFileMD5(File file) { + if (file == null || !file.exists()) { + return ""; + } + FileInputStream fis = null; + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + fis = new FileInputStream(file); + byte[] buffer = new byte[8192]; + int len; + while ((len = fis.read(buffer)) != -1) { + digest.update(buffer, 0, len); + } + return bytes2Hex(digest.digest()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException ignored) { + } + } + } + return ""; + } + + /** + * 一个byte转为2个hex字符 + * + * @param src byte数组 + * @return 16进制大写字符串 + */ + private static String bytes2Hex(byte[] src) { + char[] res = new char[src.length << 1]; + final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + for (int i = 0, j = 0; i < src.length; i++) { + res[j++] = hexDigits[src[i] >>> 4 & 0x0F]; + res[j++] = hexDigits[src[i] & 0x0F]; + } + return new String(res); + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_md5_helper.xml b/app/src/main/res/layout/activity_md5_helper.xml new file mode 100644 index 0000000..b5ec3d5 --- /dev/null +++ b/app/src/main/res/layout/activity_md5_helper.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_recyclerview.xml b/app/src/main/res/layout/item_recyclerview.xml index befbf1b..0bb89e0 100644 --- a/app/src/main/res/layout/item_recyclerview.xml +++ b/app/src/main/res/layout/item_recyclerview.xml @@ -7,8 +7,9 @@ android:paddingTop="16dp"> + \ No newline at end of file diff --git a/cretinautoupdatelibrary/build.gradle b/cretinautoupdatelibrary/build.gradle index d6ebafd..e4e55aa 100644 --- a/cretinautoupdatelibrary/build.gradle +++ b/cretinautoupdatelibrary/build.gradle @@ -30,7 +30,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' - implementation 'com.liulishuo.filedownloader:library:1.7.6' + api 'com.liulishuo.filedownloader:library:1.7.6' } repositories { diff --git a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/activity/RootActivity.java b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/activity/RootActivity.java index 0f96268..05e25bd 100644 --- a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/activity/RootActivity.java +++ b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/activity/RootActivity.java @@ -16,6 +16,7 @@ import com.cretin.www.cretinautoupdatelibrary.R; import com.cretin.www.cretinautoupdatelibrary.interfaces.AppDownloadListener; +import com.cretin.www.cretinautoupdatelibrary.interfaces.MD5CheckListener; import com.cretin.www.cretinautoupdatelibrary.interfaces.OnDialogClickListener; import com.cretin.www.cretinautoupdatelibrary.model.DownloadInfo; import com.cretin.www.cretinautoupdatelibrary.service.UpdateService; @@ -37,6 +38,10 @@ public abstract class RootActivity extends AppCompatActivity { public DownloadInfo downloadInfo; + private AppDownloadListener appDownloadListener = obtainDownloadListener(); + + private MD5CheckListener md5CheckListener = obtainMD5CheckListener(); + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -109,11 +114,23 @@ public void cancelTask() { * 执行下载 */ private void doDownload() { - AppUpdateUtils.getInstance().download(downloadInfo, obtainDownloadListener()); + AppUpdateUtils.getInstance() + .addMd5CheckListener(md5CheckListener) + .addAppDownloadListener(appDownloadListener) + .download(downloadInfo); } public abstract AppDownloadListener obtainDownloadListener(); + /** + * 如果需要监听MD5校验结果 自行重写此方法 + * + * @return + */ + public MD5CheckListener obtainMD5CheckListener() { + return null; + } + /** * 获取权限 */ @@ -157,4 +174,15 @@ public void download() { requestPermission(); } } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (appDownloadListener != null) { + AppUpdateUtils.getInstance().removeAppDownloadListener(appDownloadListener); + } + if (md5CheckListener != null) { + AppUpdateUtils.getInstance().removeMD5CheckListener(md5CheckListener); + } + } } diff --git a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/interfaces/MD5CheckListener.java b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/interfaces/MD5CheckListener.java new file mode 100644 index 0000000..c99120e --- /dev/null +++ b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/interfaces/MD5CheckListener.java @@ -0,0 +1,13 @@ +package com.cretin.www.cretinautoupdatelibrary.interfaces; + +/** + * @date: on 2019-10-22 + * @author: a112233 + * @email: mxnzp_life@163.com + * @desc: MD5检验监听 如果开启了MD5校验才会回调 + */ +public interface MD5CheckListener { + void fileMd5CheckFail(String originMD5, String localMD5); + + void fileMd5CheckSuccess(); +} diff --git a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/DownloadInfo.java b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/DownloadInfo.java index 1337b0a..e8ae374 100644 --- a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/DownloadInfo.java +++ b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/DownloadInfo.java @@ -24,6 +24,17 @@ public class DownloadInfo implements Parcelable { private int forceUpdateFlag; //受影响的版本号 如果开启强制更新 那么这个字段包含的所有版本都会被强制更新 格式 2|3|4 private String hasAffectCodes; + //文件MD5的校验值 + private String md5Check; + + public String getMd5Check() { + return md5Check; + } + + public DownloadInfo setMd5Check(String md5Check) { + this.md5Check = md5Check; + return this; + } public String getHasAffectCodes() { return hasAffectCodes; @@ -107,6 +118,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.updateLog); dest.writeInt(this.forceUpdateFlag); dest.writeString(this.hasAffectCodes); + dest.writeString(this.md5Check); } public DownloadInfo() { @@ -120,6 +132,7 @@ protected DownloadInfo(Parcel in) { this.updateLog = in.readString(); this.forceUpdateFlag = in.readInt(); this.hasAffectCodes = in.readString(); + this.md5Check = in.readString(); } public static final Creator CREATOR = new Creator() { diff --git a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/LibraryUpdateEntity.java b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/LibraryUpdateEntity.java index bd61fcf..77d84f7 100644 --- a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/LibraryUpdateEntity.java +++ b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/LibraryUpdateEntity.java @@ -25,4 +25,7 @@ public interface LibraryUpdateEntity { //受影响的版本号 如果开启强制更新 那么这个字段包含的所有版本都会被强制更新 格式 2|3|4 String getAppHasAffectCodes(); + + //获取文件的加密校验值 + String getFileMd5Check(); } diff --git a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/UpdateConfig.java b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/UpdateConfig.java index 93b4352..0876eca 100644 --- a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/UpdateConfig.java +++ b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/UpdateConfig.java @@ -43,6 +43,9 @@ public class UpdateConfig { //自定义Bean类 此类必须实现LibraryUpdateEntity接口 private Object modelClass; + //是否需要进行文件的MD5校验 + private boolean isNeedFileMD5Check; + public boolean isShowNotification() { return showNotification; } @@ -61,6 +64,15 @@ public UpdateConfig setCustomActivityClass(Class customActivityClass) { return this; } + public boolean isNeedFileMD5Check() { + return isNeedFileMD5Check; + } + + public UpdateConfig setNeedFileMD5Check(boolean needFileMD5Check) { + isNeedFileMD5Check = needFileMD5Check; + return this; + } + public int getNotificationIconRes() { return notificationIconRes; } diff --git a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/UpdateEntity.java b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/UpdateEntity.java index 2d22e9e..b39db36 100644 --- a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/UpdateEntity.java +++ b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/model/UpdateEntity.java @@ -119,4 +119,9 @@ public String getAppApkSize() { public String getAppHasAffectCodes() { return getHasAffectCodes(); } + + @Override + public String getFileMd5Check() { + return ""; + } } diff --git a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/utils/AppUpdateUtils.java b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/utils/AppUpdateUtils.java index 1b188e0..b321f4f 100644 --- a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/utils/AppUpdateUtils.java +++ b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/utils/AppUpdateUtils.java @@ -19,11 +19,11 @@ import com.cretin.www.cretinautoupdatelibrary.activity.UpdateType8Activity; import com.cretin.www.cretinautoupdatelibrary.activity.UpdateType9Activity; import com.cretin.www.cretinautoupdatelibrary.interfaces.AppDownloadListener; +import com.cretin.www.cretinautoupdatelibrary.interfaces.MD5CheckListener; import com.cretin.www.cretinautoupdatelibrary.model.DownloadInfo; import com.cretin.www.cretinautoupdatelibrary.model.LibraryUpdateEntity; import com.cretin.www.cretinautoupdatelibrary.model.TypeConfig; import com.cretin.www.cretinautoupdatelibrary.model.UpdateConfig; -import com.cretin.www.cretinautoupdatelibrary.model.UpdateEntity; import com.cretin.www.cretinautoupdatelibrary.net.HttpCallbackModelListener; import com.cretin.www.cretinautoupdatelibrary.net.HttpUtils; import com.cretin.www.cretinautoupdatelibrary.service.UpdateReceiver; @@ -35,6 +35,8 @@ import com.liulishuo.filedownloader.util.FileDownloadUtils; import java.io.File; +import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -53,8 +55,6 @@ public class AppUpdateUtils { private static UpdateConfig updateConfig; //是否初始化 private static boolean isInit; - //下载过程监听 - private AppDownloadListener appDownloadListener; //下载任务 private BaseDownloadTask downloadTask; @@ -68,9 +68,16 @@ public class AppUpdateUtils { //apk下载的路径 private static String downloadUpdateApkFilePath = ""; + //AppDownloadListener的集合 + private List appDownloadListenerList; + + //MD5校验监听 + private List md5CheckListenerList; + //私有化构造方法 private AppUpdateUtils() { - + appDownloadListenerList = new ArrayList<>(); + md5CheckListenerList = new ArrayList<>(); } /** @@ -247,12 +254,8 @@ public void checkUpdate(DownloadInfo info) { * 开始下载 * * @param info - * @param appDownloadListener */ - public void download(DownloadInfo info, AppDownloadListener appDownloadListener) { - if (appDownloadListener != null) - this.appDownloadListener = appDownloadListener; - + public void download(DownloadInfo info) { checkInit(); downloadInfo = info; @@ -310,7 +313,7 @@ protected void progress(BaseDownloadTask task, long soFarBytes, long totalBytes) @Override protected void paused(BaseDownloadTask task, long soFarBytes, long totalBytes) { - if (appDownloadListener != null) { + for (AppDownloadListener appDownloadListener : getAllAppDownloadListener()) { appDownloadListener.pause(); } } @@ -340,7 +343,7 @@ private void downloadError(Throwable e) { isDownloading = false; AppUtils.deleteFile(downloadUpdateApkFilePath); UpdateReceiver.send(mContext, -1); - if (appDownloadListener != null) { + for (AppDownloadListener appDownloadListener : getAllAppDownloadListener()) { appDownloadListener.downloadFail(e.getMessage()); } LogUtils.log("文件下载出错,异常信息为:" + e.getMessage()); @@ -354,13 +357,44 @@ private void downloadError(Throwable e) { private void downloadComplete(String path) { isDownloading = false; UpdateReceiver.send(mContext, 100); - if (appDownloadListener != null) { + for (AppDownloadListener appDownloadListener : getAllAppDownloadListener()) { appDownloadListener.downloadComplete(path); } LogUtils.log("文件下载完成,准备安装,文件地址:" + downloadUpdateApkFilePath); - AppUtils.installApkFile(mContext, new File(path)); + //校验MD5 + File newFile = new File(path); + if (newFile.exists()) { + //如果需要进行MD5校验 + if (updateConfig.isNeedFileMD5Check()) { + try { + String md5 = Md5Utils.getFileMD5(newFile); + if (!TextUtils.isEmpty(md5) && md5.equals(downloadInfo.getMd5Check())) { + //校验成功 + for (MD5CheckListener md5CheckListener : getAllMD5CheckListener()) { + md5CheckListener.fileMd5CheckSuccess(); + } + AppUtils.installApkFile(mContext, newFile); + LogUtils.log("文件MD5校验成功"); + } else { + //校验失败 + for (MD5CheckListener md5CheckListener : getAllMD5CheckListener()) { + md5CheckListener.fileMd5CheckFail(downloadInfo.getMd5Check(), md5); + } + LogUtils.log("文件MD5校验失败,originMD5:" + downloadInfo.getMd5Check() + " localMD5:" + md5); + } + } catch (Exception e) { + LogUtils.log("文件MD5解析失败,抛出异常:" + e.getMessage()); + //安装文件 + AppUtils.installApkFile(mContext, newFile); + } + } else { + //安装文件 + AppUtils.installApkFile(mContext, newFile); + } + } } + /** * 正在下载 * @@ -372,7 +406,7 @@ private void downloading(long soFarBytes, long totalBytes) { int progress = (int) (soFarBytes * 100.0 / totalBytes); if (progress < 0) progress = 0; UpdateReceiver.send(mContext, progress); - if (appDownloadListener != null) { + for (AppDownloadListener appDownloadListener : getAllAppDownloadListener()) { appDownloadListener.downloading(progress); } LogUtils.log("文件正在下载中,进度为" + progress + "%"); @@ -385,7 +419,7 @@ private void downloadStart() { LogUtils.log("文件开始下载"); isDownloading = true; UpdateReceiver.send(mContext, 0); - if (appDownloadListener != null) { + for (AppDownloadListener appDownloadListener : getAllAppDownloadListener()) { appDownloadListener.downloadStart(); } } @@ -427,8 +461,10 @@ public Context getContext() { * 重新下载 */ public void reDownload() { - appDownloadListener.reDownload(); - download(downloadInfo, appDownloadListener); + for (AppDownloadListener appDownloadListener : getAllAppDownloadListener()) { + appDownloadListener.reDownload(); + } + download(downloadInfo); } /** @@ -492,7 +528,48 @@ private void requestSuccess(Object response) { .setProdVersionName(libraryUpdateEntity.getAppVersionName()) .setApkUrl(libraryUpdateEntity.getAppApkUrls()) .setHasAffectCodes(libraryUpdateEntity.getAppHasAffectCodes()) + .setMd5Check(libraryUpdateEntity.getFileMd5Check()) .setUpdateLog(libraryUpdateEntity.getAppUpdateLog())); } } + + public AppUpdateUtils addMd5CheckListener(MD5CheckListener md5CheckListener) { + if (md5CheckListener != null && !md5CheckListenerList.contains(md5CheckListener)) { + md5CheckListenerList.add(md5CheckListener); + } + return this; + } + + public AppUpdateUtils addAppDownloadListener(AppDownloadListener appDownloadListener) { + if (appDownloadListener != null && !appDownloadListenerList.contains(appDownloadListener)) { + appDownloadListenerList.add(appDownloadListener); + } + return this; + } + + private List getAllAppDownloadListener() { + List listeners = new ArrayList<>(); + listeners.addAll(appDownloadListenerList); + return listeners; + } + + private List getAllMD5CheckListener() { + List listeners = new ArrayList<>(); + listeners.addAll(md5CheckListenerList); + return listeners; + } + + //移除不需要的AppDownloadListener + public void removeAppDownloadListener(AppDownloadListener appDownloadListener) { + if (appDownloadListener != null && appDownloadListenerList.contains(appDownloadListener)) { + appDownloadListenerList.remove(appDownloadListener); + } + } + + //移除不需要的MD5CheckListener + public void removeMD5CheckListener(MD5CheckListener md5CheckListener) { + if (md5CheckListener != null && md5CheckListenerList.contains(md5CheckListener)) { + md5CheckListenerList.remove(md5CheckListener); + } + } } diff --git a/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/utils/Md5Utils.java b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/utils/Md5Utils.java new file mode 100644 index 0000000..36692de --- /dev/null +++ b/cretinautoupdatelibrary/src/main/java/com/cretin/www/cretinautoupdatelibrary/utils/Md5Utils.java @@ -0,0 +1,66 @@ +package com.cretin.www.cretinautoupdatelibrary.utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.MessageDigest; + +/** + * MD5加密工具类 + * + */ +public final class Md5Utils { + + private Md5Utils() { + throw new UnsupportedOperationException("cannot be instantiated"); + } + + /** + * 获取文件的MD5值 + * + * @param file + * @return + */ + public static String getFileMD5(File file) throws Exception { + if (file == null || !file.exists()) { + return ""; + } + FileInputStream fis = null; + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + fis = new FileInputStream(file); + byte[] buffer = new byte[8192]; + int len; + while ((len = fis.read(buffer)) != -1) { + digest.update(buffer, 0, len); + } + return bytes2Hex(digest.digest()); + } catch (Exception e) { + throw new Exception(e); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException ignored) { + } + } + } + } + + /** + * 一个byte转为2个hex字符 + * + * @param src byte数组 + * @return 16进制大写字符串 + */ + private static String bytes2Hex(byte[] src) { + char[] res = new char[src.length << 1]; + final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + for (int i = 0, j = 0; i < src.length; i++) { + res[j++] = hexDigits[src[i] >>> 4 & 0x0F]; + res[j++] = hexDigits[src[i] & 0x0F]; + } + return new String(res); + } + +} \ No newline at end of file diff --git a/pic/demo.apk b/pic/demo.apk index c9b1293..660d5cc 100644 Binary files a/pic/demo.apk and b/pic/demo.apk differ