在详细分析SurfaceFlinger模块之前要先看看Android的开机动画,因为这个BootAnimation是一个C++应用程序,需要使用SurfaceFlinger服务来创建和渲染自己的Surface,并且不涉及与用户进行交互,所以能以最简洁的方式体现Android与SurfaceFlinger服务的关系。
开机动画的启动
第一个开机画面是在内核启动的过程中出现的,它是一个静态的画面。第二个开机画面是在init进程启动的过程中出现的,它也是一个静态的画面。第三个开机画面BootAnimation是在系统服务启动的过程中出现的,它是一个动态的画面。无论是哪一个画面,它们都是在一个称为帧缓冲区(frame buffer,简称fb)的硬件设备上进行渲染的。接下来,我们就分别分析第三个画面是如何在fb上显示的。
bootanimation的启动过程
第三个开机动画使用应用程序BootAnimation来负责显示的,它在启动脚本init.rc中被配置成了一个服务,位于system/core/rootdir/Init.rc:
1 2 3 4 5 6 7 8 |
...... service bootanim /system/bin/bootanimation class core user graphics //用户 group graphics audio //用户组 disabled //init进程启动时,不会自动启动bootanimation oneshot ...... |
应用程序bootanimation的用户和用户组名称分别被设置为graphics。注意, 用来启动应用程序bootanimation的服务是disable的,即init进程在启动的时候,不会主动将应用程序bootanimation启动起来。
当SurfaceFlinger服务启动的时候,它会通过修改系统属性ctl.start的值来通知init进程启动应用程序bootanimation,以便可以显示第三个开机画面。
在早期的Android版本中,SurfaceFlinger服务是由SystemServer启动的。但在Android5.0中,该服务是init进程启动过程中就启动了。在init.rc中能看到对该服务的描述:
1 2 3 4 5 6 7 |
...... service surfaceflinger /system/bin/surfaceflinger class core user system group graphics drmrpc onrestart restart zygote ...... |
SurfaceFlinger服务的源码路径位于framework/native/services/surfaceflinger/下面,服务的入口在下面的main_surfaceflinger.cpp中,我们查看它的main函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
int main(int, char**) { // When SF is launched in its own process, limit the number of // binder threads to 4. ProcessState::self()->setThreadPoolMaxThreadCount(4); // start the thread pool sp<ProcessState> ps(ProcessState::self()); ps->startThreadPool(); // instantiate surfaceflinger sp<SurfaceFlinger> flinger = new SurfaceFlinger(); #if defined(HAVE_PTHREADS) setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY); #endif set_sched_policy(0, SP_FOREGROUND); // initialize before clients can connect flinger->init(); //我们主要看这里,其他忽略 // publish surface flinger sp<IServiceManager> sm(defaultServiceManager()); sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false); // run in this thread flinger->run(); return 0; } |
主要工作就是新建一个SurfaceFlinger对象,然后调用其中的init函数,最后调用run函数。我们的重点在init方法,位于framework/native/services/surfaceflinger/SurfaceFlinger.cpp中:
1 2 3 4 5 |
void SurfaceFlinger::init() { ...省略大量代码... // start boot animation startBootAnim(); } |
最后一行调用了startBootAnim函数,我们继续往下看:
1 2 3 4 5 |
void SurfaceFlinger::startBootAnim() { // start boot animation property_set("service.bootanim.exit", "0"); property_set("ctl.start", "bootanim"); } |
这里讲系统属性”service.bootanim.exit”设置为”0”,并将”ctl.start”设置为”bootanim”。重点是第二个。
当系统属性发生改变时,init进程就会接收到一个系统属性变化通知,这个通知最终是由在init进程中的函数handle_property_set_fd来处理的。我们可以查看init进程的入口main函数,位于system/core/init/Init.c中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
int main(int argc, char **argv){ ...省略大量代码... for(;;) { int nr, i, timeout = -1; ...省略一些代码... //poll函数用来轮询事件 nr = poll(ufds, fd_count, timeout); if (nr <= 0) continue; for (i = 0; i < fd_count; i++) { if (ufds[i].revents & POLLIN) { //我们关注这里,当系统属性值被修改时,得到该事件,会执行handle_property_set_fd函数 if (ufds[i].fd == get_property_set_fd()) handle_property_set_fd(); else if (ufds[i].fd == get_keychord_fd()) handle_keychord(); else if (ufds[i].fd == get_signal_fd()) handle_signal(); } } } return 0; } |
可以看到,init进程会使用poll机制来轮询事件,其中一个事件是系统属性值被修改。得到该事件后,会执行handle_property_set_fd(),位于system/core/init/Property_service.c中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
void handle_property_set_fd() { prop_msg msg; //init进程是通过一个socket来接收系统属性变化事件的 int s; int r; int res; struct ucred cr; struct sockaddr_un addr; socklen_t addr_size = sizeof(addr); socklen_t cr_size = sizeof(cr); char * source_ctx = NULL; struct pollfd ufds[1]; const int timeout_ms = 2 * 1000; /* Default 2 sec timeout for caller to send property. */ int nr; //接收TCP连接 服务端阻塞?等待客户端连接 if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) { return; } /* Check socket options here */ //取出socket的可选内容,可能包括客户端进程的权限等属性 if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) { close(s); ERROR("Unable to receive socket options\n"); return; } ufds[0].fd = s; ufds[0].events = POLLIN; ufds[0].revents = 0; //轮询客户端事件 nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms)); //轮询事件超时 if (nr == 0) { ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid); close(s); return; //等待错误 } else if (nr < 0) { ERROR("sys_prop: error waiting for uid=%d to send property message. err=%d %s\n", cr.uid, errno, strerror(errno)); close(s); return; } //接收socket的主体数据 r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT)); //接收事件不匹配,return if(r != sizeof(prop_msg)) { ERROR("sys_prop: mis-match msg size received: %d expected: %zu errno: %d\n", r, sizeof(prop_msg), errno); close(s); return; } //开始条件判断接收事件 switch(msg.cmd) { case PROP_MSG_SETPROP: //如果是属性发生变化 msg.name[PROP_NAME_MAX-1] = 0; msg.value[PROP_VALUE_MAX-1] = 0; //检查属性名,不能有特殊字符,或者两个点..这样的名字 if (!is_legal_property_name(msg.name, strlen(msg.name))) { ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name); close(s); return; } getpeercon(s, &source_ctx); /* 如果是ctl开头的消息,则认为是控制消息,控制消息用来执行一些命令,例如用adb shell登录后,输入setprop ctl.start bootanim就可以查看开机动画了;输入setprop service.bootanim.exit 1就可以退出开机动画了 */ if(memcmp(msg.name,"ctl.",4) == 0) { // Keep the old close-socket-early behavior when handling // ctl.* properties. close(s); //改变系统属性是需要权限,所以需要减产是否有权限 if (check_control_mac_perms(msg.value, source_ctx)) { //通过了权限检查之后,另外一个函数handle_control_message就会被调用,以便可以执行一个名称为“bootanim”的命令 handle_control_message((char*) msg.name + 4, (char*) msg.value); } else { ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n", msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid); } } else { /* 不是ctl开头的属性,则首先检查其权限。例如,设置net.开头的属性需要AID_SYSTEM权限,log.开头的属性需要AID_SHELL属性等。 */ if (check_perms(msg.name, source_ctx)) { // 最后通过property_set函数设置客户端需要设置的属性 property_set((char*) msg.name, (char*) msg.value); } else { ERROR("sys_prop: permission denied uid:%d name:%s\n", cr.uid, msg.name); } // Note: bionic's property client code assumes that the // property server will not close the socket until *AFTER* // the property is written to memory. close(s); } freecon(source_ctx); break; default: close(s); break; } } |
init进程是通过一个socket来接收系统属性变化事件的。每一个系统属性变化事件的内容都是通过一个prop_msg对象来描述的。在prop_msg对象对,成员变量name用来描述发生变化的系统属性的名称,而成员变量value用来描述发生变化的系统属性的值。系统属性分为两种类型,一种是普通类型的系统属性,另一种是控制类型的系统属性(属性名称以“ctl.”开头)。控制类型的系统属性在发生变化时,会触发init进程执行一个命令,而普通类型的系统属性就不具有这个特性。注意,改变系统属性是需要权限,因此,函数handle_property_set_fd在处理一个系统属性变化事件之前,首先会检查修改系统属性的进程是否具有相应的权限,这是通过调用函数check_control_perms或者check_perms来实现的。
从前面的调用过程可以知道,当前发生变化的系统属性的名称为“ctl.start”,它的值被设置为“bootanim”。由于这是一个控制类型的系统属性,因此,在通过了权限检查之后,另外一个函数handle_control_message就会被调用,以便可以执行一个名称为“bootanim”的命令。
函数handle_control_message实现在system/core/init/Init.c中,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 |
void handle_control_message(const char *msg, const char *arg) { if (!strcmp(msg,"start")) {//所以进入了这里 msg_start(arg);//arg是bootanimation } else if (!strcmp(msg,"stop")) { msg_stop(arg); } else if (!strcmp(msg,"restart")) { msg_restart(arg); } else { ERROR("unknown control msg '%s'\n", msg); } } |
控制类型的系统属性的名称是以”ctl.”开头,并且是以“start”或者“stop”结尾的,其中,“start”表示要启动某一个服务,而“stop”表示要停止某一个服务,它们是分别通过函数msg_start和msg_stop来实现的。由于当前发生变化的系统属性是以“start”来结尾的,因此,接下来就会调用函数msg_start来启动一个名称为“bootanim”的服务。
函数msg_start实现在文件system/core/init/Init.c中,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
static void msg_start(const char *name)//bootanim { struct service *svc = NULL; char *tmp = NULL; char *args = NULL; if (!strchr(name, ':'))//into //查找init.rc中配置的service列表 svc = service_find_by_name(name); else { tmp = strdup(name); if (tmp) { args = strchr(tmp, ':'); *args = '\0'; args++; svc = service_find_by_name(tmp); } } if (svc) {//如果在列表中找到了bootanim,则启动它 service_start(svc, args); } else { ERROR("no such service '%s'\n", name); } if (tmp) free(tmp); } |
该函数首先调用service_find_by_name(),从service_list中查询要启动的服务是否有存在,若存在,返回服务的相关信息。因为init.rc中有bootanimation的定义,因此在init进程执行parse_config()时,会将该服务添加到service_list中,所以bootanimation应用是存在的。然后,如果找到了该服务,就调用service_start启动服务。
到此,bootanimation应用就启动了。
开机动画显示
从前面的内容可以知道,名称等于“bootanim”的服务所对应的应用程序为/system/bin/bootanimation,这个应用程序实现在frameworks/base/cmds/bootanimation目录中,其中,应用程序入口函数main是实现在frameworks/base/cmds/bootanimation/Bootanimation_main.cpp中的,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
int main(int argc, char** argv) { #if defined(HAVE_PTHREADS) setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY); #endif char value[PROPERTY_VALUE_MAX]; property_get("debug.sf.nobootanimation", value, "0"); int noBootAnimation = atoi(value); ALOGI_IF(noBootAnimation, "boot animation disabled"); if (!noBootAnimation) {//检查系统属性“debug.sf.nobootnimaition”的值是否不等于0 //启动一个Binder线程池 sp<ProcessState> proc(ProcessState::self()); ProcessState::self()->startThreadPool(); // create the boot animation object //创建一个BootAnimation对象 sp<BootAnimation> boot = new BootAnimation(); IPCThreadState::self()->joinThreadPool(); } return 0; } |
这个函数首先检查系统属性“debug.sf.nobootnimaition”的值是否不等于0。如果不等于的话,那么接下来就会启动一个Binder线程池,并且创建一个BootAnimation对象。这个BootAnimation对象就是用来显示第三个开机画面的。由于BootAnimation对象在显示第三个开机画面的过程中,需要与SurfaceFlinger服务通信,因此,应用程序bootanimation就需要启动一个Binder线程池。
接着我们看看BootAnimation类的声明,位于frameworks/base/cmds/bootanimation/BootAnimation.h中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class BootAnimation : public Thread, public IBinder::DeathRecipient { public: BootAnimation(); virtual ~BootAnimation(); ....... private: virtual bool threadLoop(); virtual status_t readyToRun(); virtual void onFirstRef(); virtual void binderDied(const wp<IBinder>& who); status_t initTexture(Texture* texture, AssetManager& asset, const char* name); status_t initTexture(const Animation::Frame& frame); bool android(); bool readFile(const char* name, String8& outString); bool movie(); ...... }; |
BootAnimation类继承了Thread类和IBinder::DeathRecipient类,其中几个重要的函数说明如下:
- onFirstRef() —– 属于其父类RefBase,该函数在强引用sp新增引用计数時调用,就是当有sp包装的类初始化的时候调用;
- binderDied() —– 当对象死掉或者其他情况导致该Binder结束时,就会回调binderDied()方法;
- readyToRun() —– Thread执行前的初始化工作;
- threadLoop() —– 每个线程类都要实现的,在这里定义thread的执行内容。这个函数如果返回true,且没有requestExist()没有被调用,则该函数会再次执行;如果返回false,则threadloop中的内容仅仅执行一次,线程就会退出。
其他主要函数的说明如下:
- android() —– 显示系统默认的开机画面;
- movie() —– 显示用户自定义的开机动画。
BootAnimation类成员函数的实现位于frameworks/base/cmds/bootanimation/BootAnimation.cpp。
BootAnimation类间接地继承了RefBase类,并且重写了RefBase类的成员函数onFirstRef,因此,当一个BootAnimation对象第一次被智能指针引用的时,这个BootAnimation对象的成员函数onFirstRef就会被调用:
1 2 3 4 5 6 7 |
void BootAnimation::onFirstRef() { status_t err = mSession->linkToComposerDeath(this); ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err)); if (err == NO_ERROR) { run("BootAnimation", PRIORITY_DISPLAY); } } |
mSession是BootAnimation类的一个成员变量,它的类型为SurfaceComposerClient,是用来和SurfaceFlinger执行Binder进程间通信的,它是在BootAnimation类的构造函数中创建的,如下所示:
1 2 3 4 |
BootAnimation::BootAnimation() : Thread(false), mZip(NULL) { mSession = new SurfaceComposerClient(); } |
SurfaceComposerClient类内部有一个实现了ISurfaceComposerClient接口的Binder代理对象mClient,这个Binder代理对象引用了SurfaceFlinger服务,SurfaceComposerClient类就是通过它来和SurfaceFlinger服务通信的。这个我们后面章节会详细分析。
回到BootAnimation类的成员函数onFirstRef中,由于BootAnimation类引用了SurfaceFlinger服务,因此,当SurfaceFlinger服务意外死亡时,BootAnimation类就需要得到通知,这是通过调用成员变量mSession的成员函数linkToComposerDeath来注册SurfaceFlinger服务的死亡接收通知来实现的。
BootAnimation类继承了Thread类,因此,当BootAnimation类的成员函数onFirstRef调用了父类Thread的成员函数run之后,系统就会创建一个线程,这个线程在第一次运行之前,会调用BootAnimation类的成员函数readyToRun来执行一些初始化工作,后面再调用BootAnimation类的成员函数htreadLoop来显示第三个开机画面。
BootAnimation类的成员函数readyToRun的实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
status_t BootAnimation::readyToRun() { mAssets.addDefaultAssets(); //检查显示屏信息是否正确 sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay( ISurfaceComposer::eDisplayIdMain)); DisplayInfo dinfo; status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo); if (status) return -1; // create the native surface //调用SurfaceComposerClient对象mSession的成员函数createSurface可以获得一个SurfaceControl对象control sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"), dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565); SurfaceComposerClient::openGlobalTransaction(); control->setLayer(0x40000000); SurfaceComposerClient::closeGlobalTransaction(); //调用SurfaceControl对象control的成员函数getSurface会返回一个Surface对象s sp<Surface> s = control->getSurface(); // initialize opengl and egl //初始化OPENEGL和EGL const EGLint attribs[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_DEPTH_SIZE, 0, EGL_NONE }; EGLint w, h, dummy; EGLint numConfigs; EGLConfig config; EGLSurface surface; EGLContext context; EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(display, 0, 0); eglChooseConfig(display, attribs, &config, 1, &numConfigs); surface = eglCreateWindowSurface(display, config, s.get(), NULL); context = eglCreateContext(display, config, NULL, NULL); eglQuerySurface(display, surface, EGL_WIDTH, &w); eglQuerySurface(display, surface, EGL_HEIGHT, &h); if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) return NO_INIT; mDisplay = display; mContext = context; mSurface = surface; mWidth = w; mHeight = h; mFlingerSurfaceControl = control; mFlingerSurface = s; // If the device has encryption turned on or is in process // of being encrypted we show the encrypted boot animation. //如果设备加密功能开启了,就要显示加密的开机动画 char decrypt[PROPERTY_VALUE_MAX]; property_get("vold.decrypt", decrypt, ""); bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt); //选取动画文件 ZipFileRO* zipFile = NULL; if ((encryptedAnimation && (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) && ((zipFile = ZipFileRO::open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE)) != NULL)) || ((access(OEM_BOOTANIMATION_FILE, R_OK) == 0) && ((zipFile = ZipFileRO::open(OEM_BOOTANIMATION_FILE)) != NULL)) || ((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) && ((zipFile = ZipFileRO::open(SYSTEM_BOOTANIMATION_FILE)) != NULL))) { mZip = zipFile; } return NO_ERROR; } |
readyToRun主要做了如下工作:
1.
BootAnimation类的成员函数session用来返回BootAnimation类的成员变量mSession所描述的一个SurfaceComposerClient对象。通过调用SurfaceComposerClient对象mSession的成员函数createSurface可以获得一个SurfaceControl对象control。
SurfaceComposerClient类的成员函数createSurface首先调用内部的Binder代理对象mClient(frameworks/native/services/surfaceflinger/Client.cpp)来请求SurfaceFlinger返回一个类型为(class Handle : public BBinder, public LayerCleaner)Binder代理对象(封装了SurfaceFlinger的sp指针和Layer对象)handle,和一个IGraphicBufferProducer的sp指针(封装了SurfaceFlinger的sp指针)gbp,接着再使用这两个对象来创建一个SurfaceControl对象。创建出来的SurfaceControl对象的成员变量handle就指向了从SurfaceFlinger返回来的类型为Handle 的Binder代理对象。有了这个Binder代理对象之后,SurfaceControl对象就可以和SurfaceFlinger服务通信了。(下一章节会分析)
调用SurfaceControl对象control的成员函数getSurface会返回一个Surface对象s。这个Surface对象s内部也有一个类型为IGraphicBufferProducer的sp指针mGraphicBufferProducer,这个sp指针与前面所创建的SurfaceControl对象control的内部的sp指针(封装了SurfaceFlinger的sp指针)gbp引用的是同一个对象。这样,Surface对象s也可以通过其内部的sp指针mGraphicBufferProducer来和SurfaceFlinger服务通信。(下一章节会分析)
Surface类继承了ANativeWindow类。ANativeWindow类是连接OpenGL和Android窗口系统的桥梁,即OpenGL需要通过ANativeWindow类来间接地操作Android窗口系统。这种桥梁关系是通过EGL库来建立的,所有以egl为前缀的函数名均为EGL库提供的接口。
2.
为了能够在OpenGL和Android窗口系统之间的建立一个桥梁,我们需要一个EGLDisplay对象display,一个EGLConfig对象config,一个EGLSurface对象surface,以及一个EGLContext对象context,其中,EGLDisplay对象display用来描述一个EGL显示屏,EGLConfig对象config用来描述一个EGL帧缓冲区配置参数,EGLSurface对象surface用来描述一个EGL绘图表面,EGLContext对象context用来描述一个EGL绘图上下文(状态),它们是分别通过调用egl库函数eglGetDisplay、eglChooseConfig、eglCreateWindowSurface和eglCreateContext来获得的。注意,EGLConfig对象config、EGLSurface对象surface和EGLContext对象context都是用来描述EGLDisplay对象display的。有了这些对象之后,就可以调用函数eglMakeCurrent来设置当前EGL库所使用的绘图表面以及绘图上下文。
还有另外一个地方需要注意的是,每一个EGLSurface对象surface有一个关联的ANativeWindow对象。这个ANativeWindow对象是通过函数eglCreateWindowSurface的第三个参数来指定的。在我们这个场景中,这个ANativeWindow对象正好对应于前面所创建的 Surface对象s。每当OpenGL需要绘图的时候,它就会找到前面所设置的绘图表面,即EGLSurface对象surface。有了EGLSurface对象surface之后,就可以找到与它关联的ANativeWindow对象,即Surface对象s。有了Surface对象s之后,就可以通过其内部的sp指针mGraphicBufferProducer来请求SurfaceFlinger服务返回帧缓冲区硬件设备的一个图形访问接口。这样,OpenGL最终就可以将要绘制的图形渲染到帧缓冲区硬件设备中去,即显示在实际屏幕上。屏幕的大小,即宽度和高度,可以通过函数eglQuerySurface来获得。
3.
动画文件的读取是按顺序进行的,如果读取成功,则不再读取后续的文件,如果失败,则读取下一个文件。顺序如下:
- 如果设备的加密功能已经开启,或者设备正在进行加密,则读取加密开机动画文件,路径为:
1 |
#define SYSTEM_ENCRYPTED_BOOTANIMATION_FILE "/system/media/bootanimation-encrypted.zip" |
- OEM厂商指定的开机动画,路径为:
1 |
#define OEM_BOOTANIMATION_FILE "/oem/media/bootanimation.zip" |
- 系统开机动画,路径为:
1 |
#define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip" |
这一步执行完成之后,用来显示第三个开机画面的线程的初始化工作就执行完成了,接下来,就会执行这个线程的主体函数,即BootAnimation类的成员函数threadLoop。
BootAnimation类的成员函数threadLoop的实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
bool BootAnimation::threadLoop() { bool r; // We have no bootanimation file, so we use the stock android logo // animation. if (mZip == NULL) { r = android(); } else { r = movie(); } eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(mDisplay, mContext); eglDestroySurface(mDisplay, mSurface); mFlingerSurface.clear(); mFlingerSurfaceControl.clear(); eglTerminate(mDisplay); IPCThreadState::self()->stopProcess(); return r; } |
这个函数流程比较简单,首先判断自定义的开机动画文件mZip是否存在,如果存在就调用movie()完成自定义开机画面的显示;如果不存在,调用android()完成系统默认开机画面的显示。然后进行开机动画显示后的销毁、释放工作,主要就是readyToRun中初始化的一些EGL对象。最后终止线程,并return。注意,movie()和android()的返回值都是false,因此线程结束也会返回false。threadLoop()函数如果返回值为false,则该函数中的内容只会执行一次;如果返回true,则会不停的执行。这里返回false,因此只会执行一次。
接下来,我们就分别分析BootAnimation类的成员函数android和movie的实现。
系统默认开机动画android()
BootAnimation类的成员函数android的实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
bool BootAnimation::android() { //读取开机动画默认图片,根据图片创建两个纹理对象 //"android"字样图片 initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png"); //闪光图片 initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png"); // clear screen //清理屏幕 glShadeModel(GL_FLAT); glDisable(GL_DITHER); glDisable(GL_SCISSOR_TEST); glClearColor(0,0,0,1); glClear(GL_COLOR_BUFFER_BIT); eglSwapBuffers(mDisplay, mSurface); glEnable(GL_TEXTURE_2D); glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); //图片在屏幕中现实位置 const GLint xc = (mWidth - mAndroid[0].w) / 2; const GLint yc = (mHeight - mAndroid[0].h) / 2; const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h); glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(), updateRect.height()); // Blend state glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); const nsecs_t startTime = systemTime(); do { //计算每次偏移时间,然后计算出偏移位置 nsecs_t now = systemTime(); double time = now - startTime; float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w; GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w; GLint x = xc - offset; glDisable(GL_SCISSOR_TEST); glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_SCISSOR_TEST); glDisable(GL_BLEND); //绘制闪光图片,这个会根据上面计算的位置来显示 glBindTexture(GL_TEXTURE_2D, mAndroid[1].name); glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h); glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h); glEnable(GL_BLEND); //绘制Android字样的图片,这个不动 glBindTexture(GL_TEXTURE_2D, mAndroid[0].name); glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h); //交换缓冲区,以显示到屏幕 EGLBoolean res = eglSwapBuffers(mDisplay, mSurface); if (res == EGL_FALSE) break; // 12fps: don't animate too fast to preserve CPU const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now); if (sleepTime > 0) usleep(sleepTime); //最后执行checkExit函数,判断是否退出了 checkExit(); } while (!exitPending()); glDeleteTextures(1, &mAndroid[0].name); glDeleteTextures(1, &mAndroid[1].name); return false; } |
一共有以下几个步骤:
Android系统默认的开机动画是由两张图片android-logo-mask.png和android-logo-shine.png中。这两张图片保存在frameworks/base/core/res/assets/images目录中,它们最终会被编译在framework-res模块(frameworks/base/core/res)中,即编译在framework-res.apk文件中。编译在framework-res模块中的资源文件可以通过AssetManager类来访问。
BootAnimation类的成员函数android首先调用另外一个成员函数initTexture来将根据图片android-logo-mask.png和android-logo-shine.png的内容来分别创建两个纹理对象,这两个纹理对象就分别保存在BootAnimation类的成员变量mAndroid所描述的一个数组中。通过混合渲染这两个纹理对象,我们就可以得到一个开机动画,这是通过中间的while循环语句来实现的。
图片android-logo-mask.png用作动画前景,它是一个镂空的“ANDROID”图像。图片android-logo-shine.png用作动画背景,它的中间包含有一个高亮的呈45度角的条纹。在每一次循环中,图片android-logo-shine.png被划分成左右两部分内容来显示。左右两个部分的图像宽度随着时间的推移而此消彼长,这样就可以使得图片android-logo-shine.png中间高亮的条纹好像在移动一样。另一方面,在每一次循环中,图片android-logo-mask.png都作为一个整体来渲染,而且它的位置是恒定不变的。由于它是一个镂空的“ANDROID”图像,因此,我们就可以通过它的镂空来看到它背后的图片android-logo-shine.png的条纹一闪一闪地划过。这个while循环语句会一直被执行,直到应用程序/system/bin/bootanimation被结束为止。
在循环语句最后会执行checkExit()函数:
1 2 3 4 5 6 7 8 9 10 11 12 |
void BootAnimation::checkExit() { // Allow surface flinger to gracefully request shutdown char value[PROPERTY_VALUE_MAX]; property_get(EXIT_PROP_NAME, value, "0"); int exitnow = atoi(value); if (exitnow) { requestExit(); if (mAudioPlayer != NULL) { mAudioPlayer->requestExit(); } } } |
首先调用property_get获取属性EXIT_PROP_NAME的值:
1 |
#define EXIT_PROP_NAME "service.bootanim.exit" |
&ems;然后判断该值,如果为1,则调用requestExit()要求退出当前线程,该函数是异步的。位于system/core/libutils/Thread.cpp中:
1 2 3 4 5 6 |
void Thread::requestExit() { Mutex::Autolock _l(mLock); //这里将mExitPending 赋值为true mExitPending = true; } |
回到android()代码:
1 |
} while (!exitPending()); |
调用exitPending(),改函数判断requestExit()是否被调用过,如果调用过则返回true,否则为false。依然位于system/core/libutils/Thread.cpp中:
1 2 3 4 5 6 |
bool Thread::exitPending() const { Mutex::Autolock _l(mLock); //上面我们在requestExit赋过值了 return mExitPending; } |
这样,当属性“service.bootanim.exit”值被设为”1”时,android()就会调用requestExit(),exitPending()返回值为true。于是do…while()循环就会退出,开机动画绘制就会结束。
至于什么时候是哪个服务将属性“service.bootanim.exit”的值设置为1的,我们后面讲开机动画的停止的时候会提到。
自定义开机动画movie()
BootAnimation类的成员函数movie的实现比较长,我们分段来阅读:
Part.1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
bool BootAnimation::movie(){ String8 desString; //读取desc.txt文件内容 if (!readFile("desc.txt", desString)) { return false; } char const* s = desString.string(); // Create and initialize an AudioPlayer if we have an audio_conf.txt file //如果存在audio_conf.txt文件,则会创建一个AudioPlayer,并根据读取的字符串初始化 //ignore it String8 audioConf; if (readFile("audio_conf.txt", audioConf)) { mAudioPlayer = new AudioPlayer; if (!mAudioPlayer->init(audioConf.string())) { ALOGE("mAudioPlayer.init failed"); mAudioPlayer = NULL; } } ...... } |
从前面BootAnimation类的成员函数readyToRun的实现可以知道,如果目标设备上存在压缩文件/system/media/bootanimation.zip(另外两种我们忽略),那么BootAnimation类的成员变量mZip就会指向它,这段代码作用是读取开机动画文件mZip中的描述文件“desc.txt”。每个动画文件压缩包中必须要包含一个desc.txt,该文件用来描述开机动画如何显示。下面以一个示例来分析一下该文件:
1 2 3 4 5 |
480 640 20 p 1 0 folder1 p 2 20 folder2 c 0 0 folder3 c 1 0 folder4 |
Part.2:
第1行用来描述开机动画在屏幕显示的大小及速度。具体为:开机动画的宽度为480个像素,高度为640个像素,显示频率为每秒20帧,即每帧显示1/20秒。
下面的每一行代表一个片段,显示的时候会按照顺序从上到下依次显示。第1个字符为片段类型,有’c’和’p’两种,两者的区别后面会结合代码说明。
第2个数字为该片段重复显示的次数,如果为‘0’,表示会无限重复显示;第3个数字为两次显示之间的间隔,单位为第一行中定义的每帧显示的时间;第4个字符串为该片段所在的文件夹,一个片段可以由多个png图片组成,都存放在folder文件夹中。
- “p 1 0 folder1”代表该片段显示1次,与下一个片段间隔0s,该片段的显示图片路径为bootanimation.zip/folder1。
- “p 2 20 folder2”代表该片段显示2次,且两次之间显示的间隔为20(1/20)=1s,与下一个片段间隔20(1/20)=1s,该片段的显示图片路径为bootanimation.zip/folder2。
- “c 0 0 folder3”代表该片段无限循环显示,且两次显示的间隔为0s,与下一个片段间隔0s,该片段的显示图路径为bootanimation.zip/folder3。
- “c 1 10 folder4”代表该片段显示1次,显示后暂停10*(1/20)=0.5s,该片段的显示图路径为bootanimation.zip/folder4。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
bool BootAnimation::movie(){ ...Part.1... Animation animation; // Parse the description file for (;;) { const char* endl = strstr(s, "\n");//检测首次出现换行符的地址 if (!endl) break; //每次读取一行 String8 line(s, endl - s); const char* l = line.string(); int fps, width, height, count, pause; char path[ANIM_ENTRY_NAME_MAX]; char color[7] = "000000"; // default to black if unspecified char pathType; if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {//每行如果有三个字符串,则依次是宽、高、帧率 // ALOGD("> w=%d, h=%d, fps=%d", width, height, fps); animation.width = width; animation.height = height; animation.fps = fps; } //如果是大于等于四个字符串,则依次是显示类型、显示次数、与下一次间隔时间、显示颜色 else if (sscanf(l, " %c %d %d %s #%6s", &pathType, &count, &pause, path, color) >= 4) { // ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s", pathType, count, pause, path, color); Animation::Part part; part.playUntilComplete = pathType == 'c'; part.count = count; part.pause = pause; part.path = path; part.audioFile = NULL; if (!parseColor(color, part.backgroundColor)) { ALOGE("> invalid color '#%s'", color); part.backgroundColor[0] = 0.0f; part.backgroundColor[1] = 0.0f; part.backgroundColor[2] = 0.0f; } animation.parts.add(part); } s = ++endl; } ...... } |
上面的for循环语句分析完成desc.txt文件的内容后,就得到了开机动画的显示大小、速度以及片断信息。这些信息都保存在Animation对象animation中,其中,每一个动画片断都使用一个Animation::Part对象来描述,并且保存在Animation对象animation的成员变量parts所描述的一个片断列表中。
Part.3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
bool BootAnimation::movie(){ ...Part.1... ...Part.2... // read all the data structures //开始读取zip文件 const size_t pcount = animation.parts.size(); void *cookie = NULL; if (!mZip->startIteration(&cookie)) { return false; } ZipEntryRO entry; char name[ANIM_ENTRY_NAME_MAX]; //开始循环遍历每一个文件 while ((entry = mZip->nextEntry(cookie)) != NULL) { const int foundEntryName = mZip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX); if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) { ALOGE("Error fetching entry file name"); continue; } const String8 entryName(name); const String8 path(entryName.getPathDir()); const String8 leaf(entryName.getPathLeaf()); if (leaf.size() > 0) { for (size_t j=0 ; j<pcount ; j++) { if (path == animation.parts[j].path) { int method; // supports only stored png files if (mZip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) { if (method == ZipFileRO::kCompressStored) { FileMap* map = mZip->createEntryFileMap(entry); if (map) { Animation::Part& part(animation.parts.editItemAt(j)); if (leaf == "audio.wav") { // a part may have at most one audio file part.audioFile = map; } else { Animation::Frame frame; frame.name = leaf; frame.map = map; part.frames.add(frame); } } } } } } } } mZip->endIteration(cookie); ...... } |
接下来,BootAnimation类的成员函数movie再断续将每一个片断所对应的png图片读取出来。每一个png图片都表示一个动画帧,使用一个Animation::Frame对象来描述,并且保存在对应的Animation::Part对象的成员变量frames所描述的一个帧列表中。
Part.4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
bool BootAnimation::movie(){ ...Part.1... ...Part.2... ...Part.3... // clear screen glShadeModel(GL_FLAT); glDisable(GL_DITHER); glDisable(GL_SCISSOR_TEST); glDisable(GL_BLEND); glClearColor(0,0,0,1); glClear(GL_COLOR_BUFFER_BIT); eglSwapBuffers(mDisplay, mSurface); glBindTexture(GL_TEXTURE_2D, 0); glEnable(GL_TEXTURE_2D); glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); const int xc = (mWidth - animation.width) / 2; const int yc = ((mHeight - animation.height) / 2); nsecs_t lastFrame = systemTime(); nsecs_t frameDuration = s2ns(1) / animation.fps; Region clearReg(Rect(mWidth, mHeight)); clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height)); ...... } |
前面的一系列gl函数首先用来清理屏幕,接下来的一系列gl函数用来设置OpenGL的纹理显示方式。
变量xc和yc的值用来描述开机动画的显示位置,即需要在屏幕中间显示开机动画,另外一个变量frameDuration的值用来描述每一帧的显示时间,它是以纳秒为单位的。
Region对象clearReg用来描述屏幕中除了开机动画之外的其它区域,它是用整个屏幕区域减去开机动画所点据的区域来得到的。
Part.5:
<
pre>
bool BootAnimation::movie(){
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
...Part.1... ...Part.2... ...Part.3... ...Part.4... //第一层for循环用来显示每一个动画片断 for (size_t i=0 ; i<pcount ; i++) { const Animation::Part& part(animation.parts[i]); const size_t fcount = part.frames.size(); glBindTexture(GL_TEXTURE_2D, 0); //第二层的for循环用来循环显示每一个动画片断 for (int r=0 ; !part.count || r<part.count ; r++) { // Exit any non playuntil complete parts immediately if(exitPending() && !part.playUntilComplete) break; // only play audio file the first time we animate the part if (r == 0 && mAudioPlayer != NULL && part.audioFile) { mAudioPlayer->playFile(part.audioFile); } glClearColor( part.backgroundColor[0], part.backgroundColor[1], part.backgroundColor[2], 1.0f); //第三层的for循环用来显示每一个动画片断所对应的png图片 //可以看到,如果exitPending()返回值为true且part.playUntilComplete=false,则会break。 //即:当SurfaceFlinger服务要求bootanimation停止显示动画时,以‘p’标识的片段会停止, //而以'c'标识的片段会继续显示。这就是两者之间的主要区别。 for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) { const Animation::Frame& frame(part.frames[j]); nsecs_t lastFrame = systemTime(); if (r > 0) { glBindTexture(GL_TEXTURE_2D, frame.tid); } else {//如果一个动画片断的循环显示次数不等于1,那么就说明这个动画片断中的png图片需要重复地显示在屏幕中 //第一次显示一个png图片的时候,会调用函数glGenTextures来为这个png图片创建一个纹理对象, //并且将这个纹理对象的名称保存在对应的Animation::Frame对象的成员变量tid中 if (part.count != 1) { glGenTextures(1, &frame.tid); glBindTexture(GL_TEXTURE_2D, frame.tid); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } initTexture(frame); } // 如果Region对象clearReg所包含的区域不为空,首先要将它所包含的区域裁剪掉,避免开机动画可以显示在指定的位置以及大小中 if (!clearReg.isEmpty()) { Region::const_iterator head(clearReg.begin()); Region::const_iterator tail(clearReg.end()); glEnable(GL_SCISSOR_TEST); while (head != tail) { const Rect& r(*head++); glScissor(r.left, mHeight - r.bottom, r.width(), r.height()); glClear(GL_COLOR_BUFFER_BIT); } glDisable(GL_SCISSOR_TEST); } glDrawTexiOES(xc, yc, 0, animation.width, animation.height); eglSwapBuffers(mDisplay, mSurface); nsecs_t now = systemTime(); nsecs_t delay = frameDuration - (now - lastFrame); //ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay)); lastFrame = now; //调用函数usleep函数来让线程睡眠一下,以保证每一个png图片,即每一帧动画都按照预先指定好的速度来显示 if (delay > 0) { struct timespec spec; spec.tv_sec = (now + delay) / 1000000000; spec.tv_nsec = (now + delay) % 1000000000; int err; do { err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL); } while (err<0 && errno == EINTR); } checkExit(); } //每当循环显示完成一个片断时,需要调用usleep函数来使得线程睡眠part.pause * ns2us(frameDuration)毫秒,以便可以按照预先设定的节奏来显示开机动画。 usleep(part.pause * ns2us(frameDuration)); // For infinite parts, we've now played them at least once, so perhaps exit //如果无限循环,则检查是否有退出消息来了,上面android()函数分析过了 if(exitPending() && !part.count) break; } // free the textures for this part //最后一个if语句判断一个动画片断是否是循环显示的,即循环次数不等于1。 //如果是的话,那么就说明前面为它所对应的每一个png图片都创建过一个纹理对象。 //现在既然这个片断的显示过程已经结束了,因此,就需要释放前面为它所创建的纹理对象。 if (part.count != 1) { for (size_t j=0 ; j<fcount ; j++) { const Animation::Frame& frame(part.frames[j]); glDeleteTextures(1, &frame.tid); } } } return false; |
}
- 第一层for循环用来显示每一个动画片断,第二层的for循环用来循环显示每一个动画片断,第三层的for循环用来显示每一个动画片断所对应的png图片。这些png图片以纹理的方式来显示在屏幕中。
- 注意,如果一个动画片断的循环显示次数不等于1,那么就说明这个动画片断中的png图片需要重复地显示在屏幕中。由于每一个png图片都需要转换为一个纹理对象之后才能显示在屏幕中,因此,为了避免重复地为同一个png图片创建纹理对象,第三层的for循环在第一次显示一个png图片的时候,会调用函数glGenTextures来为这个png图片创建一个纹理对象,并且将这个纹理对象的名称保存在对应的Animation::Frame对象的成员变量tid中,这样,下次再显示相同的图片时,就可以使用前面已经创建好了的纹理对象,即调用函数glBindTexture来指定当前要操作的纹理对象。
-
如果Region对象clearReg所包含的区域不为空,那么在调用函数glDrawTexiOES和eglSwapBuffers来显示每一个png图片之前,首先要将它所包含的区域裁剪掉,避免开机动画可以显示在指定的位置以及大小中。
-
每当显示完成一个png图片之后,都要将变量frameDuration的值从纳秒转换为毫秒。如果转换后的值大小于,那么就需要调用函数usleep函数来让线程睡眠一下,以保证每一个png图片,即每一帧动画都按照预先指定好的速度来显示。注意,函数usleep指定的睡眠时间只能精确到毫秒,因此,如果预先指定的帧显示时间小于1毫秒,那么BootAnimation类的成员函数movie是无法精确地控制地每一帧的显示时间的。
-
还有另外一个地方需要注意的是,每当循环显示完成一个片断时,需要调用usleep函数来使得线程睡眠part.pause * ns2us(frameDuration)毫秒,以便可以按照预先设定的节奏来显示开机动画。
-
最后一个if语句判断一个动画片断是否是循环显示的,即循环次数不等于1。如果是的话,那么就说明前面为它所对应的每一个png图片都创建过一个纹理对象。现在既然这个片断的显示过程已经结束了,因此,就需要释放前面为它所创建的纹理对象。
附注:
可以看到,如果exitPending()返回值为true且part.playUntilComplete=false,则会break。即:当SurfaceFlinger服务要求bootanimation停止显示动画时,以‘p’标识的片段会停止,而以’c’标识的片段会继续显示。这就是两者之间的主要区别。 我猜想”c”标识的意思是continue,即:即使SurfaceFlinger要求bootanimation停止动画,bootanimation也不会立刻停止动画,它会等c标识片段都显示完毕后,再停止。
至此,第三个开机画面的显示过程就分析完成了。
开机动画的停止
接下来,我们再继续分析第三个开机画面是如何停止显示的。
当System进程将系统中的关键服务启动起来之后,就会将Launcher启动起来。Android应用程序的启动过程实际上就是它的根Activity组件的启动过程。对于应用程序Launcher来说,它的根Activity组件即为Launcher组件。
一个Activity组件在启动起来之后,就会被记录起来,等到它所运行在的主线程空闲的时候,这个主线程就会向ActivityManagerService发送一个Activity组件空闲的通知。由于应用程序Launcher是系统中第一个被启动的应用程序,即它的根Activity组件是系统中第一个被启动的Activity组件,因此,当ActivityManagerService接收到它的空闲通知的时候,就可以知道系统是刚刚启动起来的。在这种情况下,ActivityManagerService就会停止显示开机动画,以便可以在屏幕中显示应用程序Lancher的界面。
如果一个线程想要在空闲的时候处理一些事务,那么就必须要向这个线程的消息队列注册一个空闲消息处理器。自定义的空闲消息处理器灯必须要从MessageQueue.IdleHandler类继承下来,并且重写成员函数queueIdle。当一个线程空闲的时候,即消息队列中没有新的消息需要处理的时候,那些注册了的空闲消息处理器的成员函数queueIdle就会被调用。
应用程序的主线程是通过ActivityThread类来描述的,它实现在文件frameworks/base/core/Java/android/app/ActivityThread.java中。每当有一个新的Activity组件启动起来的时候,ActivityThread类都会向它所描述的应用程序主线程的消息队列注册一个类型为Idler的空闲消息处理器。这样一个应用程序的主线程就可以在空闲的时候,向ActivityManagerService发送一个Activity组件空闲通知,相当于是通知ActivityManagerService,一个新的Activity组件已经准备就绪了。
Idler类定义在frameworks/base/core/java/android/app/ActivityThread.java中, 它的成员函数queueIdle的实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
private class Idler implements MessageQueue.IdleHandler { @Override public final boolean queueIdle() { ActivityClientRecord a = mNewActivities; ...... if (a != null) { mNewActivities = null; IActivityManager am = ActivityManagerNative.getDefault(); ActivityClientRecord prev; do { ..... if (a.activity != null && !a.activity.mFinished) { try { am.activityIdle(a.token, a.createdConfig, stopProfiling); a.createdConfig = null; } catch (RemoteException ex) { // Ignore } } prev = a; a = a.nextIdle; prev.nextIdle = null; } while (a != null); } ...... return false; } } |
ActivityThread类有一个类型为ActivityClientRecord的成员变量mNewActivities,用来描述所有在当前应用程序主线程中新启动起来的Activity组件。这些新启动起来的Activity组件通过ActivityClientRecord类的成员变量nextIdle连接在一起。一旦当前应用程序主线程向ActivityManagerService发送了这些新启动的Activity组件的空闲通知之后,这些新启动起来的Activity组件就不会再被保存在ActivityThread类的成员变量mNewActivities中了,即每一个新启动的Activity组件只有一次机会向ActivityManagerService发送一个空闲通知。
向ActivityManagerService发送一个Activity组件空闲通知是通过调用ActivityManagerService代理对象的成员函数activityIdle来实现的,而ActivityManagerService代理对象可以通过调用ActivityManagerNative类的静态成员函数getDefault来获得。
ActivityManagerService代理对象的类型为ActivityManagerProxy,它的成员函数activityIdle实现在文件frameworks/base/core/java/android/app/ActivityManagerNative.java中,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class ActivityManagerProxy implements IActivityManager { ...... public void activityIdle(IBinder token, Configuration config) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(token); if (config != null) { data.writeInt(1); config.writeToParcel(data, 0); } else { data.writeInt(0); } mRemote.transact(ACTIVITY_IDLE_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); reply.readException(); data.recycle(); reply.recycle(); } ...... } |
ActivityManagerProxy类的成员函数activityIdle实际上是向ActivityManagerService发送一个类型为ACTIVITY_IDLE_TRANSACTION的Binder进程间通信请求,其中,参数token用来描述与这个进程间通信请求所关联的一个Activity组件,在我们这个场景中,这个Activity组件即为应用程序Launcher的根Activity组件Launcher。
类型为ACTIVITY_IDLE_TRANSACTION的Binder进程间通信请求是由ActivityManagerService类的成员函数activityIdle来处理的,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback { ...... @Override public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) { final long origId = Binder.clearCallingIdentity(); synchronized (this) { ActivityStack stack = ActivityRecord.getStackLocked(token); if (stack != null) { ActivityRecord r = mStackSupervisor.activityIdleInternalLocked(token, false, config); if (stopProfiling) { if ((mProfileProc == r.app) && (mProfileFd != null)) { try { mProfileFd.close(); } catch (IOException e) { } clearProfilerLocked(); } } } } Binder.restoreCallingIdentity(origId); } ...... } |
ActivityManagerService有一个类型为ActivityStackSupervisor的成员变量mStackSupervisor,Run all ActivityStacks through this,运行所有的ActivityStacks 通过这个,它的成员函数activityIdleInternalLocked如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// Checked. final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout, Configuration config) { ...... boolean booting = false; boolean enableScreen = false; boolean activityRemoved = false; ActivityRecord r = ActivityRecord.forToken(token); if (r != null) { ...... if (isFrontStack(r.task.stack) || fromTimeout) { booting = mService.mBooting; mService.mBooting = false; if (!mService.mBooted) { mService.mBooted = true; enableScreen = true; } } } ...... if (booting || enableScreen) { mService.postFinishBooting(booting, enableScreen); } ...... return r; } |
所以进入if判断中,将booting置为true,mService.mBooted和enableScreen也为true,进入下面的if判断,ActivityManagerService调用postFinishBooting方法完成系统启动。我们进入往下看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
void postFinishBooting(boolean finishBooting, boolean enableScreen) { mHandler.sendMessage(mHandler.obtainMessage(FINISH_BOOTING_MSG, finishBooting? 1 : 0, enableScreen ? 1 : 0)); } final MainHandler mHandler; final class MainHandler extends Handler { @Override public void handleMessage(Message msg) { ...... case FINISH_BOOTING_MSG: { if (msg.arg1 != 0) { finishBooting(); } if (msg.arg2 != 0) { enableScreenAfterBoot(); } break; } ...... } } |
这里最后调用enableScreenAfterBoot方法,以便可以将屏幕让出来显示应用程序Launcher的界面:
1 2 3 4 5 6 7 8 9 |
void enableScreenAfterBoot() { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN, SystemClock.uptimeMillis()); mWindowManager.enableScreenAfterBoot(); synchronized (this) { updateEventDispatchingLocked(); } } |
ActivityManagerService类的成员变量mWindowManager指向了系统中的Window管理服务WindowManagerService,ActivityManagerService服务通过调用它的成员函数enableScreenAfterBoot来停止显示开机动画。
WindowManagerService类的成员函数enableScreenAfterBoot的实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public void enableScreenAfterBoot() { synchronized(mWindowMap) { if (DEBUG_BOOT) { RuntimeException here = new RuntimeException("here"); here.fillInStackTrace(); Slog.i(TAG, "enableScreenAfterBoot: mDisplayEnabled=" + mDisplayEnabled + " mForceDisplayEnabled=" + mForceDisplayEnabled + " mShowingBootMessages=" + mShowingBootMessages + " mSystemBooted=" + mSystemBooted, here); } if (mSystemBooted) { return; } mSystemBooted = true; hideBootMessagesLocked(); // If the screen still doesn't come up after 30 seconds, give // up and turn it on. mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30*1000); } mPolicy.systemBooted(); performEnableScreen(); } |
WindowManagerService类的成员变量mSystemBooted用来记录系统是否已经启动完成的。如果已经启动完成的话,那么这个成员变量的值就会等于true,这时候WindowManagerService类的成员函数enableScreenAfterBoot什么也不做就返回了,否则的话,WindowManagerService类的成员函数enableScreenAfterBoot首先将这个成员变量的值设置为true,接着再调用另外一个成员函数performEnableScreen来执行停止显示开机动画的操作。
WindowManagerService类的成员函数performEnableScreen的实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public void performEnableScreen() { synchronized(mWindowMap) { ...... if (mDisplayEnabled) { return; } if (!mSystemBooted && !mShowingBootMessages) { return; } // Don't enable the screen until all existing windows have been drawn. if (!mForceDisplayEnabled && checkWaitingForWindowsLocked()) { return; } if (!mBootAnimationStopped) { // Do this one time. try { IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger"); if (surfaceFlinger != null) { //Slog.i(TAG, "******* TELLING SURFACE FLINGER WE ARE BOOTED!"); Parcel data = Parcel.obtain(); data.writeInterfaceToken("android.ui.ISurfaceComposer"); surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED data, null, 0); data.recycle(); } } catch (RemoteException ex) { Slog.e(TAG, "Boot completed: SurfaceFlinger is dead!"); } mBootAnimationStopped = true; } ...... } |
WindowManagerService类的另外一个成员变量mDisplayEnabled用来描述WindowManagerService是否已经初始化过系统的屏幕了,只有当它的值等于false,并且系统已经完成启动,即WindowManagerService类的成员变量mSystemBooted等于true的情况下,WindowManagerService类的成员函数performEnableScreen才通知SurfaceFlinger服务停止显示开机动画。
注意,WindowManagerService类的成员函数performEnableScreen是通过一个类型为IBinder.FIRST_CALL_TRANSACTION的进程间通信请求来通知SurfaceFlinger服务停止显示开机动画的。
在SurfaceFlinger服务,类型为IBinder.FIRST_CALL_TRANSACTION的进程间通信请求被定义为停止显示开机动画的请求,位于frameworks/native/include/gui/ISurfaceComposer.h中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class BnSurfaceComposer : public BnInterface<ISurfaceComposer> { public: enum { // Note: BOOT_FINISHED must remain this value, it is called from // Java by ActivityManagerService. BOOT_FINISHED = IBinder::FIRST_CALL_TRANSACTION, ...... }; virtual status_t onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0); }; |
BnSurfaceComposer类它是SurfaceFlinger服务所要继承的Binder本地对象类,其中。当SurfaceFlinger服务接收到类型为IBinder::FIRST_CALL_TRANSACTION,即类型为BOOT_FINISHED的进程间通信请求时,它就会将该请求交给它的成员函数bootFinished来处理。
SurfaceFlinger服务的成员函数bootFinished实现在文件frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp中,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void SurfaceFlinger::bootFinished() { const nsecs_t now = systemTime(); const nsecs_t duration = now - mBootTime; ALOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) ); mBootFinished = true; // wait patiently for the window manager death const String16 name("window"); sp<IBinder> window(defaultServiceManager()->getService(name)); if (window != 0) { window->linkToDeath(static_cast<IBinder::DeathRecipient*>(this)); } // stop boot animation // formerly we would just kill the process, but we now ask it to exit so it // can choose where to stop the animation. property_set("service.bootanim.exit", "1"); } |
可以看到,该函数将属性“service.bootanim.exit”设置为”1”。在第2节分析android()代码的时候,我们讲到:当属性“service.bootanim.exit”值被设为”1”时,android()就会退出,开机动画显示自然也就结束了。由于android()退出且返回值为false,BootAnimation::threadLoop()线程也就结束了。再回到BootAnimation.cpp的main()函数中,threadLoop()线程结束,main函数也就结束,至此,bootanimaiton进程就自行结束,开机动画的显示完成了。
至此,Android系统的三个开机画面的显示过程就分析完成了。
小结
本文的目的并不是单纯为了介绍Android系统的开机画面,而是希望能够以Android系统的开机画面来作为切入点来分析SurfaceFlinger。后续文章我们会详细分析SurfaceFlinger的每个模块和功能。
转载自:http://windrunnerlihuan.com/2017/05/02/Android-SurfaceFlinger-%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF-%E4%B8%89-Android%E5%BC%80%E6%9C%BA%E5%8A%A8%E7%94%BB%E6%B5%81%E7%A8%8B%E7%AE%80%E8%BF%B0/