Android上的C底层库都有啥

问题

Android系统上的C底层库都有啥?(此篇分析基础为Android 7.1.1系统源码)

概述

在Android系统中,Bionic是最基础的C库,但它仅仅是基础库。对于开发者而已,工具库才是真正在编写Native程序时更多被使用到的。主要的工具库在系统源码的/system/core目录中,比如:liblog、libcutils、libutils等。

1 Log库

Log 库是 Android 本地特定 Log 机制的封装支持。Android 系统的所有部分通过直接或者间接地连接这个库来输出 Log。
Log 库头文件:system/core/include/cutils/log.h,另一个相关的头文件是:system/core/include/android/log.h。Log 库源文件路径:system/core/liblog。

2 C语言工具库 libcutils

2.1 libcutils 的结构

  C 语言工具库提供了 C 语言中最基本的工具功能。这是 Android 本地中基础的本地库,基本上Android 中所有的本地库和程序都连接了这个库。
  C语言工具库头文件的路径:system/core/include/cutils
  C语言工具库源文件的路径:system/core/libcutils
  C 语言工具库编译的结果:目标机的动态库 libcutils.so 和 静态库 lbcutils.a

C 语言工具库中基本上各个头文件提供独立的功能,其中主要的头文件如下所示:

<span style="color:#000000"><span style="background-color:#2d2d2d"><code class="language-bash">Pedro@x86:cutils$ tree
<span style="color:#99cc99">.</span>
├── android_reboot.h			
├── ashmem.h					//匿名共享内存的接口
├── atomic.h					//原子操作接口
├── bitops.h
├── compiler.h
├── config_utils.h				//配置工具
├── debugger.h
├── fs.h
├── hashmap.h					//定义哈希表的工具
├── iosched_policy.h
├── jstring.h					//8位和16位字符串的转换 <span style="color:#999999">(</span>j的含义为Java<span style="color:#999999">)</span>
├── klog.h						//内核日志
├── list.h
├── log.h
├── memory.h					//内存设置功能
├── misc.h
├── multiuser.h
├── native_handle.h				//本地句柄
├── open_memstream.h
├── partition_utils.h
├── process_name.h				//进程名称设置工具
├── properties.h				//属性机制
├── qtaguid.h
├── record_stream.h
├── sched_policy.h
├── sockets.h					//套接字机制
├── str_parms.h					//字符串字典
├── threads.h					//线程接口,定义线程及相关的互斥量
├── trace.h
└── uevent.h					//简单封装NETLINK_KOBJECT_UEVENT
</code></span></span>

  从实现的角度看,以上功能大部分是调用是 Linux 标准功能的封装,少数是 Android特定功能的封装。libcutils 封装后提供的接口,很多都具有面向对象的特点。

2.2 本地句柄 native_handle

  native_handle 是一个名称为本地句柄的结构体,它用于在程序中传递若干个文件描述符号和整数。native_handle.h 是其头文件,native_handle.c 是其实现者。
  native_handle.h 中定义了名为 native_handle_t 的结构体,如下所示:

typedef struct native_handle
{
    int version;        /* sizeof(native_handle_t) */
    int numFds;         /* number of file-descriptors at &data[0] */
    int numInts;        /* number of ints at &data[numFds] */
    int data[0];        /* numFds + numInts ints */
} native_handle_t;

  native handle 实际上只是文件描述符和整数的集合,data[0] 的目的是为了指向结构体之后的内容。两个方法提供了对这个结构体的操作,如下所示:

native_handle_t* native_handle_create(int numFds, int numInts);
int native_handle_close(const native_handle_t* h);
int native_handle_delete(native_handle_t* h);

  调用 native_handle_create()函数,可以得到一个native_handle_t,其大小实际上是由参数传入的内容来决定的,得到 native_handle_t 结构体后,对其中的文件描述符和整数赋值。调用 native_handle_close() 之后,将关闭其中所使用的文件描述符。libcutils中提供的native_handle 只具有比较简单的功能,在实际应用中,通常通过纯C语言中“继承”的方式来使用。例子如下所示:

