您的位置:首页 > 移动开发 > Android开发

Android应用如何判断系统升级了?

2017-05-10 16:56 609 查看
android是一个不断迭代升级的系统,基于android的各家手机厂商也都在升级。对于应用程序,有些场景可能需要知道系统是否刚刚升级过了。如果升级了,需要执行一些业务逻辑,例如,系统升级之后,已安装应用信息可能会有变化,相关的数据更新业务逻辑需要被执行。应用程序如何判断近期系统有没有升级过?android并没有能够实现类似功能的官方api。这里使用应用维护系统版本号并比对的方式来实现这个需求。

先看一下android系统升级的时候,对于应用数据是怎么处理的?这里以系统应用为例说明。

从android处理升级的源代码bootable/recovery/recovery.cpp可以看到有擦除数据的逻辑(should_wipe_data相关部分),所以升级之后,应用数据是有可能失去的。

int main(int argc, char **argv) {
// Take last pmsg contents and rewrite it to the current pmsg session.
static const char filter[] = "recovery/";
// Do we need to rotate?
bool doRotate = false;
__android_log_pmsg_file_read(
LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,
logbasename, &doRotate);
// Take action to refresh pmsg contents
__android_log_pmsg_file_read(
LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,
logrotate, &doRotate);

// If this binary is started with the single argument "--adbd",
// instead of being the normal recovery binary, it turns into kind
// of a stripped-down version of adbd that only supports the
// 'sideload' command.  Note this must be a real argument, not
// anything in the command file or bootloader control block; the
// only way recovery should be run with this argument is when it
// starts a copy of itself from the apply_from_adb() function.
if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
adb_server_main(0, DEFAULT_ADB_PORT, -1);
return 0;
}

time_t start = time(NULL);

// redirect_stdio should be called only in non-sideload mode. Otherwise
// we may have two logger instances with different timestamps.
redirect_stdio(TEMPORARY_LOG_FILE);

printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));

load_volume_table();
has_cache = volume_for_path(CACHE_ROOT) != nullptr;

get_args(&argc, &argv);

const char *send_intent = NULL;
const char *update_package = NULL;
bool should_wipe_data = false;
bool should_wipe_cache = false;
bool should_wipe_ab = false;
size_t wipe_package_size = 0;
bool show_text = false;
bool sideload = false;
bool sideload_auto_reboot = false;
bool just_exit = false;
bool shutdown_after = false;
int retry_count = 0;
bool security_update = false;

int arg;
int option_index;
while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
switch (arg) {
case 'i': send_intent = optarg; break;
case 'n': android::base::ParseInt(optarg, &retry_count, 0); break;
case 'u': update_package = optarg; break;
case 'w': should_wipe_data = true; break;
case 'c': should_wipe_cache = true; break;
case 't': show_text = true; break;
case 's': sideload = true; break;
case 'a': sideload = true; sideload_auto_reboot = true; break;
case 'x': just_exit = true; break;
case 'l': locale = optarg; break;
case 'g': {
if (stage == NULL || *stage == '\0') {
char buffer[20] = "1/";
strncat(buffer, optarg, sizeof(buffer)-3);
stage = strdup(buffer);
}
break;
}
case 'p': shutdown_after = true; break;
case 'r': reason = optarg; break;
case 'e': security_update = true; break;
case 0: {
if (strcmp(OPTIONS[option_index].name, "wipe_ab") == 0) {
should_wipe_ab = true;
break;
} else if (strcmp(OPTIONS[option_index].name, "wipe_package_size") == 0) {
android::base::ParseUint(optarg, &wipe_package_size);
break;
}
break;
}
case '?':
LOGE("Invalid command argument\n");
continue;
}
}

if (locale == nullptr && has_cache) {
load_locale_from_cache();
}
printf("locale is [%s]\n", locale);
printf("stage is [%s]\n", stage);
printf("reason is [%s]\n", reason);

Device* device = make_device();
ui = device->GetUI();
gCurrentUI = ui;

ui->SetLocale(locale);
ui->Init();
// Set background string to "installing security update" for security update,
// otherwise set it to "installing system update".
ui->SetSystemUpdateText(security_update);

int st_cur, st_max;
if (stage != NULL && sscanf(stage, "%d/%d", &st_cur, &st_max) == 2) {
ui->SetStage(st_cur, st_max);
}

ui->SetBackground(RecoveryUI::NONE);
if (show_text) ui->ShowText(true);

struct selinux_opt seopts[] = {
{ SELABEL_OPT_PATH, "/file_contexts" }
};

sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);

if (!sehandle) {
ui->Print("Warning: No file_contexts\n");
}

device->StartRecovery();

printf("Command:");
for (arg = 0; arg < argc; arg++) {
printf(" \"%s\"", argv[arg]);
}
printf("\n");

if (update_package) {
// For backwards compatibility on the cache partition only, if
// we're given an old 'root' path "CACHE:foo", change it to
// "/cache/foo".
if (strncmp(update_package, "CACHE:", 6) == 0) {
int len = strlen(update_package) + 10;
char* modified_path = (char*)malloc(len);
if (modified_path) {
strlcpy(modified_path, "/cache/", len);
strlcat(modified_path, update_package+6, len);
printf("(replacing path \"%s\" with \"%s\")\n",
update_package, modified_path);
update_package = modified_path;
}
else
printf("modified_path allocation failed\n");
}
}
printf("\n");

