[Android源码解析]Property之十月怀胎到茁壮成长所涉及的方方面面

本文主要是介绍[Android源码解析]Property之十月怀胎到茁壮成长所涉及的方方面面,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

         其实在网上讲Property的文章还是蛮多的,不过源码级分析的倒是不多,晓东正好做好了一个项目,其中涉及到了Property的一些内容,折腾了一段时间,心想不如来读读源码,看看究竟是怎么回事。

1property内存区域的申请

         在网上通常都是这样开始讲的“属性服务运行于init进程中。init进程首先创建一个共享内存区域,并保存一个指向该区域的描述符fd[1]这一段从代码中如何来看呢,首先找到initmain函数:(system/core/init/init.c文件中),有这样一句话:

         queue_builtin_action(property_init_action, "property_init");

         大概的意思就是把property_init_action加入到action queue中,后面会来调用这个action。所以,我们自然而然的就会去看property_init_action的操作:

static int property_init_action(int nargs, char **args)
{
bool load_defaults = true;
INFO("property init\n");
//只要不是关机充电的模式,我们都把load_defaults置为true
if (!strcmp(bootmode, "charger"))
load_defaults = false;
//property的初始化
property_init(load_defaults);
return 0;
}
void property_init(bool load_defaults)
{
//初始化property的内存区域,就是上面传说的共享内存区域?
init_property_area();
//若是需要load,这里会load  PROP_PATH_RAMDISK_DEFAULT
// #define PROP_PATH_RAMDISK_DEFAULT  "/default.prop"
//所以网上盛传的先加载哪个文件,再加载哪个文件都是有前提的,就是首先不是在关机充电模式。哈哈~~,不过的确是得先加载这个文件default.prop
if (load_defaults)
//这里加载对应文件中的property内容,详细分析见1.2
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}
static int init_property_area(void)
{
prop_area *pa;
//这是个全局变量,不过也就只有这个函数会赋值,所以开始不会有问题,再重复进来就会报错了
if(pa_info_array)
return -1;
//初始化内存区域,详细分析见1.1
if(init_workspace(&pa_workspace, PA_SIZE))
return -1;
//设置FD_CLOEXEC,大概的意思就是在excel执行时关闭
fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
//这里如图1所示
pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);
pa = pa_workspace.data;
//把整个shared memory都初始化为0
memset(pa, 0, PA_SIZE);
//设置magic和version
pa->magic = PROP_AREA_MAGIC;
pa->version = PROP_AREA_VERSION;
/* plug into the lib property services */
//这里把__system_property_area__也指过去了
__system_property_area__ = pa;
//设置inited的状态位
property_area_inited = 1;
return 0;
}


1 property内存区域示意图[1]

1.1 共享内存区域的初始化

static int init_workspace(workspace *w, size_t size)
{
void *data;
int fd;
/* dev is a tmpfs that we can use to carve a shared workspace
* out of, so let's do that...
*/
//其实就是打开__properties__的设备节点。这个就是在内核中实现的了,具体的分析晓东有机会再和大家一起分析
fd = open("/dev/__properties__", O_RDWR | O_CREAT, 0600);
if (fd < 0)
return -1;
//改变文件的大小为size
if (ftruncate(fd, size) < 0)
goto out;
//这里实现的就是“init进程将该区域通过使用了MAP_SHARED标志的mmap映射至它自身的虚拟地址空间,这样,任何对于该区域的更新对于所有进程都是可见的[1]”
    data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(data == MAP_FAILED)
goto out;
close(fd);
//重新打开为readonly
fd = open("/dev/__properties__", O_RDONLY);
if (fd < 0)
return -1;
//remove this for BLCR
//unlink("/dev/__properties__");
//这里赋值data,size和fd参数
w->data = data;
w->size = size;
w->fd = fd;
return 0;
out:
close(fd);
return -1;
}

1.2 default.prop为例详解property文件的加载

假设一个default.prop的文件内容如下:

