有需求:
A 应用联网获取一时间戳,而B应用负责展示。为什么这么神奇,不直接在B应用中向服务端获取?别问,问就是业务导致。
既然存在这么个需求,解决数据存储以及数据获取问题就可以了。如题所说,数据存储方案暂且介绍两种:Settings.Global.putXxx 和 ContentProvider
方式一:Settings.Global.putXxx
存储:
Settings.Global.putLong(getContentResolver(),activationTimeKey,Long.parseLong(edit_text.getText().toString()));
读取:
long activationTimeLong = Settings.Global.getLong(getContentResolver(), activationTimeKey);
实时读取:
//add listener to receive info
getContentResolver().registerContentObserver(
Settings.Global.getUriFor(activationTimeKey),
false, new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
// data has changed
//time_to_show.setText("设备激活时间:" + Settings.Global.getString(getContentResolver(),activationTimeKey));
initDataByTimestampSavedInSettings();
getActivationTime();
}
});
注意事项:
但凡是想要写入数据,则有写入需求的app第一必须是系统应用(即是使用的系统签名),第二需要在功能清单列表中声明权限:
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
adb快速调试相关:
①可以使用下面命令,快速查看数据是否保存成功:
adb shell "settings list global | grep activation_time"
②adb shell 命令快速调试:
adb shell settings get global activation_time
adb shell settings put global activation_time 2024-09-12
adb shell settings put global activation_time 1726713724423
③通过上面api写入的数据,其实都写在了下面文件中:
/data/system/users/0/settings_global.xml
方式二:ContentProvider
这种方式使用也比较普遍
存储:
获取:
public static final long ACTIVATION_TIME_INVALID = -1;
public static final String ACTIVATION_TIME_PATH = "ActivationTime";
public static final String KEY_FOR_ACTIVATION_TIME = "ActivationTime";
public static final String ACTIVATION_TIME_PROVIDER_AUTH = "com.yuanfudao.android.megrez.ActivationTimeProvider";
/**
* 获取本地存储的时间戳并尝试将其转换为格式如 xx年xx月xx日xx时xx分(北京时间) 的数据
* @return xx年xx月xx日xx时xx分(北京时间)
*
* 举例:
* 转换前 : 1849342080000
* 转换后 : 2028年8月8日18时8分(北京时间)
*
* 设备激活时间源数据是服务端生成的一时间戳,app侧获取时间戳后,存储在定好的指定位置,当用户打开设置界面时,settings
* 需将该时间戳转译为指定格式的数据,展示给用户。
*
* 参考信息:
* 企微群:feature-设备实时激活+展示激活时间
* 设备激活时间实时记录&展示:
* https://confluence.zhenguanyu.com/pages/viewpage.action?pageId=720588110
* 设备激活 Swagger
* https://device--megrez.test-venv.yuanfudao.biz/megrez-user-data/api/swagger/doc.html#/defaul
* t/%E8%AE%BE%E5%A4%87%E6%BF%80%E6%B4%BB/activateUsingPOST
*
* 需求负责:@李业来
* 排期:9月20号提测
* 在线工具:https://tool.lu/timestamp/
* https://blog.youkuaiyun.com/learnframework/article/details/121870431
*
* 调试命令:
* adb shell content insert --uri content://com.yuanfudao.android.megrez.ActivationTimeProvider/ActivationTime --bind name:s:ActivationTime --bind value:s:1849342080000
*
*/
public String getActivationTime(Context context){
try {
// ①首先获取app侧保存在 ContentProvider 中的 long 类型数据
long activationTimeLong = getActivationTimeFromContentProvider(context);
// ②判断数据的合法性
if (activationTimeLong <= 0){
//代码执行到这里,意味着本地没有激活时间,或者app侧存储的数据存在问题
Log.w("activation_time","ContentProvider ActivationTime:" + activationTimeLong );
return null;
}
// ③将毫秒级时间戳转换为Instant对象
Instant instant = Instant.ofEpochMilli(activationTimeLong);
// ④东八区时区ID,对于中国大陆,通常是"Asia/Shanghai"
ZoneId cstZoneId = ZoneId.of("Asia/Shanghai");
// ⑤转换为东八区的ZonedDateTime
ZonedDateTime cstTime = instant.atZone(cstZoneId);
// ⑥格式化输出日期和时间
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年M月d日H时m分(北京时间)");
String formattedDateTime = cstTime.format(formatter);
Log.d("activation_time","本地数据:" + activationTimeLong + ",转换后:" + formattedDateTime);
return formattedDateTime;
} catch (NumberFormatException e) {
e.printStackTrace();
return null;
}
}
/**
* 获取app侧保存在 ContentProvider 中的激活时间数据
* @param context Context
* @return Activation Time
*/
public static Long getActivationTimeFromContentProvider(Context context) {
try {
Uri uri = Uri.parse("content://" + ACTIVATION_TIME_PROVIDER_AUTH + "/" + ACTIVATION_TIME_PATH);
Cursor cursor = context.getApplicationContext().getContentResolver()
.query(uri,
null,
null,
null,
null);
if (cursor != null) {
try {
int index = cursor.getColumnIndex(KEY_FOR_ACTIVATION_TIME);
if (cursor.moveToFirst()) {
try {
return Long.parseLong(cursor.getString(index));
} catch (NumberFormatException e) {
return ACTIVATION_TIME_INVALID;
}
}
} finally {
cursor.close();
}
}
} catch (Throwable t) {
Log.e("ActivationProviderHelper", "找不到Provider, Auth:" + ACTIVATION_TIME_PROVIDER_AUTH, t);
}
return ACTIVATION_TIME_INVALID;
}
注意事项:
①因为我这里是B应用,所以关于数据如何存入的逻辑,我这里没有列举出来;
②另外,因为A应用的开发者讲话说,考虑到数据的安全性来说,他为这个数据做了权限管理,其他想要访问该字段数据的应用,要么和A应用是同一个应用签名,要么是使用了系统签名,否则其他签名的应用,是不能成功获取数据的。
③最后采取了第二个方案,至于为啥,也是因为app端不想使用系统签名对他们的A应用进行签名,再问就是业务导致如此。所以现在是方案二的情况下,我在B应用中使用系统签名,也是能成功获取数据。
④这里其实还有一个调试命令,不过不确定是不是app设置了权限还是怎么着,只能查询获取,无法直接通过adb主动设置该指定数据:
adb shell content query --uri content://com.yuanfudao.android.megrez.ActivationTimeProvider/ActivationTime --projection name:value --where "name='ActivationTime'"
参考:
Android Settings系统属性读写_android settings.global-优快云博客
时间戳(Unix timestamp)转换工具 - 在线工具 (tool.lu)
adb shell调试contentprovider相关命令-千里马android framework_adb 模拟往contentprivider写数据-优快云博客