# mypatch **Repository Path**: gzlpsdp/mypatch ## Basic Information - **Project Name**: mypatch - **Description**: App增量更新合并差分文件 - **Primary Language**: Android - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2024-06-27 - **Last Updated**: 2026-04-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # mypatch [![](https://jitpack.io/v/com.gitee.gzlpsdp/mypatch.svg)](https://jitpack.io/#com.gitee.gzlpsdp/mypatch) ## 介绍 该库是一个功能完整的 Android 应用更新模块,支持全量更新和差分更新,完全解耦 UI,需配合回调自行实现界面。下面分手动更新和自动更新两种场景,提供从配置到实现的完整代码示例。 ## API ### 1. 入口类:AppUpdate **静态方法** |方法| 描述| |----|----| |void| init(UpdateConfig config)| 必须在使用前调用一次,初始化更新模块。传入配置对象。| |AppUpdate| getInstance()| 获取单例实例。需先调用 init()。| **实例方法** |方法| 描述| |----|----| |void setUpdateParser(UpdateParser parser)| (可选)设置自定义的更新信息解析器,用于解析服务器响应。| |String getCurrentVersion() |获取当前应用的版本名(versionName)。| |void checkUpdate(UpdateCheckCallback callback)| 检查更新。结果通过回调返回。| |void downloadAndInstall(UpdateInfo info, DownloadCallback downloadCallback, InstallCallback installCallback)| 下载并安装更新。自动判断使用全量包还是差分包。| |void cancelDownload()| 取消当前正在进行的下载任务。| ### 2. 配置类:UpdateConfig 通过 UpdateConfig.Builder 构建,必须设置 checkUrl。 **Builder 方法** |方法| 描述| |----|----| |Builder(Context context)| 构造函数,传入 Context(建议使用 ApplicationContext)。| |Builder setPackageName(String packageName)| 设置目标应用包名,默认使用当前应用的包名。| |Builder setApkStorePath(String path)| 设置 APK 和补丁文件的存储目录,默认使用 context.getExternalFilesDir("apk")。| |Builder setCheckUrl(String url)| 必须设置检查更新的接口 URL。| |Builder addRequestHeader(String key, String value)| 添加 HTTP 请求头,可多次调用。| |Builder setExecutor(ExecutorService executor)| 自定义线程池,默认使用 Executors.newCachedThreadPool()。| |Builder enableLog(boolean enable)| 是否打印内部日志,默认 false。| |UpdateConfig build()| 构建配置对象。| **配置对象方法(通常由模块内部使用,但也可公开)** |方法| 描述 |----|----| |Context getContext()| 获取 ApplicationContext。 |String getPackageName()| 获取目标包名。 |String getApkStorePath() |获取存储路径。 |String getCheckUrl()| 获取检查 URL。 |Map getRequestHeaders()| 获取请求头。 |ExecutorService getExecutor()| 获取线程池。 |boolean isLogEnabled() |是否启用日志。 ### 3. 更新信息抽象类:UpdateInfo 需要调用方在解析服务器响应后,提供一个实现了该抽象类的对象。模块内部通过它获取更新所需数据。 **必须实现的抽象方法** |方法| 描述 |----|----| |String getNewVersion() |新版本号。 |String getDownloadUrl() |全量包下载地址。 |String getPatchDownloadUrl() |差分包下载地址(若无差分可返回 null)。 |String getOldVersion() |差分所需的基础版本号(当有差分包时有效)。 |String getDescription() |更新说明。 |boolean isForceUpdate() |是否强制更新。 ### 4. 回调接口 **UpdateCheckCallback(检查更新回调)** |方法| 描述 |----|----| |void onSuccess(UpdateInfo info)| 检查成功,返回更新信息。 |void onFailure(int code, String message) |检查失败,code 为 HTTP 状态码或 -1,message 为错误描述。 **DownloadCallback(下载回调)** |方法| 描述| |----|----| |void onStart() |下载开始。 |void onProgress(int percent) |下载进度(0-100)。 |void onComplete(File file) |下载完成,返回下载的文件。 |void onError(String message) |下载失败,返回错误信息。 **InstallCallback(安装回调)** |方法| 描述| |----|----| |void onSuccess()| 安装启动成功(注意:安装过程由系统完成,此回调仅表示安装 Intent 已发出)。 |void onFailure(String message)| 安装失败,返回错误信息。 |void onNeedPermission(Intent intent)| Android 8.0+ 需要请求安装未知来源权限时调用,调用方应使用 startActivityForResult(intent, requestCode) 启动权限设置页面。 ### 5. 解析器接口:UpdateParser 用于自定义服务器响应解析,实现该接口后通过 AppUpdate.getInstance().setUpdateParser(parser) 设置。 |方法| 描述| |----|----| |UpdateInfo parse(String response) throws Exception |解析服务器返回的 JSON 字符串,返回 UpdateInfo 对象。 **默认实现:DefaultUpdateParser** 默认解析器期望服务器返回如下 JSON 格式: ```json { "data": { "version": "x.x.x", "downurl": "https://...", "patchPath": "https://...", "oldVersion": "x.x.x", "description": "...", "force": false } } ``` # 如何使用 ## 1. 添加依赖 在项目根目录的 build.gradle 中添加码云 Maven 仓库: ```gradle dependencies { implementation 'com.gitee.gzlpsdp:mypatch:1.6.0' } ``` ## 2. 必要配置 **2.1 添加权限** 在 AndroidManifest.xml 中添加网络权限: ```xml ``` 如果希望将 APK 下载到外部存储公共目录(非必须,应用专属目录无需存储权限),则需添加: ```xml ``` **2.2 配置 FileProvider(Android 7.0+ 必需)在 AndroidManifest.xml 的 标签内添加:** ```xml ``` 创建 res/xml/file_paths.xml 文件,内容如下(根据你设置的存储路径调整): ```xml ``` 也可以直接使用 ```xml ``` **2.3 确保 Native 库存在** 库内部依赖 libmyPatch.so,该 so 文件已包含在 AAR 中,无需额外处理。 ## 3. 初始化更新模块 建议在自定义 Application 类的 onCreate() 中完成初始化。 ```java public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); // 构建配置 UpdateConfig config = new UpdateConfig.Builder(this) .setCheckUrl("https://your-server.com/api/update") // 替换为你的检查接口 .setApkStorePath(getExternalFilesDir("apk").getAbsolutePath()) .addRequestHeader("User-Agent", "MyApp/1.0") // 可选 .enableLog(BuildConfig.DEBUG) // 调试时开启日志 .build(); // 初始化 AppUpdate.init(config); } } ``` 别忘了在 AndroidManifest.xml 中注册该 Application: ```xml ... ``` ## 4. 手动更新示例(用户主动触发) **4.1 在 Activity 中实现手动检查** ```java public class MainActivity extends AppCompatActivity { private static final int REQUEST_INSTALL_PERMISSION = 1001; private UpdateInfo latestInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn_check_update).setOnClickListener(v -> checkUpdateManually()); } private void checkUpdateManually() { // 显示进度对话框 ProgressDialog pd = new ProgressDialog(this); pd.setMessage("正在检查更新..."); pd.setCancelable(false); pd.show(); AppUpdate.getInstance().checkUpdate(new UpdateCheckCallback() { @Override public void onSuccess(UpdateInfo info) { pd.dismiss(); latestInfo = info; String currentVer = AppUpdate.getInstance().getCurrentVersion(); if (needUpdate(currentVer, info.getNewVersion())) { showUpdateDialog(info); } else { Toast.makeText(MainActivity.this, "已是最新版本", Toast.LENGTH_SHORT).show(); } } @Override public void onFailure(int code, String message) { pd.dismiss(); Toast.makeText(MainActivity.this, "检查更新失败:" + message, Toast.LENGTH_SHORT).show(); } }); } // 简单版本比较(可根据实际版本格式优化) private boolean needUpdate(String current, String latest) { return !current.equals(latest); } private void showUpdateDialog(UpdateInfo info) { AlertDialog.Builder builder = new AlertDialog.Builder(this) .setTitle("发现新版本 " + info.getNewVersion()) .setMessage(info.getDescription()) .setPositiveButton("立即更新", (dialog, which) -> startDownload(info)); if (info.isForceUpdate()) { builder.setCancelable(false); } else { builder.setNegativeButton("以后再说", null); } builder.show(); } private void startDownload(UpdateInfo info) { ProgressDialog pd = new ProgressDialog(this); pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); pd.setMessage("下载中..."); pd.setCancelable(false); pd.show(); AppUpdate.getInstance().downloadAndInstall(info, new DownloadCallback() { @Override public void onStart() { } @Override public void onProgress(int percent) { pd.setProgress(percent); } @Override public void onComplete(File file) { pd.dismiss(); } @Override public void onError(String message) { pd.dismiss(); Toast.makeText(MainActivity.this, "下载失败:" + message, Toast.LENGTH_SHORT).show(); } }, new InstallCallback() { @Override public void onSuccess() { } @Override public void onFailure(String message) { Toast.makeText(MainActivity.this, "安装失败:" + message, Toast.LENGTH_SHORT).show(); } @Override public void onNeedPermission(Intent intent) { startActivityForResult(intent, REQUEST_INSTALL_PERMISSION); } } ); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_INSTALL_PERMISSION) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (getPackageManager().canRequestPackageInstalls()) { // 权限已获取,重新下载安装(需保存 latestInfo) startDownload(latestInfo); } else { Toast.makeText(this, "未获得安装权限,无法自动安装", Toast.LENGTH_SHORT).show(); } } } } } ``` ## 5. 自动更新示例(后台静默检查) 自动更新通常在应用启动时或特定时机执行。我们建议封装一个 UpdateManager 来统一管理更新逻辑,避免代码分散。下面是一个简单的实现。 **5.1 创建 UpdateManager 单例类** ```java public class UpdateManager { private static volatile UpdateManager instance; private Context appContext; private UpdateInfo latestInfo; private UpdateManager(Context context) { this.appContext = context.getApplicationContext(); } public static UpdateManager getInstance(Context context) { if (instance == null) { synchronized (UpdateManager.class) { if (instance == null) { instance = new UpdateManager(context); } } } return instance; } /** * 自动检查更新(静默方式) * @param foregroundActivity 当前前台的 Activity,用于强制更新时弹窗,若为 null 则只发送通知 */ public void checkAutoUpdate(final Activity foregroundActivity) { AppUpdate.getInstance().checkUpdate(new UpdateCheckCallback() { @Override public void onSuccess(UpdateInfo info) { latestInfo = info; String current = AppUpdate.getInstance().getCurrentVersion(); if (!current.equals(info.getNewVersion())) { // 发现新版本 if (info.isForceUpdate()) { // 强制更新:必须立即弹窗,需要前台 Activity if (foregroundActivity != null) { showForceUpdateDialog(foregroundActivity, info); } else { // 无前台 Activity,可发送通知,但强制更新建议直接退出 } } else { // 非强制更新:可发送通知栏提醒 showUpdateNotification(info); } } } @Override public void onFailure(int code, String message) { // 静默失败不处理 } }); } private void showForceUpdateDialog(Activity activity, UpdateInfo info) { AlertDialog dialog = new AlertDialog.Builder(activity) .setTitle("强制更新") .setMessage(info.getDescription()) .setPositiveButton("立即更新", (d, w) -> startDownload(activity, info)) .setCancelable(false) .create(); dialog.setCanceledOnTouchOutside(false); dialog.show(); } private void showUpdateNotification(UpdateInfo info) { // 构建通知(略,可使用 NotificationManager) // 点击通知后跳转到某个 Activity 并传入 info 开始下载 } private void startDownload(Activity activity, UpdateInfo info) { // 同手动更新中的下载安装代码,可复用 ProgressDialog pd = new ProgressDialog(activity); pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); pd.setMessage("下载中..."); pd.setCancelable(false); pd.show(); AppUpdate.getInstance().downloadAndInstall(info, new DownloadCallback() { @Override public void onStart() { } @Override public void onProgress(int percent) { pd.setProgress(percent); } @Override public void onComplete(File file) { pd.dismiss(); } @Override public void onError(String message) { pd.dismiss(); Toast.makeText(activity, "下载失败", Toast.LENGTH_SHORT).show(); } }, new InstallCallback() { @Override public void onSuccess() { } @Override public void onFailure(String message) { Toast.makeText(activity, "安装失败", Toast.LENGTH_SHORT).show(); } @Override public void onNeedPermission(Intent intent) { activity.startActivityForResult(intent, 1001); } } ); } } ``` **5.2 在 Application 中启动自动更新** ```java public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); // 初始化配置(同上) UpdateConfig config = ...; AppUpdate.init(config); // 延迟5秒后自动检查(可调整) new Handler().postDelayed(() -> { // 注意:此时可能没有前台 Activity,因此传入 null UpdateManager.getInstance(this).checkAutoUpdate(null); }, 5000); } } ``` 如果需要在前台时进行自动更新(例如用户回到应用时),可以在 BaseActivity 中注册监听,然后调用 UpdateManager 的检查方法并传入当前 Activity。 ## 6. 关于版本号比较 上述示例使用简单的字符串 equals 比较,但在实际业务中版本号可能为 1.2.3 格式,建议使用 versionCode(整数)进行比较。库中 AppUpdate.getInstance().getCurrentVersion() 返回的是 versionName,你也可以自行通过 PackageManager 获取 versionCode: ```java int currentCode = context.getPackageManager() .getPackageInfo(context.getPackageName(), 0).versionCode; ``` 如果服务器返回的 JSON 中包含 versionCode,则可以在 UpdateInfo 中提供 getNewVersionCode() 方法,然后在自定义解析器中实现。 ## 7. 自定义 UpdateInfo 解析(可选) 如果服务器返回的 JSON 格式与默认不同,可以实现 UpdateParser 接口: ```java public class CustomUpdateParser implements UpdateParser { @Override public UpdateInfo parse(String response) throws Exception { JSONObject root = new JSONObject(response); JSONObject data = root.getJSONObject("data"); return new UpdateInfo() { @Override public String getNewVersion() { return data.optString("ver"); } @Override public String getDownloadUrl() { return data.optString("full_url"); } @Override public String getPatchDownloadUrl() { return data.optString("patch_url"); } @Override public String getOldVersion() { return data.optString("old_ver"); } @Override public String getDescription() { return data.optString("desc"); } @Override public boolean isForceUpdate() { return data.optBoolean("force", false); } }; } } // 在初始化后设置 AppUpdate.getInstance().setUpdateParser(new CustomUpdateParser()); ``` ## 8. 常见问题 - FileProvider 冲突:如果应用中已使用其他 FileProvider,需合并 file_paths.xml,并确保 authorities 唯一。 - 安装时解析错误:可能是 APK 下载不完整或差分合成失败,请检查日志。 - 权限问题:Android 8.0+ 需要动态申请安装未知来源权限,已在回调中处理。 - Native 库找不到:检查 APK 是否包含 lib/armeabi-v7a/libmyPatch.so 等文件,可解压 APK 确认。 ## 9. 完整项目示例结构 建议将更新相关代码统一放在一个包内,例如: ```text com.example.app ├── update │ ├── UpdateManager.java # 更新管理器 │ ├── CustomUpdateParser.java # 自定义解析器(可选) │ └── activity │ └── UpdateActivity.java # 处理通知点击后的下载界面 ``` ## 10. 期望后端接口返回的 JSON 数据格式要求 模块默认的更新信息解析器 DefaultUpdateParser 期望后端接口返回的 JSON 数据格式如下: #### 基本响应结构 ```json { "data": { "version": "2.0.0", // 新版本号(字符串,必需) "downurl": "https://.../app.apk", // 全量包下载地址(字符串,必需) "patchPath": "https://.../patch.patch", // 差分包下载地址(字符串,可选) "oldVersion": "1.0.0", // 差分所需的基础版本号(字符串,可选) "description": "更新说明内容", // 更新说明(字符串,可选,可用"|"分隔换行) "force": false // 是否强制更新(布尔值,可选,默认false) } } ``` #### 字段说明 |字段 |类型 |必需 |描述| |------|------|------|------| |version| String| 是| 新版本号,用于版本比较。| |downurl| String| 是| 全量 APK 的下载地址。| |patchPath| String| 否| 差分补丁文件的下载地址。若提供此字段,且设备当前版本与 oldVersion 匹配,则会使用差分更新以节省流量。| |oldVersion| String| 否| 差分补丁对应的旧版本号。如果提供了 patchPath,建议也提供此字段,以便模块判断是否适用差分更新。| |description| String| 否| 更新说明。如果包含 | 字符,会自动替换为换行符,方便在对话框中显示。| |force| Boolean| 否| 是否为强制更新。若为 true,建议调用方显示不可取消的对话框。| #### 示例 ```json { "code": 200, "message": "success", "data": { "version": "3.1.0", "downurl": "https://cdn.example.com/app-v3.1.0.apk", "patchPath": "https://cdn.example.com/patch-v3.0.0-to-v3.1.0.patch", "oldVersion": "3.0.0", "description": "新增暗黑模式|修复登录闪退|优化启动速度", "force": false } } ``` #### 注意事项 - 差分更新条件:只有当 patchPath 不为空且当前应用的版本号与 oldVersion 完全一致时,模块才会自动下载差分包并合成新 APK。否则将回退到全量下载。 - 版本号比较:模块内部仅通过字符串 equals 比较版本号,若您的版本号格式复杂(如 1.2.3),建议在调用方自行实现版本比较逻辑,或通过自定义解析器返回更合适的数据(如增加 versionCode 字段)。 - 自定义解析:如果后端接口格式与上述不同,您可以通过实现 UpdateParser 接口自定义解析逻辑,然后在初始化后调用 AppUpdate.getInstance().setUpdateParser(parser) 替换默认解析器。 #### 自定义解析器示例 假设您的后端返回如下格式: ```json { "version": "2.0.0", "apk_url": "https://.../app.apk", "patch_url": "https://.../patch.patch", "base_version": "1.0.0", "changelog": "更新内容", "force_update": true } ``` 您可以编写自定义解析器: ```java public class MyUpdateParser implements UpdateParser { @Override public UpdateInfo parse(String response) throws Exception { JSONObject root = new JSONObject(response); return new UpdateInfo() { @Override public String getNewVersion() { return root.optString("version"); } @Override public String getDownloadUrl() { return root.optString("apk_url"); } @Override public String getPatchDownloadUrl() { return root.optString("patch_url", null); } @Override public String getOldVersion() { return root.optString("base_version", null); } @Override public String getDescription() { return root.optString("changelog", ""); } @Override public boolean isForceUpdate() { return root.optBoolean("force_update", false); } }; } } ``` 然后在初始化后设置: ```java AppUpdate.getInstance().setUpdateParser(new MyUpdateParser()); ``` 通过以上步骤,你可以轻松地在项目中集成手动和自动更新功能。如果还有疑问,欢迎查阅库的源码或提交 issue。