[025]能否监听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=1
    start 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
     */
    @UnsupportedAppUsage
    public 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.cpp

jmethodID 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.cpp

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


frameworks/base/core/jni/android_os_SystemProperties.cpp

void 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
@Override
    public 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);
    ...省略大量代码
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,208评论 6 524
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,502评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,496评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,176评论 1 302
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,185评论 6 401
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,630评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,992评论 3 431
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,973评论 0 280
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,510评论 1 325
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,546评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,659评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,250评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,990评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,421评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,569评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,238评论 3 382
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,732评论 2 366