#
# ADDITIONAL_DEFAULT_PROPERTIES
#
ro.secure=1
ro.allow.mock.location=0
ro.debuggable=1
persist.sys.usb.config=mtp,adb
static void load_properties_from_file(const char *fn)
{
char *data;
unsigned sz;
//读出文件中的内容,保存到data所指向的一段内存中,size是sz
data = read_file(fn, &sz);
if(data != 0) {
//加载对应的value key对
load_properties(data);
free(data);
}
}
static void load_properties(char *data)
{
char *key, *value, *eol, *sol, *tmp;
sol = data;
//这个while循环就是一个key和value的解析过程了,大概的意思就是把=号前的保存到key中,把=号后的内容保存到value中
while((eol = strchr(sol, '\n'))) {
key = sol;
*eol++ = 0;
sol = eol;
value = strchr(key, '=');
if(value == 0) continue;
*value++ = 0;
while(isspace(*key)) key++;
if(*key == '#') continue;
tmp = value - 2;
while((tmp > key) && isspace(*tmp)) *tmp-- = 0;
while(isspace(*value)) value++;
tmp = eol - 2;
while((tmp > value) && isspace(*tmp)) *tmp-- = 0;
//这里是关键的设置
property_set(key, value);
}
}
//提醒一下,这里的property_set和我们真正调用的property_set可不是一回事哦,当然最终我们仍然会调用到这个函数。具体见最后的分析
int property_set(const char *name, const char *value)
{
prop_area *pa;
prop_info *pi;
//得到name和value的长度
int namelen = strlen(name);
int valuelen = strlen(value);
//这里property的name和value都是有最大长度的哦,name是32,value是92,所以大家在写自己的name和value的时候,不要超过这个长度哦
    if(namelen >= PROP_NAME_MAX) return -1;
if(valuelen >= PROP_VALUE_MAX) return -1;
if(namelen < 1) return -1;
//去prop_info中找一下是否已经有了同名的
pi = (prop_info*) __system_property_find(name);
if(pi != 0) {
//若是有这个name,假如是以ro开头,则不修改,直接返回
/* ro.* properties may NEVER be modified once set */
if(!strncmp(name, "ro.", 3)) return -1;
//否则就需要update新的value
pa = __system_property_area__;
update_prop_info(pi, value, valuelen);
pa->serial++;
__futex_wake(&pa->serial, INT32_MAX);
} else {
pa = __system_property_area__;
//首先看一下pa的count是不是已经到max了
if(pa->count == PA_COUNT_MAX) return -1;
//这里知道后面的pa_info,然后保存对应的name和value
pi = pa_info_array + pa->count;
pi->serial = (valuelen << 24);
memcpy(pi->name, name, namelen + 1);
memcpy(pi->value, value, valuelen + 1);
pa->toc[pa->count] =
(namelen << 24) | (((unsigned) pi) - ((unsigned) pa));
//count++
pa->count++;
pa->serial++;
__futex_wake(&pa->serial, INT32_MAX);
}
/* If name starts with "net." treat as a DNS property. */
//若是以net.开头的,把它作为一个DNS的property
if (strncmp("net.", name, strlen("net.")) == 0)  {
if (strcmp("net.change", name) == 0) {
return 0;
}
/*
* The 'net.change' property is a special property used track when any
* 'net.*' property name is updated. It is _ONLY_ updated here. Its value
* contains the last updated 'net.*' property.
*/
//同时需要改变net.change的值,net.change本身就在上面直接返回了
property_set("net.change", name);
} else if (persistent_properties_loaded &&
strncmp("persist.", name, strlen("persist.")) == 0) {
/*
* Don't write properties to disk until after we have read all default properties
* to prevent them from being overwritten by default values.
*/
//先写到temp文件中,暂时不要写到disk
write_persistent_property(name, value);
}
property_changed(name, value);
return 0;
}
void property_changed(const char *name, const char *value)
{
//这个参数会在queue_property_triggers_action中调用,目前还是没有设置的,所以就先这样
if (property_triggers_enabled)
queue_property_triggers(name, value);
}


总得来说property_init_action就是申请共享内存区域,然后load default.prop文件。 

2"set_init_properties"设置初始化的property

property_init_action之后和property相关的就是set_init_properties_action

queue_builtin_action(set_init_properties_action, "set_init_properties");

所以我们接着来分析这个函数:

