上一章学习如何log、debug和分析代码。本章中将会学习Android NDK提供的native API。
Bionic是Android提供的符合POSIX标准由C/C++实现的C库。Bionic是Google为Android实现的BSD标准的C库。Bionic的名字来源于BSD C 库和Linux处理线程、进程和signal的混合。
Bionic对于native应用开发至关重要,因为他提供了在Android平台上开发native代码最基础的API。在接下来的章节中,会接触到Bionic提供的各种各样的API。在使用具体的API之前,先大体看一下这些标准库。
标准库预览
标准库提供了编程语言常用的构造、算法、数据结构和构筑于硬件和操作系统的抽象接口,例如网络访问、多线程、内存管理和文件IO。依赖于编程语言本身的设计哲学,标准库之间有很多不同。标准库为应用开发提供了很多方便。每种语言都有标准库。Java的Java Class Library(JCL)提供了排序,字符串操作和IO等。Android framework也基于JCL提供了应用开发的标准库。
对于C语言,ANSI C标准定义了标准库的范围。这个标准库就是C standard library,简写libc。实现C语言伴随着实现libc。除了libc实现,POSIX C 库声明了额外的构造用于POSIX兼容系统。
另一个C库?
Google创建了一个新的C库而不是使用现成的GNU C Library(glibc)或者C Library for Embedded Linux(uClibc)的原因可以总结为以下三点:- License:glibc和uClibc都是LGPL协议,这限制了他们只能被使用在专有的应用。Bionic是BSD协议,不会限制任何使用
- Speed:Bionic为移动计算而生。专门为小型CPU和有限的内存设备打造。
- Size:Bionic将简单的哲学贯彻到底。它通过轻量封装和更少的API提供了对内核的访问,和其他实现相比更轻量。
二进制兼容性
尽管也是一个C标准库,但是Bionic和其他C标准库并不兼容。其他C库生成的文件和静态库并不能动态加载到Bionic中。此外,单独静态链接其他C标准库的代码(不和Bionic混合)可以正常运行在Android上,但是如果在运行时动态加载库就不行了。
提供的功能
Bionic提供了C标准库中的宏,类型定义,方法和一些Android专用的功能:- Memory Management: 内存管理
- File Input and Output: 文件IO
- String Manipulation:字符串操作
- Mathematics:数学库
- Date and Time:时间
- Process Controll:进程控制
- Signal Handler:信号控制
- Socket Networking:Socket网络
- Multithreading:多线程
- Users and Groups:用户和用户group
- System Configuration:系统配置
- Name Service Switch:命名服务切换?
缺失了什么?
Bionic是为Android专门设计的,所以不是所有的C library中的方法都支持。Android NDK 文档提供了缺失的方法列表,但是这样的信息也能在头文件中找到。Bionic头文件在ANDROID_NDK_HOME文件夹下platforms/android-/arch-/usr/include文件夹中。这个文件夹中的每一个头文件都清楚地标明了缺失的方法。例如,stdio.h中缺失的方法表示如下:
#if 0 /* MISSING FROM BIONIC */
char *ctermid(char *);
char *cuserid(char *);
#endif /* MISSING */
头文件中if 预处理指令用于disable后面的两行代码,并且相关的comment提示了缺失的方法。此外,Android NDK documentation也引用了那些在Bionic中只实现了很少一部分或者没有实现的方法(Stub)。
知识点:Stub是指实现不完整的方法。
Memory Management 内存管理
内存是进程能够获取的基础资源。对Java应用来说,内存由JVM管理。内存在new对象的时候分配,在GC的时候自动回收。但是,在native代码中,应用需要自行管理拥有的内存。正确的管理内存非常重要,否则内存紧张会导致应用稳定性和性能表现。Memory Allocation 内存分配
C/C++支持三种形式的内存分配:- Static allocation(静态分配):代码中定义的静态和全局变量就是应用启动的时候静态分配的。
- Automatic allocation(自动分配):方法传参和局部变量在方法调用的时候被自动分配 ,在方法结束的时候被自动释放。
- Dynamic allocation(动态分配):静态和自动分配都基于需要的内存区域是固定大小的,这个大小可以在编译期间被确定。动态分配用于运行时动态确定大小的内存。
接下来主要关注C/C++中的动态分配。
C动态分配内存管理
C语言本身没有提供内置的动态内存管理。Bionic C library提供了C代码动态内存管理的方法。C动态内存分配
C可以通过libc中的`malloc` 方法在运行时动态分配内存: void* malloc(size_t size);
使用这个方法需要导入stdlib.h 头文件。malloc需要一个表明需要分配内存大小的参数,单位是byte,然后返回一个指针指向这个新分配的内存:
/* Include standard C library header. */
#include < stdlib.h>
...
/* Allocate an integer array of 16 elements. */
int* dynamicIntArray = (int*) malloc(sizeof(int) * 16);
if (NULL == dynamicIntArray) {
/* Unable to allocate enough memory. */
...
} else {
/* Use the memory through the integer pointer. */
*dynamicIntArray = 0;
dynamicIntArray[8] = 8;
...
/* Free the memory allocation. */
free(dynamicIntArray);
dynamicIntArray = NULL;
}
提示:malloc是以byte为单位分配内存的,通过sizeof可以返回内存中数据类型(如int)的大小
如果无法申请指定大小的内存,malloc会返回NULL来表明申请失败。所以调用malloc后要通过返回值检查分配是否成功。一旦申请内存成功,动态申请内存可以通过指针来操作这个内存,直到这块内存被释放。
C释放动态内存
动态申请的内存在应用不在需要的时候需要被手动释放掉。standard C library(libc)中的`free` 方法就是用于动态释放内存的:void free(void* memory);
free 方法需要传入一个之前动态申请内存返回的指针:
int* dynamicIntArray = (int*) malloc(sizeof(int) * 16);
...
/* Use the allocated memory. */
...
free(dynamicIntArray);
dynamicIntArray = NULL;
注意,即使是在释放掉内存后,指向内存的指针dynamicIntArray 仍然指向原来的内存地址。后续通过这个指针访问内存会引起segmentation violation。在释放完内存后将立刻这个指针置为NULL防止后续不经意使用到是一个非常好的习惯。
C中改变已动态分配的内存
一旦内存动态分配完毕,如果需要改变它的大小,就需要通过standard C library提供的`realloc` 方法:void* realloc(void* memory, size_t size);
根据传入的size动态分配内存扩大或者缩小。这个方法需要传入两个参数,原始分配内存和新的size:
int* dynamicIntArray = (int*) malloc(sizeof(int) * 16);
int* newDynamicIntArray = (int*) realloc(
dynamicIntArray, sizeof(int) * 32);
if (NULL == newDynamicIntArray) {
/* Unable to reallocate enough memory. */
...
} else {
/* Update the memory pointer. */
dynamicIntArray = newDynamicIntArray;
...
}
realloc 返回指向重新分配的内存指针。这个方法会在保留内存中值的情况下可能在内存空间中申请新地址,在这种情况下会返回新的地址。如果方法调用失败,原来的内存不变,返回NULL。
C++动态内存管理
C++提供内置的动态内存管理支持。在C++中可以使用`new`和`delete`关键字来管理动态内存。当使用C++的时候,推荐使用C++中的关键字而不是standard C library(libc)来管理内存对象。和libc中的方法不一样,C++通过关键字的内存管理方法是类型可感知的并且支持C++对象生命周期。为了动态分配内存,new 关键字会调用C++ class的构造函数,与之相对,delete 关键字会调用class的析构函数来释放内存。
C++动态分配内存
C++通过new data type来动态分配内存:int* dynamicInt = new int;
if (NULL == dynamicInt) {
/* Unable to allocate enough memory. */
...
} else {
/* Use the allocated memory. */
*dynamicInt = 0;
...
}
如果需要申请一个array,后面需要跟上[数组大小]:
int* dynamicIntArray = new int[16];
if (NULL == dynamicIntArray) {
/* Unable to allocate enough memory. */
...
} else {
/* Use the allocated memory. */
dynamicIntArray[8] = 8;
...
}
C++释放动态内存
动态申请内存需要手动通过C++ `delete` 关键字释放内存:delete dynamicInt;
dynamicInt = 0;
delete[] dynamicIntArray;
dynamicIntArray = 0;
注意,释放单个对象和数组的方法delete 和delete[] ,错误地使用释放方法会导致内存泄漏。
C++改变已动态分配内存
C++并未像C那样提供内置的动态重新分配内存支持。内存的分配基于数据类型的大小和数据的数量。如果在运行过程中需要动态的改变数据的数量,推荐使用Standard Template Library(STL)中的集合类。内存分配和释放的方法需要匹配
`malloc`申请的内存需要通过`free` 释放,`new` 申请的内存需要通过`delete` 释放。开发者需要严格的按照上面的匹配来进行内存管理,否则会造成未知的异常。Standard File I/O 标准文件IO
native代码可以通过standard C library(libc)提供的Standard File I/O(stdio)方法访问文件系统。standard C library(libc)提供了两种I/O:- Low-level I/O:原始的I/O方法有着对数据源更精细的访问
- Stream I/O: 更高等级适合处理数据流的有buffer的I/O
Stream I/O更加灵活,通常处理文件也更加方便。本章将会重点关注Stream I/O,Low-level I/O将会在后续章节的socket里面部分介绍。
Standard Stream
native代码有三个已有的stream来使用stream I/O。这些 stream代表着native应用的标准input和output渠道。他们分别在下面三个头文件中定义:stdin: 应用Standard input streamstdout: 应用Standard output streamstderror: 应用Standard error stream
因为native应用作为Android这样一个图形化界面系统的一个模块运行,stream显得不那么有用。但是集成已有的代码时,开发者需要搞清楚图形化界面背后的各种标准stream对于解决问题会非常有帮助。
使用Stream I/O
Stream I/O的构造和方法在standard C library(libc)的`stdio.h` 中定义。为了在native代码中使用stream I/O,需要再代码中导入该头文件: #include <stdio.h>
由于历史原因,在standard C library中代表stream的数据结构叫做FILE ,而不是stream。FILE对象中包含了stream I/O链接的所有需要的信息。FILE对象通过stream I/O方法创建和维护,并且一般不会直接通过应用的代码交互。
打开Stream
通过stream的`fopen` 方法,stream可以访问一个新的或者已存在的文件。`fopen` 方法需要两个参数:文件的名称和打开方式,并且返回一个指向stream的指针:FILE* fopen(const char* filename, const char* opentype);
fopen的第二个参数是一个用于控制文件如何被打开的标识字符串。包括以下类型:
- r:以只读模式打开已经存在的文件
- w:以只写模式打开文件。如果文件已经存在,内容会被清空,大小变为0
- a:以append模式打开文件。文件的内容会得到保存,新的输出内容会append到文件的末尾。如果文件不存在,会创建一个新文件。
- r+:以读-写模式打开文件
- w+:以读-写模式打开文件。如果文件已经存在,内容会被清空,大小为0。
- a+:以读和append模式打开文件。读的时候文件的起始位置在文件开头,append的时候文件起始位置在文件末尾。
注意:如果文件以r+、w+或者a+这三种双重模式打开文件时,在读和写切换的时候要调用
fflush方法清空buffer。
如果文件在指定的模式下打开失败,fopen 方法返回NULL指针。在成功的情况下返回一个FILE指针指向stream:
#include <stdio.h>
...
FILE* stream = fopen("/data/data/com.example.hellojni/test.txt", "w");
if (NULL == stream)
{
/* File could not be opened for writing. */
}
else
{
/* Use the stream. */
/* Close the stream. */
}
一旦stream打开,就会通过它读写直到这个stream close。
向Stream写入
Stream I/O提供了四个写方法。接下来会简单过一遍这些方法:向Stream写入数据块
`fwrite` 方法用于向Stream中写入数据块: size_t fwrite(const void* data, size_t size, size_t count, FILE* stream);
下面的代码显示了向stream写入count个每个大小为sizeof的data buffer:
char data[] = { 'h', 'e', 'l', 'l', 'o', '\n' };
size_t count = sizeof(data) / sizeof(data[0]);
/* Write data to stream. */
if (count ! = fwrite(data, sizeof(char), count, stream))
{
/* Error occured while writing to stream. */
}
方法会返回最终向stream中写入的元素数量。在成功的情况下,返回的个数等于写入的个数。
向Stream中写入字符串
`fputs`方法可以向stream中写入null结尾的字符串:int fputs(const char* data, FILE* stream);
/* Writing character sequence to stream. */
if (EOF == fputs("hello\n", stream))
{
/* Error occured while writing to the stream. */
}
如果字符串写入失败,fputs 方法返回EOF。
向stream写入单个字符
`fputc`方法可以向stream写入单个字符或者byte: int fputc(int c, FILE* stream);
fputc 方法把单个字符c当成一个integer并且在写入stream之前转化成一个unsigned char:
char c = 'c';
/* Writing a single character to stream. */
if (c ! = fputc(c, stream))
{
/* Error occured while writing character to string.
}
如果向stream写入character成功,fputc 方法返回character本身,否者返回EOF。
向Stream写入格式化数据
`fpintf` 方法可以被用于向stream写入格式和可变数量的参数的数据:int fprintf(FILE* stream, const char* format, ...);
fpintf 需要传入一个指向stream的指针,格式化字符串和可变数量(格式化字符串中指定的数量)的参数。格式化字符串中包含普通的字符和格式化占位符。在后续的stream写入中普通字符保持不变。格式化占位符会被fprintf 方法替换成为相应的参数。常见的占位符如下:
- %d,%i: 格式化integer为signed decimal
- %u:格式化unsigned integer 为 unsigned decimal
- %o:格式化unsigned integer 为 octal
- %x:格式化unsigned integer 为 hexadecimal
- %c:格式化integer 为单个character
- %f:格式化double precision为 floating point number
- %e:格式化double precision为固定格式
- %s:打印NULL结尾的字符串array
- %p:将给定的指针作为内存地址打印出来
- %%:写入一个%字符
格式化字符串中的占位符的顺序和类型要和后续提供的参数相匹配:
/* Writes the formatted data. */
if (0 > fprintf(stream, "The %s is %d.", "number", 2))
{
/* Error occurred while writing formatted data. */
}
fprint 方法返回写入stream的字符数量。发生错误的时候返回负值。关于更多的格式可参考以下链接:pubs.opengroup.org/onlinepubs/…
Flushing the Buffer
Stream I/O 会把需要写入文件的数据累积起来,然后异步写入,而不是立刻写入。同样的,在读取文件的时候也是通过block的形式读取,而不是一个字符一个字符地读取。就是buffering。刷新buffer意味着将累积的数据写入底层的文件。Flush会在以下情况自动完成:
- 正常关闭应用
- 向行缓冲区中写入新行
- 当buffer满的时候
- 当stream 关闭的时候
Stream I/O提供了fflush 方法来在需要的时候刷新缓冲区:
int fflush(FILE* stream);
下面的代码显示如何获取stream指针然后刷新output buffer。
char data[] = { 'h', 'e', 'l', 'l', 'o', '\n' };
size_t count = sizeof(data) / sizeof(data[0]);
/* Write data to stream. */
fwrite(data, sizeof(char), count, stream);
/* Flush the output buffer. */
if (EOF == fflush(stream))
{
/* Error occured while flushing the buffer. */
}
从Stream中读取
和向stream中写入一样,stream I/O也提供了四个读取的方法。从Steam中读取数据块
`fread` 方法用于从stream中读取数据块:size_t fread(void* data, size_t size, size_t count, FILE* stream);
fread 方法从stream中读取count个每个大小为size的数据元素到data buffer中。这个方法会返回读取元素的个数:
char buffer[5];
size_t count = 4;
/* Read 4 characters from the stream. */
if (count ! = fread(buffer, sizeof(char), count, stream))
{
/* Error occured while reading from the stream. */
}
else
{
/* Null terminate. */
buffer[4] = NULL;
/* Output buffer. */
MY_LOG_INFO("read: %s", buffer);
}
如果成功的话,返回的数量和传入的count一致。
从Stream中读取字符串
`fget`方法从stream中读取newline-terminated 字符结尾的字符串。 char* fgets(char* buffer, int count, FILE* stream);
fget 方法从strem中读取最多count-1个字符和一个newline 字符到buffer中:
char buffer[1024];
/* Read newline terminated character sequence from the stream. */
if (NULL == fgets(buffer, 1024, stream))
{
/* Error occured while reading the stream. */
}
else
{
MY_LOG_INFO("read: %s", buffer);
}
如果调用成功返回buffer的指针,否则返回NULL。
从Stream中读取单个字符
`fgetc`方法用于从stream中读取单个字符(single unsigned char)。int fgetc(FILE* stream);
fgetc方法从stream中读取单个字符,并转化为intger返回:
unsigned char ch;
int result;
/* Read a single character from the stream. */
result = fgetc(stream);
if (EOF == result)
{
/* Error occured while reading from the stream. */
}
else
{
/* Get the actual character. */
ch = (unsigned char) result;
}
如果调用成功返回代表字符的integer,失败返回EOF。
从Stream中读取格式化数据
`fscanf`方法用于从stream中读取格式化数据。这个方法和`fprintf`类似,但是过程是反的:int fscanf(FILE* stream, const char* format, ...);
这个方法从stream中读取format格式的字符,并将满足条件的占位字符赋值给后续的可变数量的变量。占位符如下:
- %d,%i: 读取一个signed decimal
- %u:读取 unsigned decimal
- %o:读取 octal 转化为unsigned integer
- %x:读取hexadecimal为unsigned integer
- %c:读取单个character
- %f:读取floating point number
- %e:读取固定格式的浮点数
- %s:读取string
- %%:读取%
示例代码:
char s[5];
int i;
/* Stream has "The number is 2" */
/* Reads the formatted data. */
if (2 ! = fscanf(stream, "The %s is %d", s, &i))
{
/* Error occured while reading formatted data. */
}
如果方法调用成功,返回读取到的item的数量。如果发生错误,返回EOF。
检查文件的结束
当读取文件的时候,`feof`方法可以被用于检查stream是否设置了end-of-file指示符。 int feof(FILE* stream);
feof方法接受一个stream指针,如果没有到达stream末尾,返回一个非0值,否者返回0表示到达stream的结尾:
char buffer[1024];
/* Until the end of the file. */
while (0 == feof(stream))
{
/* Read and output string. */
fgets(buffer, 1024, stream);
MY_LOG_INFO("read: %s", buffer);
}
Seeking Position
通过`fseek`方法可以改变stream中的位置:int fseek(FILE* stream, long offset, int whence);
fseek 方法需要传入一个stream指针,相对偏移量和相对模式。相对模式的值有以下三种:
- SEEK_SET:相对于stream开始的偏移量
- SEEK_CUR:相对于当前位置的偏移量
- SEEK_END:相对于结尾的偏移量
下面的样例代码写入4个字符,回退4个字符,然后在用另外四个字符覆盖:
/* Write to the stream. */
fputs("abcd", stream);
/* Rewind for 4 bytes. */
fseek(stream, -4, SEEK_CUR);
/* Overwrite abcd with efgh. */
fputs("efgh", stream);
这里缺少了错误检查。fseek 在调用成功的时候返回0,任何非0返回值都表示错误。
错误检查
大多数stream I/O 方法会返回EOF来表示错误或者表示达到文件结尾。可以通过`ferror` 方法检查前一个操作是否发生了错误:int ferror(FILE* stream);
ferror方法会在发生错误后返回非 0值。
/* Check for the errors. */
if (0 ! = ferror(stream))
{
/* Error occured on the previous request. */
}
关闭Streams
`fclose` 用于关闭stream。输出buffer中包含的任何内容都会被写入stream,输入buffer中包含的任何内容都会被丢弃:int fclose(FILE* stream);
fclose 方法接受一个stream指针作为参数。在调用成功的情况下返回0,返回EOF表示发生错误:
if (0 ! = fclose(stream))
{
/* Error occured while closing the stream. */
}
因为磁盘空间不足导致输出buffer向stream写入失败会引起关闭stream错误。同样检查fclose 返回值是一个好习惯。
和进程交互
Bionic允许native代码开启进程并和其他native进程交互。Native代码可以执行shell commands,也可以在后台执行一个进程,并且与之交互。本章中会简单介绍相关方法。执行Shell Command
`system` 方法可以用于执行shell command。为了使用这个方法,`stdlib.h` 头文件需要被导入: #include <stdlib.h>
system执行shell command方法会阻塞native代码直到被执行的command执行结束:
int result;
/* Execute the shell command. */
result = system("mkdir /data/data/com.example.hellojni/temp");
if (−1 == result || 127 == result)
{
/* Execution of the shell failed. */
}
和子进程通信
`system`命令没有提供发送指令给子进程或者从子进程接收指令的通信渠道。native代码只是等待command执行结束。在某些场景下,提供native代码进程间通信是非常有用的。popen 方法被用于在父进程和子进程间打开一个双向通信的管道。为了使用这个方法,需要导入stdio.h头文件:
FILE *popen(const char* command, const char* type);
popen方法接收需要被执行的方法和通信渠道类型的参数,返回一个指向stream的指针。在方法执行错误的情况下会返回NULL。下面的代码显示了通过之前讲过的stream I/O方法像和文件交互一样和子进程交互:
#include <stdio.h>
...
FILE* stream;
/* Opening a read-only channel to ls command. */
stream = popen("ls", "r");
if (NULL == stream)
{
MY_LOG_ERROR("Unable to execute the command.");
}
else
{
char buffer[1024];
int status;
/* Read each line from command output. */
while (NULL ! = fgets(buffer, 1024, stream))
{
MY_LOG_INFO("read: %s", buffer);
}
/* Close the channel and get the status. */
status = pclose(stream);
MY_LOG_INFO("process exited with status %d", status);
}
注意:
popen返回的stream默认是完全buffer的,开发者在需要的时候需要flush相关buffer。
当子进程执行结束,stream应当使用pclose关闭:
int pclose(FILE* stream);
这个方法接收stream 指针作为参数,等待子进程执行结束并且返回。
系统配置
Android系统以key-value的形式保存一些系统属性。Bionic提供了一系列方法用于native代码查询。为了使用这些方法,系统属性头文件首先需要被导入: #include <sys/system_properties.h>
系统属性头文件声明了必要的方法和结构。每个系统属性包含了最长PROP_NAME_MAX的字符key和最长PROP_VALUE_MAX的字符VALUE。
通过名称获取系统属性值
`__system_property_get` 方法用于通过name寻找系统属性: int __system_property_get(const char* name, char* value);
如下面代码所示,这个方法将null结尾的属性值拷贝到提供的值指针中,并且返回属性的size。整个复制的长度不会超过PROP_VALUE_MAX:
char value[PROP_VALUE_MAX];
/* Gets the product model system property. */
if (0 == __system_property_get("ro.product.model", value))
{
/* System property is not found or it has an empty value. */
}
else
{
MY_LOG_INFO("product model: %s", value);
}
如果属性未定义,会返回0。
通过名称获取系统属性
`__system_property_find__` 可以用于一个直接指向系统属性的指针const prop_info* __system_property_find(const char* name);
这个方法会通过名称搜索系统属性,返回一个指向这个名称的指针,如果没有找到,返回NULL。返回的指针在系统整个生命周期都有效,可以通过缓存下来的方式避免后续再次查找。__system_property_read 方法可以用于从这个指针中获取系统属性值:
const prop_info* property;
/* Gets the product model system property. */
property = __system_property_find("ro.product.model");
if (NULL == property)
{
/* System property is not found. */
}
else
{
char name[PROP_NAME_MAX];
char value[PROP_VALUE_MAX];
/* Get the system property name and value. */
if (0 == __system_property_read(property, name, value))
{
MY_LOG_INFO("%s is empty.");
}
else
{
MY_LOG_INFO("%s: %s", name, value);
}
}
__system_property_read 方法需要传入一个系统属性指针和两个字符串指针:
int __system_property_read(const prop_info* pi, char* name, char* value);
这个方法会拷贝null结尾的字符串指针,并且返回value的size。整个拷贝的值长度不会超过PROP_VALUE_MAX。name参数是可选的。如果传入了name字符串指针,这个方法会拷贝名称到这个指针指向的内存中,长度不会超过PROP_NAME_MAX。
Users and Groups
Linux内核支持多用户。尽管Android是给单个手持设备使用的,但是使用了基于用户的权限控制模型。- Android在虚拟机沙盒中运行应用并且将他们当成系统中的不同用户。依赖于基于用户的权限控制模型,Android可以很轻松的做到防止应用访问其他应用的数据和内存。
- 服务和硬件资源的保护也是基于用户的权限控制实现的。每种资源都有自己的保护用户组。在应用部署过程中,应用向系统请求使用这些资源。如果应用不在相应的资源使用用户组内,则无法访问相应资源。
Bionic提供了users 和 groups相关信息的方法支持,大多数这些方法都只有不完整实现的方法(stub)。本章中将会介绍其中关键的方法,首先需要导入unistd.h 头文件:
#include <unistd.h>
获取应用User、Group ID 和 User Name
每个安装的应用都有从10000开始的自己的user和group ID。低于10000的ID是给系统服务用的。user ID可以通过`getuid` 方法获取,group ID可以通过`getgid`方法获取。每个安装的应用都会被赋予一个以app_<应用ID>的user name。例如,user ID为10040的应用user name是app_40。user name可以通过`getlogin` 方法获取:uid_t uid;
/* Get the application user ID. */
uid = getuid();
MY_LOG_INFO("Application User ID is %u", uid);
gid_t gid;
/* Get the application group ID. */
gid = getgid();
MY_LOG_INFO("Application Group ID is %u", gid);
char* username;
/* Get the application user name. */
username = getlogin();
MY_LOG_INFO("Application user name is %s", username);