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