安卓系统监听system property值?

预备知识-什么是system property

system property是系统属性,以key-value格式保存。
可以通过以下方式读取和修改system property的值:
1.adb

adb shell getprop <key> 
adb shell setprop <key> <value>

2.C/C++

int property_get(const char *key, char *value, const char *default_value)
int property_set(const char *key, const char *value)

3.Java

SystemProperties.get(key)
SystemProperties.set(key, value);

前言

有一个朋友问我能否在App中监听system property值的变化,我想到rc文件中有大量类似下面的写法,通过监听system property的值启动服务。

on property:persist.debug.atrace.boottrace=1start boottrace

我猜肯定可以App中监听,果不其然在SystemProperties类中找到了addChangeCallback方法,看注释就就感觉自己找对了,但是这个类和方法都是@hide的,无法直接通过SDK调用addChangeCallback方法。这能难得到我嘛,我有三种方法调用hide的接口,我选用反射。

/frameworks/base/core/java/android/os/SystemProperties.java/*** Gives access to the system properties store.  The system properties* store contains a list of string key-value pairs.* {@hide}*/
@SystemApi
@TestApi
public class SystemProperties {@UnsupportedAppUsage@GuardedBy("sChangeCallbacks")private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>();/*** Add a callback that will be run whenever any system property changes.* @param callback The {@link Runnable} that should be executed when a system property* changes.* @hide*/@UnsupportedAppUsagepublic static void addChangeCallback(@NonNull Runnable callback) {synchronized (sChangeCallbacks) {if (sChangeCallbacks.size() == 0) {native_add_change_callback();}sChangeCallbacks.add(callback);}}
}

很快我写完了demo,结果发现修改system property的值,根本不会触发注册的callback。
我检查了很多次我写的代码,都没发现问题,这是怎么回事呢?

这是一个典型的观察者模式,那我们跟踪一下代码,找一下触发callback的地方。

代码分析

开始跟踪代码,callback是被callChangeCallbacks回调的,从注释就可以看出callChangeCallbacks这个方法是从native层被回调。

@SuppressWarnings("unused")  // Called from native code.private static void callChangeCallbacks() {synchronized (sChangeCallbacks) {//Log.i("foo", "Calling " + sChangeCallbacks.size() + " change callbacks!");if (sChangeCallbacks.size() == 0) {return;}ArrayList<Runnable> callbacks = new ArrayList<Runnable>(sChangeCallbacks);final long token = Binder.clearCallingIdentity();try {for (int i = 0; i < callbacks.size(); i++) {try {callbacks.get(i).run();//循环调用callback} catch (Throwable t) {Log.wtf(TAG, "Exception in SystemProperties change callback", t);// Ignore and try to go on.}}} finally {Binder.restoreCallingIdentity(token);}}}

callChangeCallbacks是在Native层中被do_report_sysprop_change回调的。

frameworks/base/core/jni/android_os_SystemProperties.cppjmethodID sCallChangeCallbacks;void do_report_sysprop_change() {...env->CallStaticVoidMethod(sClazz, sCallChangeCallbacks);...
}void SystemProperties_add_change_callback(JNIEnv *env, jobject clazz)
{...sCallChangeCallbacks = env->GetStaticMethodID(sClazz, "callChangeCallbacks", "()V");...
}

do_report_sysprop_change在report_sysprop_change被回调。

system/core/libutils/misc.cppvoid report_sysprop_change() {do_report_sysprop_change();
}
整个流程:report_sysprop_change ---> do_report_sysprop_change ---> callChangeCallbacks ---> callbacks.get(i).run();

report_sysprop_change的调用点

基本理清楚了整个流程,只要找到report_sysprop_change的地方就可以解开谜题了。但是我发现好多地方调用report_sysprop_change,一处处的分析。

第1处调用点

看到下面的代码我懵逼了,饶了一圈又回到SystemProperties.java的reportSyspropChanged这个方法,这时候我有种不祥的预感,难道这个report_sysprop_change需要我们在修改完system property之后主动调用SystemProperties.reportSyspropChanged()才能回调我们设置的callback嘛,而且就算需要我们主动调用reportSyspropChanged也无法跨进程通知其他应用的callback,那这个功能太鸡肋了。