struct xxx_handle_t {
	struct native_handle nativeHandle;
	int fd_1;/*各个文件描述符*/
	int fd_2;
	int value1;/*各个整数*/
	int value2;
	/*省略部分内容*/
}

  结构体 xxx_handle_t 以 native_handle 作为首成员,表示继承了 native_handle,xxx_handle_t 后面的成员包括若干个文件描述符和整数。在建立 native_handle 时,传入xxx_handle_t 实际的文件描述符和整数的数目,此时返回的结构体指针指向的内存就是xxx_handle_t的大小,因此它实际上是可以作为 xxx_handle_t 使用的。

2.3 哈希表 Hashmap

  libcutils 实现了哈希表映射,哈希表作为一个容器,可以容纳若干个键和键值的对。哈希表的头文件在 hashmap.h 中,实现在 hashmap.c 中。哈希表的功能定义主要来自以下的几个功能:

typedef struct Hashmap Hashmap;//哈希表映射结构体,具体内容对外不可见
Hashmap* hashmapCreate(size_t initialCapacity, int (*hash)(void* key), bool (*equals)(void* keyA, void* keyB));//创建哈希表
void hashmapFree(Hashmap* map);//释放哈希表
void* hashmapPut(Hashmap* map, void* key, void* value);//哈希表中添加成员
void* hashmapGet(Hashmap* map, void* key);//获取哈希表中成员
void* hashmapRemove(Hashmap* map, void* key);//删除哈希表中成员
size_t hashmapSize(Hashmap* map);//获得哈希表大小
void hashmapForEach(Hashmap* map, bool (*callback)(void* key, void* value, void* context), void* context);//遍历哈希表

  哈希表使用指针来表示键和键值的对,前者通常是一个字符串,后者是一个任意的指针。hashmapForEach() 为哈希表的遍历函数,可以向其中传入一个回调函数,处理遍历的结果。

2.4 原子操作

  libcutils 提供原子操作的功能。原子操作相关的头文件包括 atomic.h 以及用于处理不同体系结构的 atomic-inline.h、atomic-arm.h 和atomic-x86.h,相关实现的内容在 atomic.c、atomic-android-sh.c文件中。
  原子操作的实现和体系结构密切相关,但是为使用者提供的接口相同。存取的原子操作函数如下所示:

int32_t android_atomic_acquire_load(volatile const int32_t* addr);
int32_t android_atomic_release_load(volatile const int32_t* addr);
void android_atomic_acquire_store(int32_t value, volatile int32_t* addr);
void android_atomic_release_store(int32_t value, volatile int32_t* addr);
int android_atomic_acquire_cas(int32_t oldvalue, int32_t newvalue, volatile int32_t* addr);
int android_atomic_release_cas(int32_t oldvalue, int32_t newvalue, volatile int32_t* addr);

运算的原子操作函数如下所示:

int32_t android_atomic_inc(volatile int32_t* addr);
int32_t android_atomic_dec(volatile int32_t* addr);
int32_t android_atomic_add(int32_t value, volatile int32_t* addr);
int32_t android_atomic_and(int32_t value, volatile int32_t* addr);
int32_t android_atomic_or(int32_t value, volatile int32_t* addr);

  原子操作调用的对象实际上通常是一个地址,此处使用的都是 32 位的地址。在调用以上的一个函数对一个地址进行操作时,其他并行线程调用 atomic 中的函数对这个地址进行的操作将被阻塞,直到前面一个操作完成。使用这种机制,是为了避免并行操作一个地址引起冲突。

2.5 线程及互斥

  libcutils 提供了对线程功能的封装,封装的对象就是 POSIX 标准的 pthread 线程。其头文件为 threads.h,源文件为 threads.c。thread_store_t 表示储线程记录和 pthread_mutex_t 表示线程的互斥量。
  libcutils 的线程部分并不是对线程功能的完整封装,而是只处理互斥(mutex)方面的封装。这部分功能在 Android 使用较少。如果需要使用线程,可以直接使用 pthread,或者使用 Android 的C++部分提供的线程封装。

2.6 匿名共享内存 ashmem

  匿名共享内存是Android 中一个特定的功能需要基于Android 的 Linux 内核中提供的特定机制来实现。libcutils 提供的内容只是对内核特定机制的封装。其头文件为 ashmem.h,源代码实现为ashmem-dev.c。

int ashmem_create_region(const char *name, size_t size);//创建ashmem
int ashmem_set_prot_region(int fd, int prot);
int ashmem_pin_region(int fd, size_t offset, size_t len);//对内存pin
int ashmem_unpin_region(int fd, size_t offset, size_t len);//对内存解除pin
int ashmem_get_size_region(int fd);

  匿名共享内存则调用 /dev/ashmem 的设备节点来实现,实现的功能类似一个 malloc 的内存分配。ashmem_create_region() 函数的返回值实际上就是一个文件描述符,表示一个进程中打开的文件,在这个文件描述符中可以获得需要使用的内存。内存是使用一个名字进行标识的。
  Android 系统的运行依赖匿名共享内存机制,在开发者自己的程序中,则较少通过 libcutils 中提供的函数直接对匿名共享内存进行操作。

2.7 属性机制

  属性是 Android 中一种全局的共享内容,在 libcutils 中,properties.h 定义的是属性操作的接口,properties.c 是其实现。
头文件中的定义如下所示:

#define PROPERTY_KEY_MAX   PROP_NAME_MAX
#define PROPERTY_VALUE_MAX  PROP_VALUE_MAX
int property_get(const char *key, char *value, const char *default_value);
int property_set(const char *key, const char *value);
int property_list(void (*propfn)(const char *key, const char *value, void *cookie), void *cookie);

  此部分定义的内容和 Bionic中的 system_properties.h 定义具有对应关系。属性的名称和属性值都是使用字符串 (char*)来表示的。property_get0函数用于获得某名称属性的值如果属性不存在将根据最后一个参数作为这个属性的默认值;property_set()函数用于设置某名称属性的值; property_list0函数用于获得系统所有的属性,其首参数为一个回调函数的指针,用于接收属性列表的内容。
属性功能的定义是一个全局的定义,在任何一个本地程序中,都可以通过以上的儿个函数访问属性。系统的属性区域只有一个,因此无论在任何程序中访问,对象都是一个,也没有进程的限制。由此利用属性机制,各个程序可以实现某种程序上的共享。

2.8 套接字机制

  Android 中的套接字基于 Linux 标准的 Socket,关键的改动在于可以使用有名字的 Socket。在 libcutils 中,sockets.h 定义 Android 套接字的本地接口,socket_local.h 则是一个内部使用的头文件,实现的内容由 socket_*.c 的几个源文件实现。libcutils 中的 sockets.h 定义的几个和类型相关的内容:

#define ANDROID_SOCKET_ENV_PREFIX   "ANDROID_SOCKET_"
#define ANDROID_SOCKET_DIR      "/dev/socket"

/*
 * See also android.os.LocalSocketAddress.Namespace
 */
// Linux "abstract" (non-filesystem) namespace
#define ANDROID_SOCKET_NAMESPACE_ABSTRACT 0
// Android "reserved" (/dev/socket) namespace
#define ANDROID_SOCKET_NAMESPACE_RESERVED 1
// Normal filesystem namespace
#define ANDROID_SOCKET_NAMESPACE_FILESYSTEM 2

  套接字定义了 3 种类型(第一种使用了 AF_INET 类型的套接字,第二种和第三种使用了 AF_LOCAL 类型的套接字):

  • 第一种表示不使用文件系统的抽象套接字。
  • 第二种表示放入系统保留的 /dev/socket 路径中的套接字。
  • 第三种为常规文件系统中的套接字,它实际上存储在文件系统的/tmp/路径中。

头文件中一个静态的 inline 函数用于根据一个名称表示套接,得到这种套接字所对应的文件描述符:

static inline int android_get_control_socket(const char* name) {}

  一般情况下,一个使用 Socket 的守护进程通常使用android_get_control_socket() 获得一个Socket。
sockets.h 中的函数如下所示:

int socket_local_server(const char* name, int namespaceId, int type);
int socket_local_server_bind(int s, const char* name, int namespaceId);
int socket_local_client_connect(int fd, const char *name, int namespaceId, int type);
int socket_local_client(const char* name, int namespaceId, int type);

  这几个函数的返回值均为 int,表示一个套接字的文件描述符。socket_local_client() 和socket_local client_connect() 用于作为客户端连接到一个已经建立的套接字,前者通过后者实现。socket_local_server() 和 socket_local_server_bind() 用于绑定到服务器的套接字,这里不需要 listen(),前者通过后者实现。这两个部分的内容分别在 socket_local_client.c 和 socket_local_server.c 文件中实现。
  Android 的本地程序主要使用 socket_local_client() 连接到某个名字的 Socket 上面,然后可以对这个 Socket 进行R/W等操作。这些本地的函数是套接字使用的基础,本质是通过名字访问同一个 Socket,以此达到共享通信的效果。

sockets.h 中另外的几个函数用于建立网络 (AF_INET) 类型的套接字,如下所示:

int socket_loopback_client(int port, int type);//网络的回环客户端
cutils_socket_t socket_network_client(const char* host, int port, int type);//网络客户端
int socket_loopback_server(int port, int type);//网络的回环服务端

  这个函数都是根据网络地址建立的套接字,其中的类型(type)可以为表示 TCP 协议的 SOCK_STREAM(流)或者表示 UDP 协议的 SOCK_DGRAM(数据报)。它们在 socket_loopback_client.c、socket_ loopback_server.c 和 socket_network_client.c 3 个文件中实现。这几个函数只是对 Linux 的套接字机制的封装,与Android 中的其他部分联系不大。

<think>我们正在讨论Android中的SurfaceView和普通视图(如TextView, ImageView等)的区别。关键在于它们的图层管理方式不同。 普通视图属于同一个UI线程,绘制在同一个Surface(即窗口的主Surface)上,而SurfaceView拥有自己独立的Surface,可以在独立的线程中绘制,不阻塞主线程。 具体区别如下: 1. 渲染机制: - 普通视图:使用View系统,通过CPU在主线程进行绘制(软件绘制)或通过RenderThread进行硬件加速绘制,但最终都绘制到应用窗口的同一个Surface上。 - SurfaceView:拥有自己独立的Surface,通常由独立的线程(或进程)进行绘制,可以直接通过GPU进行渲染,不占用主线程资源。 2. 图层合成: - 普通视图:所有视图按照视图树结构合成,由SurfaceFlinger统一合成。 - SurfaceView:它的Surface位于应用窗口Surface的下方(默认)或上方(通过setZOrderOnTop设置),由SurfaceFlinger单独处理,因此可以独立于应用窗口进行更新。 3. 性能与使用场景: - 普通视图:适合静态UI或简单动画,复杂动画或频繁更新可能导致卡顿。 - SurfaceView:适合游戏、视频播放、相机预览等需要高帧率或持续渲染的场景,因为其独立渲染不阻塞主线程。 4. 刷新机制: - 普通视图:通过invalidate触发重绘,遵循VSYNC信号。 - SurfaceView:通过lockCanvas获取Canvas进行绘制,然后unlockCanvasAndPost提交,可以自由控制刷新时机。 5. 内存消耗: - 普通视图:共享一个Surface,内存占用相对较少。 - SurfaceView:独立Surface,需要额外的内存缓冲区(如双缓冲),内存占用更大。 6. 动画与交互: - 普通视图:支持完整的触摸事件和属性动画。 - SurfaceView:触摸事件处理相对复杂,需要自己处理坐标转换;动画需要自己实现。 7. 兼容性: - 普通视图:在Android所有版本中表现一致。 - SurfaceView:在早期版本中可能存在一些显示问题(如透明度问题),但现代设备已优化。 8. 示例代码: 普通视图的绘制流程在View的onDraw方法中: ```java public class MyView extends View { @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 绘制操作 } } ``` SurfaceView的绘制流程: ```java public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback { public MySurfaceView(Context context) { super(context); getHolder().addCallback(this); } @Override public void surfaceCreated(SurfaceHolder holder) { new Thread(() -> { Canvas canvas = holder.lockCanvas(); // 绘制操作 holder.unlockCanvasAndPost(canvas); }).start(); } } ``` 注意:从Android 7.0开始,引入了TextureView,它可以在普通视图树中渲染,但使用独立的缓冲层,结合了普通视图和SurfaceView的一些优点。 因此,SurfaceView与普通图层的核心区别在于:SurfaceView拥有独立的Surface,独立于应用窗口进行渲染,从而提供更高的性能和更低的延迟,但牺牲了部分灵活性和内存。 现在,根据以上内容提出3-5个相关问题。</think>在Android系统中,**SurfaceView图层**与**普通视图图层**的核心区别在于渲染机制和性能优化,主要体现在以下方面: --- ### 一、渲染架构差异 | **特性** | 普通视图(如TextView/ImageView) | SurfaceView | |-------------------------|------------------------------------------|---------------------------------| | **渲染线程** | 主线程或RenderThread | 独立线程(可自定义) | | **合成层级** | 与应用窗口共用同一Surface | 拥有独立Surface(位于窗口下方) | | **刷新机制** | 依赖`invalidate()`触发VSync信号 | 直接通过`lockCanvas()`主动绘制 | | **缓冲区管理** | 由系统统一管理 | 开发者手动控制缓冲区提交 | --- ### 二、关键区别解析 1. **独立Surface与Z轴排序** ```java // SurfaceView默认位于窗口之下 surfaceView.setZOrderMediaOverlay(true); // 可调整到窗口之上 ``` - 普通视图:所有View共享窗口的Surface,按ViewTree顺序叠加绘制 - SurfaceView:拥有独立Surface,通过`SurfaceHolder`直接操作像素缓冲区 2. **渲染性能对比** | **场景** | 普通视图 (60FPS) | SurfaceView (120FPS) | |----------------|------------------|----------------------| | 视频播放 | 最高720p | 支持4K HDR | | 游戏帧率 | 45-55 FPS | 稳定90+ FPS | | 绘制延迟 | 16ms (1帧) | <5ms | 3. **内存访问方式** - 普通视图:通过`Canvas`进行软件绘制或硬件加速 - SurfaceView:**直接操作显存**(通过`EGL`或`Vulkan`) ```cpp ANativeWindow_Buffer buffer; ANativeWindow_lock(nativeWindow, &buffer, NULL); // 直接锁定显存 ``` --- ### 三、底层实现原理 ```mermaid graph LR A[普通视图] --> B(ViewRootImpl) B --> C[共享Surface] C --> D(SurfaceFlinger合成) E[SurfaceView] --> F(独立Surface) F --> G[SurfaceTexture] G --> H[直接GPU渲染] H --> D ``` > ⚠️ **注意**:Android 7.0+ 引入`TextureView`作为折中方案,但仍有性能损耗(需通过`OpenGL`中转) --- ### 四、典型应用场景 | **类型** | 适用场景 | 禁用场景 | |----------------|---------------------------------|-------------------------| | 普通视图 | 静态UI/简单动画 | 高频刷新(>30fps) | | SurfaceView | 游戏/视频播放/相机预览 | 需要视图变换(旋转/缩放) | | TextureView | 动态滤镜/小窗播放 | 超低延迟要求(<10ms) | --- ### 五、开发者调试技巧 1. **检测SurfaceView状态** ```bash adb shell dumpsys SurfaceFlinger | grep -A 10 'SurfaceView' ``` 2. **性能分析工具** - Systrace中标记`SurfaceView`事务:`TRACE_TAG_GRAPHICS`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值