文章大纲
- 引言
- 一、Home Launcher 应用启动概述
- 二、PKMS 被SystemServer启动
- 1、在SystemServer#mian方法中通过PackageManagerService#main方法触发PKMS启动
- 2、调用PKMS的回调函数systemReady
- 三、PKMS 遍历安装系统目录下的App
- 1、Apk的结构
- 2、PackageManagerService#main方法触发App 安装流程
- 3、构造PackageManagerService 对象
- 3.1、保存应用的安装和运行信息
- 3.1.1、
- 3.1.2、创建Settings 对象备用,接着把系统App的部分信息封装到SharedUserSetting 对象保存到Settings中
- 2、new MessageQueue(quitAllowed)触发Native 层 MessageQueue 的创建
- 2.1、Java 层创建MessageQueue 对象触发Native 层NativeMessageQueue的创建
- 2.2、NativeMessageQueue 的构造函数里创建Native层的Looper对象
- 2.3、Native层 Looper对象构造方法初始化epoll相关
- 2.4、Native层 Looper对象rebuildEpollLocked函数执行epoll相关
- 四、Looper循环读取消息并处理的流程
- 1、Looper#loop方法开启无限循环流程
- 2、MessageQueue#next 方法读取下一个Message
- 2.1、ptr=mPtr
- 2.2、pendingIdleHandlerCount
- 2.3、nextPollTimeoutMillis
- 3、无限循环JNI调用nativePollOnce函数触发检查是否有新的消息需要处理
- 4、调用NativeMessageQueue对象的pollOnce 函数
- 5、里的pollInner函数真正通过epoll去检查是否有新消息需要处理
- 6、里的awoken函数去读取其他线程发送过来的消息
- 四、其他线程通过Handler发送消息的流程
- 1、构造Handler对象
- 2、Handler#sendMessageAtTime方法触发消息发送
- 3、#enqueueMessage 把消息存入MessageQueue
- 3.1、在当前链表的头部插入的新消息,可能需要通过JNI 调用nativeWake方法唤醒
- 3.1.1、NativeMessageQueue#wake() 函数触发唤醒流程
- 3.1.2、NativeMessageQueue#wake() 函数——># wake()
- 3.1.3、# wake()函数真正通过epoll机制唤醒
- 3.2、在当前链表的非头部(中间、尾部)插入的新消息,无需唤醒目标线程
- 五、线程消息的处理流程
- 1、#dispatchMessage方法处理消息
- 1.1、Message里配置了callback则由handleCallback方法处理
- 1.2、Message的callback为null,则由handleMessage方法处理
- 2、IdleHandler 处理线程空闲消息
- 2.1、线程空闲消息处理接口
- 2.2、#addIdleHandler方法给线程注册到空闲消息处理器
- 2.3、线程空闲时触发queueIdle方法的执行
- 3、IdleHandler的 应用
- IdleHandler它在源码里的使用场景:
- ActivityThread中GcIdler
引言
前面从大的方面总结了Android 系统的启动流程,当然还有很多其他的细节,比如JVM的启动、JNI的注册、各种核心服务的启动都没有详细介绍,这些都在后续文章中一一分享,这篇就介绍Android 启动的“最后一步”——Home Launcher 应用程序的启动。
基于Android 27 ,为了阅读,本文约定方法表示Java的,而函数表示Native层概念,仅供参考
一、Home Launcher 应用启动概述
Android 系统在启动过程中会通过PKMS(PackageManagerService)服务自动去扫描系统中指定的目录寻找APK文件并自动安装,概括起来 PKMS 在准备安装APP时 主要做了两大工作:
- 解析App的清单文件()获取其对应的包信息供后面封装为对应的JavaBean 对象(即APK的内存描述对象)
- 为App 分配Linux的用户Id 和Linux 用户组Id ,要不然可能在系统里运行时因为安全机制无法得到对应的权限。所以Android 中么一个App 都有一个对应的Linux 用户ID,在PMS 安装该App 时检测发现它没有和其他应用共享同一个用户ID时,就会自动给其分配一个用户ID,除了用户ID之外,当PMS安装时还发现该App 申请了一个特殊的资源访问权限,还会给其分配对应的Linux 用户组ID
当所有App都安装完毕之后,Android Framework 还会完成启动的最后一步——启动Home Launcher 应用程序(Home Launcher 本质上就是一个Android App 其特殊之处在于是由系统自动安装并自动启动的作为人机交互的首个界面。)
除了Launcher ,Android 里还有很多都是一个独立App,像相机、设置、墙纸、短信等等都是以App 集成到系统之中的。
在Launcher 启动时会通过Binder 请求PMS 服务返回系统中已经安装了的所有App的信息比分别将这些所有信息封装成一个快捷图标并显示到主屏之中,作为用户打开其他App 的入口。综上我们可以概况系统启动最后一步要做的工作有两:
- 通过PKMS 安装所有的指定目录下的App
- Launcher 启动并展示App的快捷图标
二、PKMS 被SystemServer启动
1、在SystemServer#mian方法中通过PackageManagerService#main方法触发PKMS启动
在前面文章中介绍System进程()启动过程中,在AMS、PowerManagerService、DisplayManagerService核心服务启动后,会通过调用PackageManagerService#main方法将PMS 启动
#startBootstrapServices
- 启动Installer服务
- 启动AMS、PowerManagerService、DisplayManagerService系统服务
- 调用PackageManagerService#main方法将PKMS 启动
- 启动MountService
- dex 优化
- 调用PackageManagerService#systemReady 回调方法
当然启动PMS 只是System进程启动后干的很小一部分工作,还有其他很多系统服务也会被启动,而且很多启动流程都大同小异。
private void startBootstrapServices() {
//启动Installer服务
Installer installer = mSystemServiceManager.startService(Installer.class);
...
// Activity manager runs the show.
mActivityManagerService = mSystemServiceManager.startService(
ActivityManagerService.Lifecycle.class).getService();
mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
mActivityManagerService.setInstaller(installer);
//处于加密状态则仅仅解析核心应用
String cryptState = SystemProperties.get("");
if (ENCRYPTING_STATE.equals(cryptState)) {
mOnlyCore = true; // ENCRYPTING_STATE = "trigger_restart_min_framework"
} else if (ENCRYPTED_STATE.equals(cryptState)) {
mOnlyCore = true; // ENCRYPTED_STATE = "1"
}
//创建 PMS 对象
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
//PMS是否首次启动
mFirstBoot = mPackageManagerService.isFirstBoot();
mPackageManager = mSystemContext.getPackageManager();
//...
}
2、调用PKMS的回调函数systemReady
private void startOtherServices() {
...
//启动 MountService,后续 PackageManager 会需要使用
mSystemServiceManager.startService(MOUNT_SERVICE_CLASS);
//dex 优化工作,dex 是 Android 针对 Java 字节码的一种优化技术,可提高运行效率
mPackageManagerService.performBootDexOpt();
//调用PKMS的回调函数,其他系统服务启动后也会有类似的操作
mPackageManagerService.systemReady();
//...
}
其他系统服务启动后也会有类似的操作。
三、PKMS 遍历安装系统目录下的App
Android 中对于App的权限管理是在Linux权限基础上的,一般说来,每一个进程都会有一个对应的 UID(即表示该进程属于哪个 user,不同 user 有不同权限),一个进程也可分属不同的用户组(每个用户组都有对应的权限)。
- UID——用户ID,USERID的缩写。
- GID ——用户组 ID,USERGROUPID 的缩写
这两个概念均与 Linux 系统中进程的权限管理有关。
1、Apk的结构
APK本质上也是一个压缩类型的文件,由以下部分组成:
目录/文件 | 说明 |
---|---|
assert | 存放的原始资源文件不会被编译,通过Asset Manager类访问 |
res | 存放资源文件,res中除了raw子目录,其他的子目录都参与编译,这些子目录下的资源是通过编译出的R类在代码中访问。 |
META-INF | 保存应用的签名信息 |
lib | 存放库文件 |
Android Manifest. xml | 清单文件,用于声明应用程序的包名称、版本、组件和权限等数据,因为经过压缩,需要使用AXMLPrinter2工具解开。 |
classes. dex | Java源码编译后生成的dex字节码文件(Android虚拟机定制化的字节码文件) |
resources. arsc | 编译后的二进制资源文件,保存着资源和ID的映射关系。 |
2、PackageManagerService#main方法触发App 安装流程
-
通过PackageManagerService构造对应的对象(Binder对象)
-
把Binder对象注册到Java层 ServiceManager中,注册完毕之后系统中其他需要使用到PackageManagerService都可以通过ServiceManager#getService方法来来获取对应的Binder对象,Binder 服务的常规流程,主要你想让你的Binder的对象可以被ServiceManager 获取到就都可以注册到ServiceManager里。
所谓注册到ServiceManager 里其实就是在ServiceManager里维护了一个Map对象,注册时传入第一个参数为key,第二个参数为binder对象,这就有点类似对象池的设计思想。
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// Self-check for initial settings.
PackageManagerServiceCompilerMapping.checkProperties();
PackageManagerService m = new PackageManagerService(context, installer,factoryTest, onlyCore);
m.enableSystemUserPackages();
ServiceManager.addService("package", m);
return m;
}
一句话总结就是,创建PKMS对象并注册到ServiceManager Binder 服务管理中心。
3、构造PackageManagerService 对象
回到构造PMKS 对象方法内部,继续往下分析,涉及到以下的类
- \frameworks\base\services\core\java\com\android\server\pm\
- \frameworks\base\services\core\java\com\android\server\pm\
3.1、保存应用的安装和运行信息
Android 系统每次启动后都需要把原来安装过的App 全部还原回来(因为不可能每一次都执行全新的安装,这样会丢失掉用户信息),而且对于应用程序的用户ID来说,每一次重启后,有可能不尽相同,如果不还原回来可能会导致不可知的权限安全问题。因此PKMS 每次在安装完成应用之后,都会将其对应的信息保存起来,供下次还原使用,而这些信息就是保存到类里。
省略掉一些不重要的步骤:
3.1.1、
final class Settings {
final ArrayMap<String, SharedUserSetting> mSharedUsers =new ArrayMap<String, SharedUserSetting>();
private final ArrayList<Object> mUserIds = new ArrayList<Object>();
private final SparseArray<Object> mOtherUserIds =new SparseArray<Object>();
...
此Settings 非我们比较熟悉的系统设置中的Settings,这里的Settings 只是相当于个JavaBean对象。暂时先关注Settings 中的成员变量:
- mUserIds——ArrayList 类型的 mUserIds保存已经分配给普通用户使用的UID(普通应用UID),如果第index 的值不为null则说明值为Process.FIRST_APPLICATION_UID+index的UID 已经被分配使用了。
- mOtherUserIds——SparseArray类型的保存已经分配给特权用户使用的UID(系统应用的UID),可以 UID 为索引找到对应的 SharedUserSettings 对象。(并非直接把UID作为索引)
- mSharedUsers ——Settings 的成员变量有一个类型为SharedUserSetting 的 mSharedUsers 该成员存储的是字符串与 SharedUserSetting 键值对。
SharedUserSetting (继承自 GrantedPermissions 类,SharedUserSetting 定义了一个成员变量 packages,类型为 HashSet,用于保存声明了相同 sharedUserId 的 Package 的权限设置信息和我们在清单文件里android:sharedUserId,每个 Package 有自己的权限设置 PackageSetting (继承自PackagesettingBase,而PackagesettingBase又继承自GrantedPermissions ) 关系密切,当解析到这个属性是说明
-
同一种 sharedUserId 的 不同的App 可共享彼此的数据且也可运行在同一进程中。
-
通过在清单文件中指定对应的 sharedUserId,可以使得 App 所在进程将被赋予指定的 UID。例如在应用清单里配置了
android:sharedUserId=""
属性,如果该App的签名也是使用platform级别的签名证书(即在其 中需要额外声明 LOCAL_CERTIFICATE := platform)时,相当于是把该进程的UID设置为system的UID,也就拥有了系统权限。
作用就在于在 Android 系统中,多个应用(package) 通过在清单设置 android:sharedUserId 属性可以运行在同一个进程,共享同一个 UID,拥有同样的权限。
虽然保存在Setting 两个成员变量集合的对象都是Object,实际保存的对象要么是PackageSetting,要么是SharedUserSetting,区别在于前者锁描述的Linux UID是独立的;而后者是共享的。
3.1.2、创建Settings 对象备用,接着把系统App的部分信息封装到SharedUserSetting 对象保存到Settings中
而这里通过Setting#addSharedUserLPw
方法把这些信息先缓存起来
//传入的形式是 "" 作为key 系统进程使用的用户id的值为 1000 包的标志
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) {
//mSharedUsers 是Settings的成员变量,本身是一个 ArrayMap,以name为key,SharedUserSetting 对象为值
SharedUserSetting s = mSharedUsers.get(name);
...
//创建一个新的 SharedUserSettings 对象,并设置的 userId 为 uid
s = new SharedUserSetting(name, pkgFlags);
s.userId = uid;
if (addUserIdLPw(uid, s, name)) {
mSharedUsers.put(name, s);//将name与s键值对添加到mSharedUsers中保存
return s;
}
}
然后进入到Settings#addUserIdLPw
方法真正把信息存储并在系统中保留一个指定的Linux用户UID并在对应的位置存入SharedUserSetting
- 应用的进程名称就是package 名称
- 系统应用所在的进程的UID <10000,虽然小于10000 的Linux 用户ID不能单独做为UID 给普通应用使用,但是可以通过共享的方式被应用使用,比如说一个应用想要修改系统设置,那么需要在清单文件配置了
android:sharedUserId=""
。 - 普通应用的进程的UID>=10000
private boolean addUserIdLPw(int uid, Object obj, Object name) {
//应用所在进程的uid范围在 19999>UID>=10000
if (uid > Process.LAST_APPLICATION_UID) {
return false;
}
//
if (uid >= Process.FIRST_APPLICATION_UID) {
int N = mUserIds.size();
//索引=uid-10000
final int index = uid - Process.FIRST_APPLICATION_UID;
while (index >= N) {
mUserIds.add(null);
N++;
} //判断该索引位置的内容是否为空,为空才保存
if (mUserIds.get(index) != null) {
PackageManagerService.reportSettingsProblem(Log.ERROR,"Adding duplicate user id: " + uid+ " name=" + name);
return false;
}//保存普通应用进程的 UID
mUserIds.set(index, obj);
} else {
if (mOtherUserIds.get(uid) != null) {
PackageManagerService.reportSettingsProblem(Log.ERROR,"Adding duplicate shared id: " + uid+ " name=" + name);
return false;
}//保存系统应用进程的 UID
mOtherUserIds.put(uid, obj);
}
return true;
}
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
//创建屏幕显示信息对象
mMetrics = new DisplayMetrics();
// 初始化Settings对象备用,
mSettings = new Settings(mPackages);
//创建 SharedUserSetting 对象并添加到 Settings,把这些系统应用的uid 保存起来
mSettings.addSharedUserLPw("", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("", RADIO_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("", LOG_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
...
String separateProcesses = SystemProperties.get("debug.separate_processes");
if (separateProcesses != null && separateProcesses.length() > 0) {
if ("*".equals(separateProcesses)) {
mDefParseFlags = PackageParser.PARSE_IGNORE_PROCESSES;
mSeparateProcesses = null;
Slog.w(TAG, "Running with debug.separate_processes: * (ALL)");
} else {
mDefParseFlags = 0;
mSeparateProcesses = separateProcesses.split(",");
Slog.w(TAG, "Running with debug.separate_processes: "
+ separateProcesses);
}
} else {
mDefParseFlags = 0;
mSeparateProcesses = null;
}
mInstaller = installer;
mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,"*dexopt*");
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
mOnPermissionChangeListeners = new OnPermissionChangeListeners(
FgThread.get().getLooper());
getDefaultDisplayMetrics(context, mMetrics);
当在Java层调用()方法时,首先会触发Looper的构造方法并在构造方法里new 创建MessageQueue,然后把Looper 保存到ThreadLocal 类型的静态成员变量中,以一个线程局部变量或者线程单例,每一个创建了MessgeQueue 的应用线程(即调用了prepare方法)都在里面有一个对应的Looper 对象,可以通过get方法或者Looper类的静态方法myLooper 就可以得到这个关联的唯一对象,至此Java 层的Looper 和MessageQueue对象创建完毕。
// () will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by
final MessageQueue mQueue;
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
2、new MessageQueue(quitAllowed)触发Native 层 MessageQueue 的创建
2.1、Java 层创建MessageQueue 对象触发Native 层NativeMessageQueue的创建
在MessageQueue的构造方法执行时除了Java层的构造工作还通过JNI 调用nativeInit 函数触发Native 层MessageQueue的创建。
private long mPtr; // 保存Native 层对应的MessageQueue对象的指针(地址)
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
//这是通过动态注册JNI的
private native static long nativeInit();
进入到JNI函数,调用Native层的NativeMessageQueue 类的构造函数创建NativeMessageQueue对象,并增加其引用计数,并将NativeMessageQueue指针mPtr保存在Java层的MessageQueue中。
frameworks/base/core/jni/android_os_MessageQueue.cpp
- 创建native消息队列NativeMessageQueue
- 把NativeMessageQueue指针强转成long类型并返回到java层
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
//创建native消息队列NativeMessageQueue
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
...
//增加引用计数,与Android的智能指针相关
nativeMessageQueue->incStrong(env);
//使用C++强制类型转换符reinterpret_cast把NativeMessageQueue指针强转成long类型并返回到java层
return reinterpret_cast<jlong>(nativeMessageQueue);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j9Je7PNb-1662128360769)(assets/)]
frameworks/base/core/jni/android_os_MessageQueue.h
#include "" #include <utils/> namespace android { class MessageQueue : public virtual RefBase { public: /* Gets the message queue's looper. */ inline sp<Looper> getLooper() const { return mLooper; } ... }
2.2、NativeMessageQueue 的构造函数里创建Native层的Looper对象
Java层的Looper 和 Native层的Looper联系不大,但功能大同小异。
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
先调用Looper的getForThread方法从当前线程获取Looper对象,如果为空,就会创建一个Looper并调用Looper的setForThread方法设置给当前线程。
相当于是Java 层实现的ThreadLocal机制
2.3、Native层 Looper对象构造方法初始化epoll相关
- 构造mWakeEventFd作为唤醒事件的FD
- rebuildEpollLocked()重建epoll事件,建立起epoll机制,通过epoll机制监听各种文件描述符。
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
//记住这货,唤醒事件的fd(文件描述符)
mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
...
rebuildEpollLocked();
}
2.4、Native层 Looper对象rebuildEpollLocked函数执行epoll相关
/system/core/libutils/
首先关闭旧的管道(Pipe),然后通过epoll_create 函数创建epoll实例并注册对应的管道,管道的作用在于当一个线程没有新消息需要处理时就会睡眠在这个管道的文件描述符上,直到有新的消息需要处理;当其他线程向这个线程的消息队列发送消息之后,本质上其他线程就是通过这个管道的写端文件描述符往这个管道写入数据从而将这个线程唤醒。
- 创建一个新的epoll实例注册管道,并返回对应点唤醒文件描述符
- 配置监听事件类型和需要监听的文件符
- 遍历实现监听
void Looper::rebuildEpollLocked() {
//关闭旧的管道
if (mEpollFd >= 0) {
close(mEpollFd);
}
//创建一个新的epoll文件描述符,并注册wake管道
mEpollFd = epoll_create(EPOLL_SIZE_HINT);//EPOLL_SIZE_HINT为8
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); //c++ 常规操作,先清空,防止内存被污染
//设置监听事件类型和需要监听的文件描述符
= EPOLLIN;//监听可读事件(EPOLLIN),next 方法里有用到
= mWakeEventFd;//设置唤醒事件的fd(mWakeEventFd)
//将唤醒事件fd(mWakeEventFd)添加到epoll文件描述符(mEpollFd),并监听唤醒事件fd(mWakeEventFd)
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
...
//将各种事件,如键盘、鼠标等事件的fd添加到epoll文件描述符(mEpollFd),进行监听
for (size_t i = 0; i < (); i++) {
const Request& request = (i);
struct epoll_event eventItem;
(&eventItem);
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, , & eventItem);
if (epollResult < 0) {
ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
, strerror(errno));
}
}
}
虽然此次创建的epoll实例只是监听了一个文件描述符的I/O事件,但并不意味着此处使用epoll没有必要,因为后续可以通过Native层的Looper的addFD 函数添加更多的FD,比如键盘消息处理机制时就会用到。
epoll机制是Linux最高效的I/O复用机制, 使用一个文件描述符管理多个描述符,实现同时监听多个文件描述符的I/O事件而设计的,如果epoll 监听了大量的FD,但只有少量的FD发生了I/O事件,那么epoll可以显著减少CPU的调用,使用方法如下:
epoll操作过程有3个方法,分别是:
1、int epoll_create(int size):用于创建一个epoll的文件描述符,创建的文件描述符可监听size个文件描述符;
参数介绍:
size:size是指监听的描述符个数2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event): 用于对需要监听的文件描述符fd执行op操作,比如将fd添加到epoll文件描述符epfd;
参数介绍:
epfd:是epoll_create()的返回值
op:表示op操作,用三个宏来表示,分别为EPOLL_CTL_ADD(添加)、EPOLL_CTL_DEL(删除)和EPOLL_CTL_MOD(修改)
fd:需要监听的文件描述符
epoll_event:需要监听的事件,有4种类型的事件,分别为EPOLLIN(文件描述符可读)、EPOLLOUT(文件描述符可写), EPOLLERR(文件描述符错误)和EPOLLHUP(文件描述符断)3、int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout): 等待事件的上报, 该函数返回需要处理的事件数目,如返回0表示已超时;
参数介绍:
epfd:等待epfd上的io事件,最多返回maxevents个事件
events:用来从内核得到事件的集合
maxevents:events数量,该maxevents值不能大于创建epoll_create()时的size
timeout:超时时间(毫秒,0会立即返回)要了解epoll机制,首先要知道,在Linux中,**文件、socket、管道(pipe)**等可以进行IO操作的对象都可以称之为流,既然是IO流,那肯定会有两端:read端和write端,我们可以创建两个文件描述符wiretFd和readFd,对应read端和write端,当流中没有数据时,读线程就会阻塞(休眠)等待,当写线程通过wiretFd往流的wiret端写入数据后,readFd对应的read端就会感应到,唤醒读线程读取数据,大概就是这样的一个读写过程,读线程进入阻塞后,并不会消耗CPU时间,这是epoll机制高效的原因之一。
说了一大堆,我们再回到rebuildEpollLocked方法,rebuildEpollLocked方法中使用了epoll机制,在Linux中,线程之间的通信一般是通过管道(pipe),在rebuildEpollLocked方法中,首先通过epoll_create方法创建一个epoll专用文件描述符(mEpollFd),同时创建了一个管道,然后设置监听可读事件类型(EPOLLIN),最后通过epoll_ctl方法把Looper对象中的唤醒事件的文件描述符(mWakeEventFd)添加到epoll文件描述符的监控范围内,当mWakeEventFd那一端发生了写入,这时mWakeEventFd可读,就会被epoll监听到(epoll_wait方法返回),我们发现epoll文件描述符不仅监听了mWakeEventFd,它还监听了其他的如键盘、鼠标等事件的文件描述符,所以一个epoll文件描述符可以监听多个文件描述符。
至此,native层的MessageQueue和Looper就构建完毕,底层通过管道与epoll机制也建立了一套消息机制。
四、Looper循环读取消息并处理的流程
1、Looper#loop方法开启无限循环流程
Looper的循环是从方法在ActivityThread#main方法里最后被调用开启的
- 获取Looper对象
- Message msg = (); 遍历读取MessageQueue的消息,如果没有消息则会block
- (msg); 触发Handler的dispatchMessage方法
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();//获取Looper对象
if (me == null) {
throw new RuntimeException("No Looper; () wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;//获取Looper的MessageQueue成员
...
for (;;) {
Message msg = queue.next(); // might block 遍历
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
// 的类型为Handler
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();
}
}
2、MessageQueue#next 方法读取下一个Message
在方法执行完毕之后,就获取Looper的MessageQueue 成员变量,并调用通过next方法遍历MessageQueue,并通过JNI调用epoll 机制去判断和读写消息,有消息则继通过遍历Message链表拿到消息Message:首先获取该消息所描述的处理时间:
- 如果小于当前系统时间则意味着需要马上处理,马上通过return 返回到方法中去调用Handler的dispatchMessage方法进行处理。
- 如果大于于当前系统时间则意味着延时处理,继续遍历寻找下一个需要处理的消息(消息队列中的消息根据处理时间从小到大依次排列的)
- 当没有消息处理时就会遍历等待下一个消息,在进入到睡眠状态时,当前线程会自己分发一个线程空闲消息给那些已经注册到MessageQueue的IdleHandler 对象进行处理,IdleHandler处理完毕之后重新返回遍历是否有新的消息需要处理。
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit which is not supported.
final long ptr = mPtr;//native 层MessageQueue的指针(地址)
if (ptr == 0) {
return null;
}
// -1 only during first iteration 用来保存注册到消息队列中的空闲消息处理器(IdleHandler)的个数
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
//触发去处理那些正等待处理的Binder 进程间的通信请求
Binder.flushPendingCommands();
}
//JNI 调用native层 检查是否有消息需要处理,没有则进入睡眠态
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 的类型为Handler
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.执行到这里时说明线程进入线程空闲状态,即将睡眠了,而且线程空闲消息处理器还未执行
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();//获取线程空闲消息处理器个数
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
//复制注册到线程空闲处理器
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again. 没有注册空闲处理器或者已经发送过一个线程空闲处理消息了,不能重复发送
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
2.1、ptr=mPtr
把native 层的MessageQueue的指针赋值到局部变量备用
2.2、pendingIdleHandlerCount
用于用来保存注册到消息队列中的空闲消息处理器(IdleHandler)的个数,遍历时发现MessageQueue 没有新的消息Message 需要处理时,线程不是马上进入睡眠等待状态,而是先调用注册到它MessageQueued中 IdleHandler对象的quequeIdle方法(可以供线程空闲时执行一些操作),后续在MessageQueue的处理流程再行详细分析。
2.3、nextPollTimeoutMillis
用于描述当MessageQueue 没有新的消息需要处理时,当前线程需要进入睡眠等待的时间(只能取0 或者 -1):
- 取值为0——表示即使当前MessageQueue中没有消息需要处理,当前线程也不要进入到睡眠等待状态。
- 取值为-1——表示当前MessageQueue中没有消息需要处理时,当前线程需要进入到睡眠等待状态,直到被其他线程唤醒。
3、无限循环JNI调用nativePollOnce函数触发检查是否有新的消息需要处理
其native 层的实现对应NativeMessageQueue#android_os_MessageQueue_nativePollOnce函数,是通过动态注册JNI 和Java层关联起来的。
/**
*@param obj Java层调用 JNI方法的对象即MessageQueue对象实例
*@param ptr NativeMessageQueue 对象的指针由前面创建Java 层的MessageQueue时顺便创建了NativeMessageQueue而得
*
*/
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
4、调用NativeMessageQueue对象的pollOnce 函数
把传入的ptr 指针转为NativeMessageQueue 对象的指针,接下来就是通过指针调用NativeMessageQueue对象的pollOnce 函数。
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL;
}
}
5、里的pollInner函数真正通过epoll去检查是否有新消息需要处理
接着调用**\system\core\libutils\里的pollOnce函数**
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
while (mResponseIndex < ()) {
const Response& response = (mResponseIndex++);
int ident = ;
if (ident >= 0) {
...
return ident;
}
}
if (result != 0) {
...
return result;
}
result = pollInner(timeoutMillis);
}
}
省略掉epoll相关的逻辑,最后是调用**\system\core\libutils\里的pollInner函数**检测是否有消息需要处理的
- mEpollFd——创建NativeMessageQueue时new出来的epoll实例对应的FD
- epoll_wait——epoll 机制,通过通过epoll_wait函数实现监听mEpollFd 对应的fd的I/O 事件,如果对应的FD 没有发生I/O事件,则当前线程会在函数中epoll_wait进入到内核层睡眠状态,等待的时间由参数timeoutMillis(即传入的nextPollTimeoutMillis )值决定
- 遍历FD并检查当前线程相关的FD是否发生了类型为EPOLLING的I/O事件,是则说明有其他线程向当前线程发送了消息,需要通过awoken函数唤醒当前线程读取消息。
int Looper::pollInner(int timeoutMillis) {
...
// Poll.
int result = POLL_WAKE;
();
mResponseIndex = 0;
// We are about to idle.
mPolling = true;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
/**
* mEpollFd : epoll实例对应的fd,是在创建NativeMessageQueue时new出来的
* 通过epoll_wait函数实现监听mEpollFd 对应的fd的I/O 事件
*/
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// No longer idling.
mPolling = false;
// Acquire lock.
();
...
//循环遍历,检查是哪个FD 发生了I/O
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].;
uint32_t epollEvents = eventItems[i].events;
//如果发生I/O事件的FD是与当前线程所关联的
if (fd == mWakeEventFd) {
//且事件类型为EPOLLIN,则说明其他线程向当前线程所关联的FD 写入了新数据,即有新消息需要被处理
if (epollEvents & EPOLLIN) {
awoken();//调用awoken 唤醒当前线程继续往下执行读取关联的数据
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
}
} else {
...
}
}
...
// Invoke pending message callbacks.
mNextMessageUptime = LLONG_MAX;
while (() != 0) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
const MessageEnvelope& messageEnvelope = (0);
if ( <= now) {
// Remove the envelope from the list.
// We keep a strong reference to the handler until the call to handleMessage
// finishes. Then we drop it so that the handler can be deleted *before*
// we reacquire our lock.
{ // obtain handler
sp<MessageHandler> handler = ;
Message message = ;
(0);
mSendingMessage = true;
();
handler->handleMessage(message);
} // release handler
();
mSendingMessage = false;
result = POLL_CALLBACK;
} else {
// The last message left at the head of the queue determines the next wakeup time.
mNextMessageUptime = ;
break;
}
}
// Release lock.
();
// Invoke all response callbacks.
for (size_t i = 0; i < (); i++) {
Response& response = (i);
if ( == POLL_CALLBACK) {
int fd = ;
int events = ;
void* data = ;
...
// Invoke the callback. Note that the file descriptor may be closed by
// the callback (and potentially even reused) before the function returns so
// we need to be a little careful when removing the file descriptor afterwards.
int callbackResult = ->handleEvent(fd, events, data);
if (callbackResult == 0) {
removeFd(fd, );
}
// Clear the callback reference in the response structure promptly because we
// will not clear the response vector itself until the next poll.
();
result = POLL_CALLBACK;
}
}
return result;
}
6、里的awoken函数去读取其他线程发送过来的消息
void Looper::awoken() {
uint64_t counter;
TEMP_FAILURE_RETRY(read(mWakeEventFd, &counter, sizeof(uint64_t)));
}
至此,Looper消息循环读取、处理消息流程完毕。
四、其他线程通过Handler发送消息的流程
在Android中我们是可以在任意线程通过Handler 向一个线程的消息队列发送消息,实现线程间的通信,接下来我们看看Handler是怎么把Looper 和MessageQueue “勾搭”上的,首先Handler自然是需要持有对应的引用
public class Handler {
private static Handler MAIN_THREAD_HANDLER = null;
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
final boolean mAsynchronous;
IMessenger mMessenger;
...
1、构造Handler对象
- () 创建Looper和对应的MessageQueue
- 设置回调处理接口
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
...
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
2、Handler#sendMessageAtTime方法触发消息发送
Handler 发送消息有很多个重载方法,最终都是先调用sendMessageAtTime方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
...
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
3、#enqueueMessage 把消息存入MessageQueue
本质上Handler 发送消息之后就是要插入到MessageQueue 的mMessages成员变量,即消息入队操作就是对链表进行相关的插入操作,不过需要遵守一个总则:消息队列里的消息总是按其对应的处理时间从小到大的顺序进行排列的,当接到消息时首先是要根据处理时间找到它对应的位置再插入到消息队列中,通常有四种情况:
消息队列是从头部开始对消息入队的
- 当前消息队列是一个空的,发送的消息对象直接成为“链表”的新头部节点。
- 要插入的消息的处理时间为0,则说明该消息需要马上被处理,优先级最高,因此只有插在头部才能被最先处理。
- 插入的消息的处理时间小于当前队列中头部节点消息的,也需要插在头部
- 插入的消息的处理时间大于当前队列中头部节点消息的,则需要遍历判断,再插入在头部之后的某个位置。
boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {//判断消息是否处理过,避免重复处理
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
...
msg.markInUse();
msg.when = when;
Message p = mMessages;//即将入队的链表引用
boolean needWake;//标识需要通过nativeWake方法唤醒目标线程
//没有消息需要处理,直接插在链表头部
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//遍历链表
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p ==
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
于是乎,总结下来插入的情况就分为两大类:
3.1、在当前链表的头部插入的新消息,可能需要通过JNI 调用nativeWake方法唤醒
3.1.1、NativeMessageQueue#wake() 函数触发唤醒流程
由于保存在消息队列头部的消息发生了变化,目标线程需要来处理新消息,但是有可能目标线程已经处于睡眠状态了(主要就是通过mBlocked来记录是否处于睡眠阻塞状态,true则为阻塞),所以当前线程需要通过JNI 调用 nativeWake 方法先唤醒目标线程。
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
3.1.2、NativeMessageQueue#wake() 函数——># wake()
void NativeMessageQueue::wake() {
mLooper->wake();
}
3.1.3、# wake()函数真正通过epoll机制唤醒
向关联的fd写入数据,导致目标线程因为产生写事件,而前面epoll实例已被创建并且监听了所有的FD,目标FD 产生I/O事件且类型的EPOLLING最终被唤醒。
\system\core\libutils\
void Looper::wake() {
uint64_t inc = 1;
//向关联的fd写入数据,导致目标线程因为产生写事件而被唤醒
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
ALOGW("Could not write wake signal: %s", strerror(errno));
}
}
}
3.2、在当前链表的非头部(中间、尾部)插入的新消息,无需唤醒目标线程
至此,消息的发送流程完毕。
五、线程消息的处理流程
前面说到当App进程启动后就会通过Native 层的Looper#pollInner函数检查是否有消息需要处理,如果当一个线程没有新消息需要被处理且IdleHandler处理完毕后,就会由pollInner去通过epoll_wait函数使得当前线程进入睡眠状态,直到有新消息需要处理时,才再次从native层pollInner 函数中唤醒并逐步返回到最初的Java调用层方法里,每一次返回消息就会去调用其Handler类型的成员变量的dispatchMessage方法
public static void loop() {
final Looper me = myLooper();//获取Looper对象
final MessageQueue queue = me.mQueue;//获取Looper的MessageQueue成员
...
for (;;) {
Message msg = queue.next(); // might block 遍历
if (msg == null) {
return;
}
...
try {
// 的类型为Handler
msg.target.dispatchMessage(msg);
}
...
}
}
1、#dispatchMessage方法处理消息
Handler#post 方法到底运行于何种线程之上,是取决于是MainHandler 还是子线程的Handler,如果是MainHandler 则运行主线程,反之则是运行于子线程。
首先得是通过Handler 向目标线程发送了Message然后才会触发dispatchMessage方法,有两条执行分支:
1.1、Message里配置了callback则由handleCallback方法处理
当Message里的call不为空时,即传递了一个Runnable,那么Handler接到对应的消息之后,就会去执行这个传入Runnable接口里实现的run方法,完成处理。
1.2、Message的callback为null,则由handleMessage方法处理
当Message里的call为空时,那么Handler接到对应的消息之后,首先去判断Handler自身的mCallback 接口是否为空,如果不为空则先去调用mCallback接口里的handleMessage方法,返回true则完成,否则继续往下执行Handler自身的handleMessage方法;反之mCallback为空则跳过mCallback回调接口里的handleMessage方法直接执行Handler自身的handleMessage方法。
public final class Message implements Parcelable {
public int what;
/**
* arg1 and arg2 are lower-cost alternatives to using
* {@link #setData(Bundle) setData()} if you only need to store a
* few integer values.
*/
public int arg1;
public int arg2;
/**
* Optional Messenger where replies to this message can be sent. The
* semantics of exactly how this is used are up to the sender and
* receiver.
*/
public Messenger replyTo;
/*package*/ long when;
/*package*/ Bundle data;
/*package*/ Handler target;
/*package*/ Runnable callback;
// sometimes we store linked lists of these things
/*package*/ Message next;
....
}
public class Handler {
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
final boolean mAsynchronous;
IMessenger mMessenger;//Message是通过IMessenger 实现IPC传送的
public void dispatchMessage(Message msg) {
// 就是一个Runnable接口,可以在Message里携带Runnable接口
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
public interface Callback {
public boolean handleMessage(Message msg);
}
public void handleMessage(Message msg) {
}
...
}
至此,消息的整个处理流程完毕,它们都是需要先被其他线程发送到目标线程的消息队列中,然后再去读取处理。
2、IdleHandler 处理线程空闲消息
2.1、线程空闲消息处理接口
除了上面所说的普通消息,需要先被发送到消息队列然后才能被处理,还有一种是由线程在空闲时后自己主动“发送”的所谓线程空闲消息,Android 中是由一种实现了MessageQueue里的IdleHandler 接口的消息处理器专门来处理此种消息。
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
IdleHandler接口里只有一个方法queueIdle()返回boolean值:
- 如果返回 true,那么执行完后不会被删除,只要执行 时消息队列中没有可执行的消息(即空闲时间),那么 IdleHandler 队列中的 IdleHandler 接口的方法还会继续被执行。
- 返回false,执行完就会删除
2.2、#addIdleHandler方法给线程注册到空闲消息处理器
首先空闲消息处理器想要接收到一个线程的线程空闲消息,首先得通过#addIdleHandler方法注册到MessageQueue的mIdleHandlers 成员变量(是一个ArrayList < mIdleHandlers >
链表)之中,所谓注册其实就是add到List集合之中。
public void addIdleHandler(@NonNull IdleHandler handler) {
...
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
反注册也是类似,都是对ArrayList的操作。
2.3、线程空闲时触发queueIdle方法的执行
一个线程在进行消息循环时,有时候会变得“无事可做”,发生两种情况时:
- 当线程对应的MessageQueue 为空时
- 当前MessageQueue 的列头的Message 处理时间大于系统当前时间时
当以上任意一种情况发生时就认为进入到了所谓线程空闲时间,接下来它会很快进入到睡眠状态,不过睡眠之前线程自己会先“发出“一个线程空闲消息给那些注册了线程处理器的进行处理,本质上就是遍历集合调用对应的回调方法,从上文next方法可知,每一次next 方法的执行,一个线程至多只会发出一个线程空闲消息。最后通过注册空闲消息处理器,我们可以把一些不重要的事情放到线程空闲时来执行,达到充分利用线程空闲时间的目的。用法很简单:
- 获取Looper
- 再获取Looper的MessageQueue
- 调用MessageQueue的addIdleHandler注册空闲消息处理器
- 记得反注册保持良好习惯
().addIdleHandler(new IdleHandler(){
@Override
public boolean queueIdle(){
// TODO Auto-generated method stub
//注意不要在Main线程的这个空闲时间里做耗时的事
return false;
}
});
3、IdleHandler的 应用
Android项目中都会遇到希望一些操作延迟一点处理,一般会使用(Runnable r, long delayMillis)来实现,但是又不知道该延迟多少时间比较合适,因为手机性能不同,有的性能较差可能需要延迟较多,有的性能较好可以允许较少的延迟时间。之前在项目中对启动过程进行优化,用到了IdleHandler,它可以在主线程空闲时执行任务,而不影响其他任务的执行。对于多个任务的延迟加载,如果addIdleHandler()调用多次明显不太优雅,而且也不要把所有要延迟的任务都一起放到queueIdle()方法内。根据queueIdle返回true时可以执行多次的特点,可以实现一个任务列表,然后从这个任务列表中取任务执行。
import android.os.Looper;
import android.os.MessageQueue;
import java.util.LinkedList;
import java.util.Queue;
public class DelayTaskDispatcher {
private Queue<Task> delayTasks = new LinkedList<>();
private MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
if (delayTasks.size() > 0) {
Task task = delayTasks.poll();
if (task != null) {
task.run();
}
}
return !delayTasks.isEmpty(); //delayTasks非空时返回ture表示下次继续执行,为空时返回false系统会移除该IdleHandler不再执行
}
};
public DelayTaskDispatcher addTask(Task task) {
delayTasks.add(task);
return this;
}
public void start() {
Looper.myQueue().addIdleHandler(idleHandler);
}
}
//使用系统Runnable接口自定义Task接口
public interface Task extends Runnable {
}
//使用方法
new DelayTaskDispatcher().addTask(new Task() {
@Override
public void run() {
Log.d(TAG, "DelayTaskDispatcher one task");
}
}).addTask(new Task() {
@Override
public void run() {
Log.d(TAG, "DelayTaskDispatcher two task");
}
}).start();
//
IdleHandler它在源码里的使用场景:
比如在ActivityThread中,就有一个名叫GcIdler的内部类,实现了IdleHandler接口。它在queueIdle方法被回调时,会做强行GC的操作(即调用BinderInternal的forceGc方法),但强行GC的前提是,与上一次强行GC至少相隔5秒以上。
ActivityThread中GcIdler
那么我们就看看最为明显的ActivityThread中声明的GcIdler。在ActivityThread中的H收到GC_WHEN_IDLE消息后,会执行scheduleGcIdler,将GcIdler添加到MessageQueue中的空闲任务集合中。具体如下:
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
//添加GC任务
Looper.myQueue().addIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
ActivityThread中GcIdler的详细声明:
//GC任务
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
//执行后,就直接删除
return false;
}
}
// 判断是否需要执行垃圾回收。
void doGcIfNeeded() {
mGcIdlerScheduled = false;
final long now = SystemClock.uptimeMillis();
//获取上次GC的时间
if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
BinderInternal.forceGc("bg");
}
}
GcIdler方法理解起来很简单、就是获取上次GC的时间,判断是否需要GC操作。如果需要则进行GC操作。这里ActivityThread中还声明了其他空闲时的任务。如果大家对其他空闲任务感兴趣,可以自行研究。那这个GcIdler会在什么时候使用呢?当ActivityThread的mH(Handler)收到GC_WHEN_IDLE消息之后。 当AMS(ActivityManagerService)中的这两个方法被调用之后会收到GC_WHEN_IDLE消息:
- doLowMemReportIfNeededLocked, 这个方法看名字就知道是不够内存的时候调用的了。
- activityIdle这个方法呢,就是当ActivityThread的handleResumeActivity方法被调用时(Activity的onResume方法也是在这方法里回调)调用的。
其他使用场景:
- Activity启动优化(加快App启动速度):onCreate,onStart,onResume中耗时较短但非必要的代码可以放到IdleHandler中执行,减少启动时间
- 想要在一个View绘制完成之后添加其他依赖于这个View的View,当然这个用View#post()也能实现,区别就是前者会在消息队列空闲时执行
- 发送一个返回true的IdleHandler,在里面让某个View不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作
- 一些第三方库中有使用,比如LeakCanary,Glide中有使用到。
一些关于IDLEHandler的Q&A
- 若在主线程的IdleHandler进行耗时操作,当Thread sleep 超过10 ms时页面会卡死,但不会崩溃,如果页面有动图,则动图变为静态图, 但此时如果点击页面按钮,则会无响应进入anr,如果不点击,n秒过后恢复正常。
- onCreate中MainLooper添加IdleHandler后也是先执行oncreate其他代码,然后执行idleHandler
- 在IdleHandler 里执行IO把文件写入到本地时,执行Sleep 后中途点击页面可能会导致ANR但是不点击就不会有影响。