frameworks/base/core/java/android/os/SystemProperties.java
/**
* Notifies listeners that a system property has changed
* @hide
*/
public static void reportSyspropChanged() {//调用native层的SystemProperties_report_sysprop_changenative_report_sysprop_change();
}frameworks/base/core/jni/android_os_SystemProperties.cppvoid SystemProperties_report_sysprop_change(JNIEnv /**env*/, jobject /*clazz*/)
{report_sysprop_change();
}int register_android_os_SystemProperties(JNIEnv *env)
{const JNINativeMethod method_table[] = {...{ "native_report_sysprop_change", "()V",(void*) SystemProperties_report_sysprop_change },};return RegisterMethodsOrDie(env, "android/os/SystemProperties",method_table, NELEM(method_table));
}

第2处调用点

看到下面这个代码,我还是抱着一点希望,说不准会有一次地方会发起SYSPROPS_TRANSACTION的Binder通信,来触发这个方法,但是一想也有点怪怪的,既然是Binder通信,有server端,就应该有client端。

status_t BBinder::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t /*flags*/)
{switch (code) {...case SYSPROPS_TRANSACTION: {report_sysprop_change();return NO_ERROR;}}
}
我们找到了三处client端:
client1:
ANRdaemon.cpp
/** Force the userland processes to refresh their property for logging.*/
static void dfs_poke_binder(void) {sp<IServiceManager> sm = defaultServiceManager();Vector<String16> services = sm->listServices();for (size_t i = 0; i < services.size(); i++) {sp<IBinder> obj = sm->checkService(services[i]);if (obj != NULL) {Parcel data;obj->transact(IBinder::SYSPROPS_TRANSACTION, data, NULL, 0);}}
}
client2:
atrace.cpp
// Poke all the binder-enabled processes in the system to get them to re-read
// their system properties.
static bool pokeBinderServices()
{sp<IServiceManager> sm = defaultServiceManager();Vector<String16> services = sm->listServices();for (size_t i = 0; i < services.size(); i++) {sp<IBinder> obj = sm->checkService(services[i]);if (obj != NULL) {Parcel data;if (obj->transact(IBinder::SYSPROPS_TRANSACTION, data,NULL, 0) != OK) {if (false) {// XXX: For some reason this fails on tablets trying to// poke the "phone" service.  It's not clear whether some// are expected to fail.String8 svc(services[i]);fprintf(stderr, "error poking binder service %s\n",svc.string());return false;}}}}return true;
}

可以发现client1和client2的代码差不多:循环遍历注册在SM的实名Binder,然后发起SYSPROPS_TRANSACTION的Binder通信,这样子所有注册在SM中注册实名Binder的进程可以触发report_sysprop_change这个方法。看到这里我基本可以确定需要修改system property的地方主动执行client1和client2类似的代码,才能通知到其他进程system property发生了变化,然后触发callback。

但是还有一个疑问,我们自己写的App中并没有注册实名Binder到SM,那App如何接收system property的变化?

答案就在client3:通过实名Binder对象ActivityManagerService通知转发SYSPROPS_TRANSACTION到每个App中的匿名Binder对象ApplicationThread。

client3
ActivityManagerService.java
@Overridepublic boolean onTransact(int code, Parcel data, Parcel reply, int flags)throws RemoteException {if (code == SYSPROPS_TRANSACTION) {// We need to tell all apps about the system property change.ArrayList<IBinder> procs = new ArrayList<IBinder>();synchronized(this) {final int NP = mProcessNames.getMap().size();for (int ip=0; ip<NP; ip++) {SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);final int NA = apps.size();for (int ia=0; ia<NA; ia++) {ProcessRecord app = apps.valueAt(ia);if (app.thread != null) {//遍历所有的ProcessRecord,获得每个ProcessRecord中保存thread,//thread是每个应用中ApplicationThread这个Binder对象的Client端。procs.add(app.thread.asBinder());}}}}int N = procs.size();for (int i=0; i<N; i++) {Parcel data2 = Parcel.obtain();try {//发送SYSPROPS_TRANSACTION到每个应用。procs.get(i).transact(IBinder.SYSPROPS_TRANSACTION, data2, null,Binder.FLAG_ONEWAY);} catch (RemoteException e) {}data2.recycle();}}}

总结

分析到这里我们基本可以回答标题的提问,我们无法通过addChangeCallback监听system property值的变化,除非在修改system property的地方,主动调用SystemProperties.reportSyspropChanged或者类似前面的client1和client2处的代码,前者只能通知当前进程,后者可以通知所有注册实名Binder到SM的进程和保存在AMS的ProcessRecord对应的App

尾巴

我还是想不明白rc文件中是如何监听system property,我继续找代码,直到我找到了下面的代码,原来是通过hook了PropertySet方法来监听system property值的变化。

static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {...省略大量代码property_changed(name, value);return PROP_SUCCESS;
}void property_changed(const std::string& name, const std::string& value) {...省略大量代码if (property_triggers_enabled) ActionManager::GetInstance().QueuePropertyChange(name, value);...省略大量代码
}

扫码或长按关注

回复「 篮球的大肚子」进入技术群聊

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/467960.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么全天坐在电脑前会让你精疲力竭

Tips 原文作者&#xff1a;Katie Heaney 原文地址&#xff1a;Why Sitting at Your Computer All Day Can Wipe You Out 像大多数日子一样&#xff0c;我昨天大部分时间坐在我认为是公寓最美丽的一角&#xff0c;一直在笔记本电脑打字。 我提交了一个故事&#xff0c;转述了几个…

这届全明星,把NBA又燃回来了

第一个罚球&#xff0c;戴维斯出手后&#xff0c;听到哐当医生&#xff0c;皮球掉了出来。我又紧张了。微信群了很多人开始发消息&#xff0c;说詹姆斯队又要输了。回到比赛。戴维斯当时没有任何微笑&#xff0c;我估计他内心也是紧张的&#xff0c;他有点埋怨哈登&#xff0c;…

redis分布式锁java代码_基于redis实现分布式锁

“ 在上一篇文章中介绍了动态配置定时任务&#xff0c;其中的原理跟spring 定时任务注解Scheduled一样的&#xff0c;都是通过线程池和定义执行时间来控制。来思考一个问题&#xff0c;如果我们的定时任务在分布式微服务里面呢&#xff1f;在分布式微服务里面一个微服务肯定可以…

C语言写个贪吃蛇游戏

贪吃蛇是个非常经典的游戏&#xff0c;用C语言来实现也是一个好玩的事情。这个游戏我写完后放在知乎&#xff0c;竟然点赞的人数超级多。我觉得大家喜欢&#xff0c;一个方面是因为写得简单&#xff0c;大家都能看得懂&#xff0c;一个可扩展性还是非常强的。我试了说一下这个代…

seir模型matlab_疫情专题 | 传染病的经典数学模型

在此次新冠肺炎疫情防控过程中&#xff0c;对疫情发展趋势的科学预测显得尤为重要。而这背后&#xff0c;离不开对传染病传播规律的建模。今天&#xff0c;小编就带各位数学学子们来了解一下传染病的四大经典数学模型&#xff1a;SI/SIS/SIR/SEIR。其中用到了许多微分方程的知识…

ubuntu电脑安装硬盘

最近在做安卓开发&#xff0c;一套RK3399的安卓代码&#xff0c;解压编译后占用170多G的硬盘。所以呢&#xff0c;原来1T大小的硬盘&#xff0c;很快就沾满了&#xff0c;然后我赶紧给北京总部申请买了一个新的硬盘。现在的台式电脑都是用的SATA硬盘接口&#xff0c;我赶紧就上…

字典表

字典表 &#xff1a; dict 声明 键;值dict(键值) 操作 获取d.get(键‘默认值) 合并d.update(d2) 键值emp.items 菜单emp.keys 效果emp.values得到视图 遍历打印for x in emp.keys 支持嵌套 由于哈希算法导致顺序混乱可以将方法转换为列表&#xff0c;在排序 方法二 全局函…

十分钟让你明白AIDL

前言我在[003]AIDL是什么中介绍的AIDL&#xff0c;但是好像还有朋友不明白问我&#xff0c;那我就来写一个终极版的文章&#xff0c;让你十分钟彻底明白AIDL&#xff0c;以下代码全为手写。目标Server进程注册一个Binder服务到SM&#xff0c;该Binder服务提供两个接口&#xff…

表达式

表达式与分支 语句 分割文档main spilit 风格pep8 赋值 序列赋值 列表切割法 扩展序列解包赋值 *获取剩余 多目标赋值 数字256为界以内指向同对象 以外就不同 字符串3个 列表属于引用类型 不要共同引用两种方法 参数化赋值 列表也可以进行参数化赋值 表达式 函数 方法…

Linux下故障分析方法

1、背景有时候会遇到一些疑难杂症&#xff0c;并且监控插件并不能一眼立马发现问题的根源。这时候就需要登录服务器进一步深入分析问题的根源。那么分析问题需要有一定的技术经验积累&#xff0c;并且有些问题涉及到的领域非常广&#xff0c;才能定位到问题。所以&#xff0c;分…

作业题

import random fopen(‘data.txt’,‘w’) for i in range(10000): f.write(str(random.randint(1,100))) f.write(’\n’) f.seek(0) print(f.read()) f.close f.seek()函数 移动文件中n个操作 正为向结束方向 enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符…

ubuntu 安装gitlab

gitlab 类似github&#xff0c;可以用来管理代码。当然除了他们两个还有很多代码管理的工具&#xff0c;国内的也有。我这篇文章就只讲gitlab的安装过程。但是gitlab并不是轻量级的东西&#xff0c;占用大概4~8G的内存&#xff0c;特别是merge的代码比较多的时候&#xff0c;占…

迭代

迭代 文章目录迭代内置可迭代对象 range&#xff08;&#xff09;内置函数使用 map&#xff08;函数加对象&#xff09;函数定义与参数定义传参作用域函数参数传值可迭代对象支持迭代协议 遍历循环 因为 迭代协议 方法—next—&#xff08;&#xff09;函数next消耗内存小所占空…

普通人的节奏

在立春当夜&#xff0c;这南方城市的上空响起了2020年的第一声春雷&#xff0c;大家都期待着这一声霹雳除去一切霉噩&#xff0c;带来万象更新。之后的天气逐渐变好&#xff0c;近日来更是风和日丽。前日&#xff0c;去采购食品和日用品&#xff0c;久不出门&#xff0c;趁好天…

函数与lambda

文章目录参数匹配将形参赋值&#xff08;关键字匹配&#xff09;默认位置匹配解包在元组前加*函数传递字典表&#xff0c;使用**解包字典表两种声明法lambda表达式初学者逻辑采用字典表可以实现委托map函数把列表对象每一个拿出来放入函数中进行迭代后放入新的列三种方式filter…

Linux内核奔溃分析

前言最近遇到一个kernel奔溃的问题&#xff0c;错误日志开头一部分如下&#xff1a;[ 355.2624510] Unhandled fault: external abort on non-linefetch (0x008) at 0xfe004328 [ 355.2645470] Internal error: : 8 [#1] PREEMPT SMP ARM [ 355.2696320] Modules linked in:…

DataGridView带图标的单元格实现

目的&#xff1a; 扩展 C# WinForm 自带的表格控件&#xff0c;使其可以自动判断数据的上下界限值&#xff0c;并标识溢出。 这里使用的方法是&#xff1a;扩展 表格的列 对象&#xff1a;DataGridViewColumn。 1.创建类&#xff1a;DataGridViewDecimalCheckCell.cs public cl…

包与模块管理及面向对象初步

模块 文章目录模块指令原因步骤搜索范围加目录面向对象编程封装实现初始化函数![在这里插入图片描述](https://img-blog.csdnimg.cn/20200201081513814.png?x-oss-processimage/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80M…

spring生命周期七个过程_Spring杂文(三)Spring循环引用

众所周知spring在默认单例的情况下是支持循环引用的Appconfig.java类的代码Configurable ComponentScan("com.sadow") public class Appconfig { }X.java类的代码Component public class X {AutowiredY y;public X(){System.out.println("X create");} }Y.…

Linux 系统中的dvfs功能

前言最近硬件的同事需要我们提供的版本能动态调频&#xff0c;何为动态调频呢&#xff1f;对于CPU来讲&#xff0c;功耗和性能是一对不可调和的矛盾&#xff0c;通过调整CPU的电压和频率&#xff0c;可以在功耗和性能之间找一个平衡点。由于调整是在系统运行的过程中&#xff0…