static int set_init_properties_action(int nargs, char **args)
{
char tmp[PROP_VALUE_MAX];
//得到kernel的cmd line的参数
if (qemu[0])
import_kernel_cmdline(1, import_kernel_nv);
//若是工厂模式,设几个ro的property,有人说这里是ro的参数,不能只读不能改吗,哈哈,你若是仔细看了前面的property_set的函数就会发现,其实这里若是第一次,还是可以写的,相当于初始化后就不能改了。
if (!strcmp(bootmode,"factory"))
property_set("ro.factorytest", "1");
else if (!strcmp(bootmode,"factory2"))
property_set("ro.factorytest", "2");
else
property_set("ro.factorytest", "0");
//设置下面这些ro参数的值
property_set("ro.serialno", serialno[0] ? serialno : "");
property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown");
property_set("ro.baseband", baseband[0] ? baseband : "unknown");
property_set("ro.carrier", carrier[0] ? carrier : "unknown");
property_set("ro.bootloader", bootloader[0] ? bootloader : "unknown");
property_set("ro.hardware", hardware);
snprintf(tmp, PROP_VALUE_MAX, "%d", revision);
property_set("ro.revision", tmp);
return 0;
}


这个函数就是根据kernelcmdline参数等设置对应的一些ro参数的值,和我们的关系不是很大,知道就可以了。

3property_service_init_action之初始化property service

这个action就是紧接着上面的一些操作了:

queue_builtin_action(property_service_init_action, "property_service_init");

该函数就是初始化property service

static int property_service_init_action(int nargs, char **args)
{
/* read any property files on system or data and
* fire up the property service.  This must happen
* after the ro.foo properties are set above so
* that /data/local.prop cannot interfere with them.
*/
//启动property的service
start_property_service();
return 0;
}
void start_property_service(void)
{
int fd;
//加载下面三个文件:"/system/build.prop","/system/default.prop",/data/local.prop"。
//这里就是网上流传的4个文件的加载顺序的说法,从这里就可以看到了。
    load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
/* Read persistent properties after all default values have been loaded. */
//这里就是所有的default 值都初始化好了,就加载persistent的property了,还记得前面我们把persisten的property写到一个文件中去的么?这里就再从里面读出来好了
load_persistent_properties();
//创建一个socket,这里就是盛传的“在这一步中,一个unix domain socket服务被创建”[1]
fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
if(fd < 0) return;
fcntl(fd, F_SETFD, FD_CLOEXEC);
fcntl(fd, F_SETFL, O_NONBLOCK);
//这里是listen了
listen(fd, 8);
//赋值给property_set_fd
property_set_fd = fd;
}


这里主要的工作就是启动property service,加载了剩下的三个文件,同时新建了一个socket,并且监听了他的内容。我想下面的内容就可以猜到了,就是对这个socket的数据进行处理了。

4socket有数据后的处理

init.c的最后会有一个循环,用来不停的处理它所监听的socket。代码如下:

for(;;){
……
//若是property fd那边有数据,就处理这边的数据
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
……
}
void handle_property_set_fd()
{
prop_msg msg;
int s;
int r;
int res;
struct ucred cr;
struct sockaddr_un addr;
socklen_t addr_size = sizeof(addr);
socklen_t cr_size = sizeof(cr);
//accept数据
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
//得到socket的options
/* Check socket options here */
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
ERROR("Unable to recieve socket options\n");
return;
}
//recv数据
r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), 0));
if(r != sizeof(prop_msg)) {
ERROR("sys_prop: mis-match msg size recieved: %d expected: %d errno: %d\n",
r, sizeof(prop_msg), errno);
close(s);
return;
switch(msg.cmd) {
//这里就是处理我们调用的property_set了,所以,理解了吧,我们调用property_set的时候,其实就是通过socket发送这个msg过来而已。
    case PROP_MSG_SETPROP:
//得到对应的name和value值
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
//这里处理ctl.start,ctl.stop等等
if(memcmp(msg.name,"ctl.",4) == 0) {
// Keep the old close-socket-early behavior when handling
// ctl.* properties.
close(s);
//检查value对应的uid和gid,若是root或者system就可以直接处理,而不需要检查,这就是官大的好处理问题啊。哈哈~~
if (check_control_perms(msg.value, cr.uid, cr.gid)) {
//根据start,stop还是restart来进行service的对应的处理,我们就不详细关注了哦
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
//这里同样会check permission,不同的是system没有特权了,只有root才有特权
if (check_perms(msg.name, cr.uid, cr.gid)) {
//然后进行设置
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d  name:%s\n",
cr.uid, msg.name);
}
//这里就是和bionic 那边进行通信的,就是写好了,这里关闭。然后bionic那边进行监听。这里有个问题,就是我们若是找不到name,那边也会认为是正确的,被害死了
// Note: bionic's property client code assumes that the
// property server will not close the socket until *AFTER*
// the property is written to memory.
close(s);
}
break;
default:
close(s);
break;
}
}


