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();
        }
    }
}

验证方法

  1. 将CHECK_URL替换为实际可访问的测试服务器地址
  2. 在服务器端返回{"code":0,"data":{"hasUpdate":true,...}},运行App后观察Logcat中是否打印检查日志,以及是否弹出更新对话框
  3. 修改服务器返回"hasUpdate":false,确认不弹窗且无异常
  4. 断开网络后运行,确认onError回调被触发,App不崩溃

通过条件:App启动后能自动发起HTTP请求,正确解析JSON并触发对应回调;周期性任务按设定间隔重复执行;网络异常时能捕获错误并回调。