Understanding Android System Property Service: Architecture, Data Structures, and APIs
This article explains the Android Property system, detailing its shared‑memory architecture, the trie‑based data structures (prop_bt, prop_area, prop_info), the initialization flow in the init process, the core C APIs for adding, reading, updating and iterating properties, and the Java SystemProperties wrapper used by applications.
Android properties are key‑value pairs stored in a globally shared memory region; they influence the runtime behavior of many processes and are managed by the Property Service running in the init process.
The shared memory is organized as a dictionary trie where the root node is a special prop_area structure. Branches are represented by prop_bt nodes and leaves by prop_info nodes.
struct prop_bt { uint8_t namelen; uint8_t reserved[3]; atomic_uint_least32_t prop; atomic_uint_least32_t left; atomic_uint_least32_t right; atomic_uint_least32_t children; char name[0]; prop_bt(const char *name, const uint8_t name_length) { this->namelen = name_length; memcpy(this->name, name, name_length); this->name[name_length] = '\0'; } DISALLOW_COPY_AND_ASSIGN(prop_bt); };
class prop_area { public: prop_area(const uint32_t magic, const uint32_t version) : magic_(magic), version_(version) { atomic_init(&serial_, 0); memset(reserved_, 0, sizeof(reserved_)); bytes_used_ = sizeof(prop_bt); } const prop_info *find(const char *name); bool add(const char *name, unsigned int namelen, const char *value, unsigned int valuelen); bool foreach(void (*propfn)(const prop_info *pi, void *cookie), void *cookie); atomic_uint_least32_t *serial() { return &serial_; } private: uint32_t bytes_used_; atomic_uint_least32_t serial_; uint32_t magic_; uint32_t version_; uint32_t reserved_[28]; char data_[0]; DISALLOW_COPY_AND_ASSIGN(prop_area); };
struct prop_info { atomic_uint_least32_t serial; char value[PROP_VALUE_MAX]; char name[0]; prop_info(const char *name, const uint8_t namelen, const char *value, const uint8_t valuelen) { memcpy(this->name, name, namelen); this->name[namelen] = '\0'; atomic_init(&this->serial, valuelen << 24); memcpy(this->value, value, valuelen); this->value[valuelen] = '\0'; } DISALLOW_COPY_AND_ASSIGN(prop_info); };
The init process creates a 128 KB binary file for each property area, maps it into memory, and populates the trie. The key functions are:
int __system_property_area_init() { ... }
static prop_area* map_prop_area_rw(const char* filename, const char* context, bool* fsetxattr_failed) { ... }
Adding a property involves locating (or creating) the appropriate prop_bt nodes and then inserting a prop_info leaf:
int __system_property_add(const char *name, unsigned int namelen, const char *value, unsigned int valuelen) { if (namelen >= PROP_NAME_MAX || valuelen >= PROP_VALUE_MAX) return -1; prop_area* pa = get_prop_area_for_name(name); if (!pa) return -1; bool ret = pa->add(name, namelen, value, valuelen); if (!ret) return -1; atomic_store_explicit(__system_property_area__->serial(), atomic_load_explicit(__system_property_area__->serial(), memory_order_relaxed) + 1, memory_order_release); __futex_wake(__system_property_area__->serial(), INT32_MAX); return 0; }
Reading a property first finds the prop_info node and then copies its value with proper memory fences:
int __system_property_get(const char *name, char *value) { const prop_info *pi = __system_property_find(name); if (pi) return __system_property_read(pi, 0, value); value[0] = '\0'; return 0; }
Updating a property is a simple memcpy guarded by atomic serial updates:
int __system_property_update(prop_info *pi, const char *value, unsigned int len) { ... }
All modifications are performed by the init process; applications invoke __system_property_set , which packages the request into a prop_msg and sends it over a UNIX domain socket to the Property Service:
int __system_property_set(const char *key, const char *value) { prop_msg msg = {}; msg.cmd = PROP_MSG_SETPROP; strlcpy(msg.name, key, sizeof(msg.name)); strlcpy(msg.value, value, sizeof(msg.value)); return send_prop_msg(&msg); }
static int send_prop_msg(const prop_msg *msg) { ... }
The Property Service runs in a loop inside init, handling these messages, updating the shared memory, and notifying listeners.
During app startup, libc’s __libc_init_common calls __system_properties_init() , which maps the read‑only property area and makes the APIs available before main() runs.
Java code accesses the same service via the android.os.SystemProperties class, which declares native methods that forward to the C functions above:
public class SystemProperties { private static native String native_get(String key); private static native void native_set(String key, String val); public static String get(String key) { return native_get(key); } public static void set(String key, String val) { native_set(key, val); } // additional overloads for int, long, boolean, and change callbacks }
Thus the article covers the full lifecycle of Android system properties: from low‑level memory layout and trie operations, through init‑time creation and runtime APIs, to the high‑level Java wrapper used by applications.
Qunar Tech Salon
Qunar Tech Salon is a learning and exchange platform for Qunar engineers and industry peers. We share cutting-edge technology trends and topics, providing a free platform for mid-to-senior technical professionals to exchange and learn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.