这里就是通过socket来得到对应msg进行对应的处理。完成property_set的真正操作。然后通过close来通知client那边这里ok了。

5、真正property_set的实现

其实上面我们也是通过代码猜测一下收到的msg处理状况,并没有去看我们调用property_set的流程,这里我们简单看一下,他位于/bionic/libc/bionic/system_properties.c文件中:

我们调用property_set最终会调用到这个函数:

int __system_property_set(const char *key, const char *value)
{
int err;
int tries = 0;
int update_seen = 0;
prop_msg msg;
if(key == 0) return -1;
if(value == 0) value = "";
if(strlen(key) >= PROP_NAME_MAX) return -1;
if(strlen(value) >= PROP_VALUE_MAX) return -1;
memset(&msg, 0, sizeof msg);
//这里就是我们收到的msg吧
msg.cmd = PROP_MSG_SETPROP;
strlcpy(msg.name, key, sizeof msg.name);
strlcpy(msg.value, value, sizeof msg.value);
//发送这个msg
err = send_prop_msg(&msg);
if(err < 0) {
return err;
}
return 0;
}
static int send_prop_msg(prop_msg *msg)
{
struct pollfd pollfds[1];
struct sockaddr_un addr;
socklen_t alen;
size_t namelen;
int s;
int r;
int result = -1;
//新建socket,有戏啊
s = socket(AF_LOCAL, SOCK_STREAM, 0);
if(s < 0) {
return result;
}
memset(&addr, 0, sizeof(addr));
namelen = strlen(property_service_socket);
strlcpy(addr.sun_path, property_service_socket, sizeof addr.sun_path);
addr.sun_family = AF_LOCAL;
alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;
//connect
if(TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *) &addr, alen) < 0)) {
close(s);
return result;
}
//Send,哈哈
r = TEMP_FAILURE_RETRY(send(s, msg, sizeof(prop_msg), 0));
if(r == sizeof(prop_msg)) {
// We successfully wrote to the property server but now we
// wait for the property server to finish its work.  It
// acknowledges its completion by closing the socket so we
// poll here (on nothing), waiting for the socket to close.
// If you 'adb shell setprop foo bar' you'll see the POLLHUP
// once the socket closes.  Out of paranoia we cap our poll
// at 250 ms.
pollfds[0].fd = s;
pollfds[0].events = 0;
r = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */));
if (r == 1 && (pollfds[0].revents & POLLHUP) != 0) {
result = 0;
} else {
//就是这个地方太坏了,超时了,他也说是ok的。所以,哎~~~大家使用的时候自求多福吧,这段代码太。。。。
            // Ignore the timeout and treat it like a success anyway.
// The init process is single-threaded and its property
// service is sometimes slow to respond (perhaps it's off
// starting a child process or something) and thus this
// times out and the caller thinks it failed, even though
// it's still getting around to it.  So we fake it here,
// mostly for ctl.* properties, but we do try and wait 250
// ms so callers who do read-after-write can reliably see
// what they've written.  Most of the time.
// TODO: fix the system properties design.
result = 0;
}
}
close(s);
return result;
}


因此,client端就是真的调用socket,然后send对应的msg,接着等待回应,反正都是ok的。他是不会出错,我就曾今遇到没有写进去,但是返回值仍然是对的情况。害的我查了很久,这段代码写的。。。。不吐槽了。。。。

 

至此,所有property相关的内容都分析好了,您还有什么疑问么?

 

参考文章:

[1]http://blog.csdn.net/jackyu613/article/details/6136620

 

若您觉得该文章对您有帮助,请在下面用鼠标轻轻按一下“顶”,哈哈~~·

这篇关于[Android源码解析]Property之十月怀胎到茁壮成长所涉及的方方面面的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1079312

相关文章

解析 XML 和 INI

