预备知识-什么是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);...省略大量代码
}
扫码或长按关注
回复「 篮球的大肚子」进入技术群聊