回顾 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 多线程并发读写时,很容易抛出以下异常:

1
2
3
4
5
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 表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
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 设置进程优先级函数:

1
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 采用数组保存,不仅一次能等待的句柄个数有限,而且查找速度慢;

深入理解 Android:系统初始化

系统初始化(init):

加载并解析配置文件:

  1. 调用 parse_config() 解析 init.rc;

  2. init.rc 中包含一个个 section,调用 parse_new_section() 逐个解析;

  3. parse_new_section() 内部调用 parse_service()parse_line_service();

创建 zygote:

  1. zygote 被放在一个 service section 中;

  2. zygote 完全解析结束后,得到 service_listsocketinfocommands 三个双向链表;

  3. zygote 最初名叫 app_process,在 main() 函数中:

    1. 先调用 set_process_name() 改名;

    2. 然后调用 AppRuntime.start() 创建虚拟机,开启 Java 世界;

    3. 最后创建系统服务驻留的 SystemServer 进程;

JNI 笔记

最近在看《深入理解 Android: 卷 I》,Framework 层要和 Native 层打交道,一开始肯定得介绍 JNI;

13 年项目中用到 WebP 的时候虽然接触过 JNI,但没有系统学习过,所以这里算是做个笔记。

JNI方法注册:

静态注册

  1. 对所有声明了 native 方法的 Java 类,运行 javah -o output packageName.className 生成 .h 头文件;

  2. 生成的 JNI 函数名带有包路径。

动态注册

  1. 通过 JNINativeMethod 结构体定义 Java native 方法和 JNI 中函数的对应关系;

  2. 调用 AndroidRunTime::registerNativeMethods() 函数完成注册,该函数内部调用 (*env)->RegisterNatives() 函数;

  3. Java 层调用 System.loadLibrary(),动态注册自动在 JNI_OnLoad() 函数中进行的。