3. 实现客户端检查更新
任务说明
实现一个Android客户端,定期向指定服务器发送版本检查请求,解析服务器返回的JSON响应,判断是否有新版本可用。本案例聚焦客户端逻辑,服务器接口约定如下:
GET http://<server>/api/check_update?versionCode=100&deviceId=abc123
响应JSON格式:
{"code":0,"data":{"hasUpdate":true,"latestVersion":"2.0","latestVersionCode":200,"downloadUrl":"http://.../app-v2.0.apk","releaseNote":"修复若干问题"}}
环境依赖
- Android Studio Arctic Fox或更高版本
- minSdk 21+,添加网络权限:
<uses-permission android:name="android.permission.INTERNET"/> - 依赖:
org.json(Android内置,无需额外引入)
关键完整代码
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class UpdateChecker {
private static final String TAG = "UpdateChecker";
private static final String CHECK_URL = "http://your-server.com/api/check_update";
private static final long CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6小时
private final Context context;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private Runnable periodicTask;
private CheckCallback callback;
public interface CheckCallback {
void onResult(boolean hasUpdate, String latestVersion, int latestVersionCode,
String downloadUrl, String releaseNote);
void onError(String message);
}
public UpdateChecker(Context context) {
this.context = context.getApplicationContext();
}
/** 启动周期性检查 */
public void startPeriodicCheck(CheckCallback callback) {
this.callback = callback;
periodicTask = new Runnable() {
@Override
public void run() {
checkUpdate(callback);
mainHandler.postDelayed(this, CHECK_INTERVAL_MS);
}
};
mainHandler.post(periodicTask);
}
/** 停止周期性检查 */
public void stopPeriodicCheck() {
if (periodicTask != null) {
mainHandler.removeCallbacks(periodicTask);
periodicTask = null;
}
}
/** 执行一次版本检查 */
public void checkUpdate(final CheckCallback callback) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
int currentVersionCode = getCurrentVersionCode();
String deviceId = getDeviceId();
URL url = new URL(CHECK_URL + "?versionCode=" + currentVersionCode
+ "&deviceId=" + deviceId);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
int responseCode = conn.getResponseCode();
if (responseCode != 200) {
postError("HTTP错误: " + responseCode);
return;
}
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
reader.close();
conn.disconnect();
parseAndNotify(sb.toString(), callback);
} catch (Exception e) {
Log.e(TAG, "检查更新失败", e);
postError("网络异常: " + e.getMessage());
}
}
});
}
private void parseAndNotify(String json, CheckCallback callback) throws Exception {
JSONObject root = new JSONObject(json);
int code = root.getInt("code");
if (code != 0) {
postError("服务器返回错误: code=" + code);
return;
}
JSONObject data = root.getJSONObject("data");
final boolean hasUpdate = data.getBoolean("hasUpdate");
final String latestVersion = data.optString("latestVersion", "");
final int latestVersionCode = data.optInt("latestVersionCode", 0);
final String downloadUrl = data.optString("downloadUrl", "");
final String releaseNote = data.optString("releaseNote", "");
mainHandler.post(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onResult(hasUpdate, latestVersion, latestVersionCode,
downloadUrl, releaseNote);
}
}
});
}
private void postError(final String message) {
mainHandler.post(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onError(message);
}
}
});
}
private int getCurrentVersionCode() {
try {
return context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0).versionCode;
} catch (Exception e) {
return 0;
}
}
private String getDeviceId() {
SharedPreferences sp = context.getSharedPreferences("device_info", Context.MODE_PRIVATE);
String id = sp.getString("device_id", "");
if (id.isEmpty()) {
id = "device-" + System.currentTimeMillis();
sp.edit().putString("device_id", id).apply();
}
return id;
}
}
调用方式
在MainActivity中启动周期性检查:
public class MainActivity extends AppCompatActivity {
private UpdateChecker updateChecker;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
updateChecker = new UpdateChecker(this);
updateChecker.startPeriodicCheck(new UpdateChecker.CheckCallback() {
@Override
public void onResult(boolean hasUpdate, String latestVersion,
int latestVersionCode, String downloadUrl,
String releaseNote) {
if (hasUpdate) {
// 弹窗提示用户有新版本
new AlertDialog.Builder(MainActivity.this)
.setTitle("发现新版本 v" + latestVersion)
.setMessage(releaseNote)
.setPositiveButton("立即更新", null)
.show();
}
}
@Override
public void onError(String message) {
Log.e("MainActivity", "检查更新失败: " + message);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if (updateChecker != null) {
updateChecker.stopPeriodicCheck();
}
}
}
验证方法
- 将
CHECK_URL替换为实际可访问的测试服务器地址 - 在服务器端返回
{"code":0,"data":{"hasUpdate":true,...}},运行App后观察Logcat中是否打印检查日志,以及是否弹出更新对话框 - 修改服务器返回
"hasUpdate":false,确认不弹窗且无异常 - 断开网络后运行,确认
onError回调被触发,App不崩溃
通过条件:App启动后能自动发起HTTP请求,正确解析JSON并触发对应回调;周期性任务按设定间隔重复执行;网络异常时能捕获错误并回调。