App (Client) → HTTPS POST (XML Body) → Server → HTTPS Response (XML) → Parse → Version Compare → UI Prompt
// build.gradle
implementation "com.squareup.okhttp3:okhttp:4.12.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
public class ApiClient {
private static final OkHttpClient CLIENT = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.hostnameVerifier((hostname, session) -> true) // 僅測試環境,生產需嚴格驗證
.build();
public static OkHttpClient get() { return CLIENT; }
}
@Data
public class UpdateRequest {
private String appId = "com.example.app";
private String currentVersion = BuildConfig.VERSION_NAME;
private int currentVersionCode = BuildConfig.VERSION_CODE;
private String deviceId; // 可選
private String os = "Android";
}
@Data
public class UpdateResponse {
private int code; // 200 成功
private String message;
private UpdateInfo data;
}
@Data
public class UpdateInfo {
private String versionName; // "2.1.0"
private int versionCode; // 20100
private String downloadUrl; // "https://cdn.example.com/app-v2.1.0.apk"
private String releaseNotes; // "修復崩潰、新增功能..."
private boolean forceUpdate; // true: 必須更新
}
**輕量選擇:手工拼接/解析** (無反射、包體積小)
public class XmlUtil {
// Request → XML String
public static String toXml(UpdateRequest req) {
return "" +
"" + escape(req.appId) + " " +
"" + escape(req.currentVersion) + " " +
"" + req.currentVersionCode + " " +
"" + escape(req.deviceId) + " " +
"" + escape(req.os) + " " +
" ";
}
// Response XML → UpdateResponse
public static UpdateResponse parse(String xml) throws XmlPullParserException, IOException {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new StringReader(xml));
UpdateResponse resp = new UpdateResponse();
UpdateInfo info = null;
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
String tag = parser.getName();
switch (eventType) {
case XmlPullParser.START_TAG:
if ("data".equals(tag)) info = new UpdateInfo();
break;
case XmlPullParser.TEXT:
String text = parser.getText();
if (info != null) {
switch (tag) {
case "versionName": info.versionName = text; break;
case "versionCode": info.versionCode = Integer.parseInt(text); break;
case "downloadUrl": info.downloadUrl = text; break;
case "releaseNotes": info.releaseNotes = text; break;
case "forceUpdate": info.forceUpdate = Boolean.parseBoolean(text); break;
}
} else {
switch (tag) {
case "code": resp.code = Integer.parseInt(text); break;
case "message": resp.message = text; break;
}
}
break;
case XmlPullParser.END_TAG:
if ("data".equals(tag)) resp.data = info;
break;
}
eventType = parser.next();
}
return resp;
}
private static String escape(String s) {
return s == null ? "" : s.replace("&", "&").replace("<", "<").replace(">", ">")
.replace("\"", """).replace("'", "'");
}
}
// Kotlin 協程版
suspend fun checkUpdate(context: Context): UpdateInfo? = withContext(Dispatchers.IO) {
val req = UpdateRequest().apply { deviceId = getDeviceId(context) }
val xmlBody = XmlUtil.toXml(req)
val request = Request.Builder()
.url("https://api.example.com/update/check")
.post(xmlBody.toRequestBody("application/xml; charset=utf-8".toMediaType()))
.build()
ApiClient.get().newCall(request).execute().use { response ->
if (!response.isSuccessful) return@withContext null
val xml = response.body?.string() ?: return@withContext null
val parsed = XmlUtil.parse(xml)
if (parsed.code == 200 && parsed.data != null && parsed.data.versionCode > BuildConfig.VERSION_CODE) {
parsed.data
} else null
}
}
// UI 層調用
lifecycleScope.launch {
val info = checkUpdate(this@MainActivity)
if (info != null) showUpdateDialog(info)
}
private fun showUpdateDialog(info: UpdateInfo) {
AlertDialog.Builder(this)
.setTitle("發現新版本 v${info.versionName}")
.setMessage(info.releaseNotes)
.setCancelable(!info.forceUpdate)
.setPositiveButton("立即更新") { _, _ -> startDownload(info.downloadUrl) }
.setNegativeButton("稍後") { _, _ -> if (info.forceUpdate) finish() }
.show()
}
private fun startDownload(url: String) {
val request = DownloadManager.Request(Uri.parse(url))
.setTitle("App 更新")
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "app-update.apk")
val dm = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadId = dm.enqueue(request)
// 可註冊 BroadcastReceiver 監聽 DOWNLOAD_COMPLETE 自動安裝
}
**OkHttp + XmlPullParser + 版本比較邏輯 + DownloadManager** 構建輕量、安全、用戶體驗良好的 Android 版本更新檢查機制。核心在於**嚴格的 XML 解析容錯**與**證書釘裹/簽名驗證**保障安全。