property_list(print_property, NULL);
printf("\n");

ui->Print("Supported API: %d\n", RECOVERY_API_VERSION);

int status = INSTALL_SUCCESS;

if (update_package != NULL) {
// It's not entirely true that we will modify the flash. But we want
// to log the update attempt since update_package is non-NULL.
modified_flash = true;

if (!is_battery_ok()) {
ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
BATTERY_OK_PERCENTAGE);
// Log the error code to last_install when installation skips due to
// low battery.
log_failure_code(kLowBattery, update_package);
status = INSTALL_SKIPPED;
} else if (bootreason_in_blacklist()) {
// Skip update-on-reboot when bootreason is kernel_panic or similar
ui->Print("bootreason is in the blacklist; skip OTA installation\n");
log_failure_code(kBootreasonInBlacklist, update_package);
status = INSTALL_SKIPPED;
} else {
status = install_package(update_package, &should_wipe_cache,
TEMPORARY_INSTALL_FILE, true, retry_count);
if (status == INSTALL_SUCCESS && should_wipe_cache) {
wipe_cache(false, device);
}
if (status != INSTALL_SUCCESS) {
ui->Print("Installation aborted.\n");
// When I/O error happens, reboot and retry installation EIO_RETRY_COUNT
// times before we abandon this OTA update.
if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) {
copy_logs();
set_retry_bootloader_message(retry_count, argc, argv);
// Print retry count on screen.
ui->Print("Retry attempt %d\n", retry_count);

// Reboot and retry the update
int ret = property_set(ANDROID_RB_PROPERTY, "reboot,recovery");
if (ret < 0) {
ui->Print("Reboot failed\n");
} else {
while (true) {
pause();
}
}
}
// If this is an eng or userdebug build, then automatically
// turn the text display on if the script fails so the error
// message is visible.
if (is_ro_debuggable()) {
ui->ShowText(true);
}
}
}
} else if (should_wipe_data) {
if (!wipe_data(false, device)) {
status = INSTALL_ERROR;
}
} else if (should_wipe_cache) {
if (!wipe_cache(false, device)) {
status = INSTALL_ERROR;
}
} else if (should_wipe_ab) {
if (!wipe_ab_device(wipe_package_size)) {
status = INSTALL_ERROR;
}
} else if (sideload) {
// 'adb reboot sideload' acts the same as user presses key combinations
// to enter the sideload mode. When 'sideload-auto-reboot' is used, text
// display will NOT be turned on by default. And it will reboot after
// sideload finishes even if there are errors. Unless one turns on the
// text display during the installation. This is to enable automated
// testing.
if (!sideload_auto_reboot) {
ui->ShowText(true);
}
status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
if (status == INSTALL_SUCCESS && should_wipe_cache) {
if (!wipe_cache(false, device)) {
status = INSTALL_ERROR;
}
}
ui->Print("\nInstall from ADB complete (status: %d).\n", status);
if (sideload_auto_reboot) {
ui->Print("Rebooting automatically.\n");
}
} else if (!just_exit) {
status = INSTALL_NONE;  // No command specified
ui->SetBackground(RecoveryUI::NO_COMMAND);

// http://b/17489952 // If this is an eng or userdebug build, automatically turn on the
// text display if no command is specified.
if (is_ro_debuggable()) {
ui->ShowText(true);
}
}

if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) {
copy_logs();
ui->SetBackground(RecoveryUI::ERROR);
}

Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
if ((status != INSTALL_SUCCESS && status != INSTALL_SKIPPED && !sideload_auto_reboot) ||
ui->IsTextVisible()) {
Device::BuiltinAction temp = prompt_and_wait(device, status);
if (temp != Device::NO_ACTION) {
after = temp;
}
}

// Save logs and clean up before rebooting or shutting down.
finish_recovery(send_intent);

switch (after) {
case Device::SHUTDOWN:
ui->Print("Shutting down...\n");
property_set(ANDROID_RB_PROPERTY, "shutdown,");
break;

case Device::REBOOT_BOOTLOADER:
ui->Print("Rebooting to bootloader...\n");
property_set(ANDROID_RB_PROPERTY, "reboot,bootloader");
break;

default:
ui->Print("Rebooting...\n");
property_set(ANDROID_RB_PROPERTY, "reboot,");
break;
}
while (true) {
pause();
}
// Should be unreachable.
return EXIT_SUCCESS;
}


系统应用可以这样来判断:

假设我们在SharedPreferences中存一个String value:last_update_sys_version,用以记录上一次执行相关业务逻辑时,系统当时的版本名。这个版本名可以是Android的版本,也可以是自家ROM的版本。

每次应用启动的时候,从SharedPreferences中读取last_update_sys_version,与调用Android或者自家ROM提供的获取版本名的api得到的值字符串比对,如果相同,不做任何事;如果不同,执行一次相关业务逻辑,并把当前调用api获取的版本名update到SharedPreferences中。

这个方案不论系统升级有没有擦除数据(SharedPreferences),都是有效的:

如果擦除了,SharedPreferences无此字段,得到null,与当前调用Android或者自家ROM的api得到的版本名字符串比对,结果不相同,会触发执行相关业务逻辑,并且记录这个版本名字段。

如果没有擦除,SharedPreferences中记录的是系统升级之前的版本名,与当前调用Android或者自家ROM的api得到的版本名字符串比对,结果不相同,会触发执行相关业务逻辑,并且记录这个版本名字段。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: