iOS 引用 C/C++ 项目:平台差异导致的代码移植

由于 Linux(包括 Android)在嵌入式和服务端的流行,很多 C/C++ 代码都是专为 Linux 而写;加上历史原因,很可能还是基于 GCC 编译;

而 iOS/MacOS 基于类 Unix 系统 BSD,并且从 2011 年的 OS X 10.7 开始,默认的 C++ Runtime 就从 GNU 的 libstdc++ 迁移到 LLVM 的 libc++,二者不完全二进制兼容(Xcode 编译时只能二选一);

同时编译器也从 GCC 切换到了 Clang,而 Clang 通常比 GCC 语法检查更严格:

Clang strives to both conform to current language standards (up to C11 and C++11) and also to implement many widely-used extensions available in other compilers, so that most correct code will “just work” when compiled with Clang.
However, Clang is more strict than other popular compilers, and may reject incorrect code that other compilers allow.

基于上述原因,我们需要对部分 C/C++ 代码做改动,以便移植到 iOS/MacOS 项目中。

下面仅梳理下最近项目中遇到的情况。

iOS 引用 C/C++ 项目:交叉编译与 Objective-C++

最近终于有幸参与公司的 iOS 项目,其中有个 C/C++ 写的库需要调用;

之前对 Android JNI/NDK 调用 C/C++ 还算熟悉,但 iOS 混编 C/C++ 却是初次接触,各种被虐..

上个周末基本都在解决库的编译问题,爱人 Amble 也耐心帮我查资料、作分析,感动之余,决定把中途遇到的问题记录下来。

回顾 2016

今年的 GitHub 提交记录相当惨淡,学东西也基本是蜻蜓点水:

FileChannel 高速拷贝文件的秘密

最近在做性能优化,发现 Java NIO 中的 FileChannel 类的 transfer 相关方法,能显著提升文件拷贝速度(自测减少耗时 30-70% 左右)。

官方的说法是,利用了文件系统的缓存:

This method is potentially much more efficient than a simple loop that reads from the source channel and writes to this channel.
Many operating systems can transfer bytes directly from the source channel into the filesystem cache without actually copying them.

SQLite 并发读写问题

Android 中对 SQLiteDatabase 多线程并发读写时,很容易抛出以下异常:

java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
    at android.database.sqlite.SQLiteConnectionPool.throwIfClosedLocked(SQLiteConnectionPool.java:962)
    at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:599)
    at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:348)
    at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:894)

回顾 2015

毕业工作三年,一直埋头应用层开发,专业理论知识有恢复出厂设置的危险,而且深感各方面提升都遇到了瓶颈;
而去年疯狂地尝试各种新技术,导致战线拉得太长;所以今年自然是回归主旋律、填坑为主:代码写得少,书看的多,总结归纳的多。
通过对基础理论的复习巩固和专业领域的深挖,不仅重新(初步)完善了知识体系,还顺利拿到鹅厂 offer,也算弥补了四年前校招的缺憾。

JNI 引用问题梳理

最近项目中有个视频文件分块上传的模块,核心逻辑是 C/C++ 实现的,Android 上层调用自然又要写 JNI。

其中有个需求是 Native 层上传进度更新时需要回调 Java 代码,这里我用了 C++11 的 Lambda 表达式:

std::function<void(unsigned)> cxx_progress_callback;
if (jcallback) {
    cxx_progress_callback = [&env, &jcallback]
            (unsigned progress) -> void {
        jclass jcallback_class = env->GetObjectClass(jcallback);
        if (jcallback_class) {
            jmethodID methodId = env->GetMethodID(jcallback_class, "onProgressChanged", "(I)V");
            if (methodId) {
                env->CallVoidMethod(jcallback, methodId, progress);
            }
        }
    };
}

Java 内存分配与垃圾回收机制

程序计数器:

  • 用于指示当前线程执行的指令行号,字节码解释器通过改变它的值选取下一条待执行的指令;

  • 分支、循环、跳转、异常处理、线程恢复都需要依赖它;

  • 它是线程私有的;

DexClassLoader 实现 Android 插件加载

Java 中的 ClassLoader:

Java 中 ClassLoader 用于动态加载 Class 到 JVM,包含 BootstrapClassLoader(C++ 编写,用于加载系统核心类)、ExtClassLoader(用于加载 lib/ext/ 目录的扩展 API)、AppClassLoader(加载 CLASSPATH 目录下的类)。

双亲委托机制:

  • 任何自定义 ClassLoader 都必须继承 ClassLoader 抽象类,并指定其 parent 加载器,默认为 BootstrapClassLoader;

  • 任何自定义 ClassLoader 在加载一个类之前都会先委托其 parent 去加载,只有 parent 加载失败才会自己加载;

    • 这样既可以防止重复加载,又可以排除安全隐患(防止用户替换系统核心类);

    • 所以一般只需要重写 findClass()方法即可(在 parent 加载失败时调用);

  • 双亲委托机制是在 loadClass() 方法实现的,要想避开(自己验证安全性,比如 Tomcat 的 WebAppClassLoader),必须重写 loadClass() 方法;

深入理解 Android:ActivityManagerService

Linux 设置进程优先级函数:

int setpriority(int which, int who, int prio);
  • 参数 whichwho 联合使用:

    • whichPRIO_PROGRESS 时,who 代表进程;

    • whichPRIO_PGROUP 时, who 代表进程组;

    • whichPRIO_USER 时,who 代表 uid

  • 参数 prio 为优先级,范围 [-20,19],值越大,被调用几率越小;

深入理解 Android:PowerManagerService、BatteryService/BatteryStatsService

PowerManagerService 简介:

  • PowerManagerServiceIPowerManager.Stub 类派生,并实现了 Watchdog.MonitorLocalPowerManager 接口;

  • 客户端使用 PowerManager 类,其内部通过代表 BinderProxy 端的 mService 成员变量与 PowerManagerService 进行 Binder 通信;

  • PowerManagerServiceBatteryServiceBatteryStatsServiceLightServiceSensorManagerActivityManagerServiceWindowManagerService 等许多系统服务均有交互;

深入理解 Android:PackageManagerService

SystemServer 创建 PackageManagerService:

  1. 调用 PackageManagerServicemain() 方法,得到 IPackageManager 对象;

  2. 调用 IPackageManager.isFirstBoot() 判断是否开机后第一次启动(ZygoteSystemServer 退出后,init 会再次启动它们);

  3. 调用 IPackageManager.performBootDexOpt() 执行 dex 优化;

  4. 调用 IPackageManager.systemReady() 通知系统已进入就绪状态;

深入理解 Android:Surface 系统

FrameBufferDevice:

  • FrameBuffer 就是存储图像帧数据的缓冲区;

  • FrameBufferDevice 是 Linux 平台的虚拟显示设备,为真实设备提供统一框架,这样应用层通过标准的 ioctlmmap 系统调用就可以操作显示设备;

  • FrameBuffer 中的缓冲区就是通过 mmap 把设备中的显存映射到用户空间的:在这块缓冲区写数据,就相当于在屏幕上绘制;

Window 和 View:

  • Window 是抽象基类,用于控制顶层窗口的外观和行为(背景、标题栏、默认按键处理等);

  • View 是基本的 UI 单元,占据屏幕一块矩形区域,用于绘制和事件处理;

深入理解 Android:Binder 通信机制

Native 层 Binder 通信:

初始化 ProcessState:

  1. 单例模式,一个进程只能初始化一次;

  2. 构造函数初始化 mDriverFD 参数时,自动调用 open_driver(),打开 /dev/binder 设备,它是用于内核进程间通信的虚拟设备;

  3. open_driver() 返回的 mDriverFD 对象,调用 nmap() 方法,为 Binder 分配内存用于接收数据;

获取 defaultServiceManager:

  1. defaultServiceManager() 函数内部调用了 ProcessStategetContextObject() 函数;

  2. 调用 getStrongProxyForHandle(),根据索引查找对应的资源,返回一个 BpBinder 对象(如果不存在则创建);

深入理解 Android:线程、同步、消息

Linux 中的 epoll 机制:

  • epoll 是 Linux 中高效的 IO 复用机制,显著提高大量并发连接下只有少数活跃时 CPU 的利用率;

  • epoll 无需要遍历整个被监听的文件描述符集合,只需遍历被内核 IO 事件异步唤醒而加入 Ready 队列的文件描述符集合;

  • epoll 不仅提供 select/poll 那种 IO 等待的水平触发(Level Triggered),还提供边缘触发(Edge Triggered),使用户程序能缓存 IO 状态,减少 epoll_wait/epoll_pwait 调用,提高效率;

  • epoll 用于保存事件的数据结构采用了红黑树,查找效率高;而 select/poll 采用数组保存,不仅一次能等待的句柄个数有限,而且查找速度慢;