XML 1.TinyXML库 TinyXML是一个C++的XML解析库  使用介绍: https://www.cnblogs.com/mythou/archive/2011/11/27/2265169.html    使用的时候,只要把 tinyxml.h、tinystr.h、tinystr.cpp、tinyxml.cpp、tinyxmlerror.cpp、tinyxmlparser.

Eclipse+ADT与Android Studio开发的区别

下文的EA指Eclipse+ADT,AS就是指Android Studio。 就编写界面布局来说AS可以边开发边预览(所见即所得,以及多个屏幕预览),这个优势比较大。AS运行时占的内存比EA的要小。AS创建项目时要创建gradle项目框架,so,创建项目时AS比较慢。android studio基于gradle构建项目,你无法同时集中管理和维护多个项目的源码,而eclipse ADT可以同时打开

android 免费短信验证功能

没有太复杂的使用的话,功能实现比较简单粗暴。 在www.mob.com网站中可以申请使用免费短信验证功能。 步骤: 1.注册登录。 2.选择“短信验证码SDK” 3.下载对应的sdk包,我这是选studio的。 4.从头像那进入后台并创建短信验证应用,获取到key跟secret 5.根据技术文档操作(initSDK方法写在setContentView上面) 6.关键:在有用到的Mo

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

Android我的二维码扫描功能发展史(完整)

最近在研究下二维码扫描功能,跟据从网上查阅的资料到自己勉强已实现扫描功能来一一介绍我的二维码扫描功能实现的发展历程: 首页通过网络搜索发现做android二维码扫描功能看去都是基于google的ZXing项目开发。 2、搜索怎么使用ZXing实现自己的二维码扫描:从网上下载ZXing-2.2.zip以及core-2.2-source.jar文件,分别解压两个文件。然后把.jar解压出来的整个c

android 带与不带logo的二维码生成

该代码基于ZXing项目,这个网上能下载得到。 定义的控件以及属性: public static final int SCAN_CODE = 1;private ImageView iv;private EditText et;private Button qr_btn,add_logo;private Bitmap logo,bitmap,bmp; //logo图标private st

Android多线程下载见解

通过for循环开启N个线程,这是多线程,但每次循环都new一个线程肯定很耗内存的。那可以改用线程池来。 就以我个人对多线程下载的理解是开启一个线程后: 1.通过HttpUrlConnection对象获取要下载文件的总长度 2.通过RandomAccessFile流对象在本地创建一个跟远程文件长度一样大小的空文件。 3.通过文件总长度/线程个数=得到每个线程大概要下载的量(线程块大小)。

springboot家政服务管理平台 LW +PPT+源码+讲解

3系统的可行性研究及需求分析 3.1可行性研究 3.1.1技术可行性分析 经过大学四年的学习,已经掌握了JAVA、Mysql数据库等方面的编程技巧和方法,对于这些技术该有的软硬件配置也是齐全的,能够满足开发的需要。 本家政服务管理平台采用的是Mysql作为数据库,可以绝对地保证用户数据的安全;可以与Mysql数据库进行无缝连接。 所以,家政服务管理平台在技术上是可以实施的。 3.1

时间服务器中,适用于国内的 NTP 服务器地址,可用于时间同步或 Android 加速 GPS 定位

NTP 是什么?   NTP 是网络时间协议(Network Time Protocol),它用来同步网络设备【如计算机、手机】的时间的协议。 NTP 实现什么目的?   目的很简单,就是为了提供准确时间。因为我们的手表、设备等,经常会时间跑着跑着就有误差,或快或慢的少几秒,时间长了甚至误差过分钟。 NTP 服务器列表 最常见、熟知的就是 www.pool.ntp.org/zo

高仿精仿愤怒的小鸟android版游戏源码

这是一款很完美的高仿精仿愤怒的小鸟android版游戏源码,大家可以研究一下吧、 为了报复偷走鸟蛋的肥猪们,鸟儿以自己的身体为武器,仿佛炮弹一样去攻击肥猪们的堡垒。游戏是十分卡通的2D画面,看着愤怒的红色小鸟,奋不顾身的往绿色的肥猪的堡垒砸去,那种奇妙的感觉还真是令人感到很欢乐。而游戏的配乐同样充满了欢乐的感觉,轻松的节奏,欢快的风格。 源码下载