1. 簡介
Android系統啓動過程中個,最多有三個開機畫面。第一個開機畫面是在內核啓動過程中出現的一個靜態畫面(默認不顯示),第二個是在init啓動過程中出現的一個靜態畫面(我們平常所說的logo),第三個開機畫面是在系統服務啓動過程中出現的,爲動態畫面。
第一個和第二個開機畫面圖片位置:
mediatek/custom/common/uboot/logo/wvga_a56_doov_cta。
第三個開機動畫位置:
mediatek/custom/huaqin75_cu_ics/system/bootanim/bootanimation
第一個和第二個開機畫面知道更換圖片的位置即可, 這兩幀畫面是由底層uboot或者Kernel顯示的,第三個開機動畫則是由系統服務啓動的。
開機動畫由應用程序bootanimation負責顯示。應用程序bootanimation在啓動腳本init.rc中被配置成一個服務,如下所示:
service bootanim /system/bin/bootanimation
class core
user graphics
group graphics audio
disabled
oneshot
程序段說明:
bootanim
爲服務名稱,它對應執行/system/bin/bootanimation應用程序。
user 和group
應用程序bootanimation的用戶和用戶組名稱分別設置爲graphics。
disabled
用來啓動應用程序bootanimation的服務是disable的,即init進程在啓動的時候,不會主動將應用程序bootanimation啓動起來。當SurfaceFlinger服務啓動的時候,它會通過修改系統屬性ctl.start的值來通知init進程啓動應用程序bootanimation,以便可以顯示第三個開機畫面。System進程將系統中的關鍵服務都啓動起來後,ActivityManagerService服務會通知SurfaceFlinger服務通過修改系統屬性ctl.stop的值通知init進程停止執行應用程序bootanimation,停止顯示第三個開機畫面。
oneshot
啓動一次。
2.工作流程分析
下面基於Android6.0分析bootanimation的工作流程
bootanimation的實現在
frameworks/base/cmds/bootanimation
目錄下,其入口函數main是實現在
frameworks/base/cmds/bootanimation/bootanimation_main.cpp
int main()
{
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
char value[PROPERTY_VALUE_MAX];
property_get("debug.sf.nobootanimation", value, "0");
int noBootAnimation = atoi(value);
ALOGI_IF(noBootAnimation, "boot animation disabled");
if (!noBootAnimation) {
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
// create the boot animation object
sp<BootAnimation> boot = new BootAnimation();
IPCThreadState::self()->joinThreadPool();
}
return 0;
}
從代碼中可以看出,實際真正工作是由BootAnimation
類來完成的。
main函數首先檢查系統屬性“debug.sf.nobootnimaition
”的值是否等於0。如果爲0則不做任何操作,反之則先啓動一個Binder線程池。由於BootAnimation對象在顯示開機動畫的過程中,需要與SurfaceFlinger服務通信,因此,應用程序bootanimation就需要啓動一個Binder線程池。
BootAnimation
類間接地繼承了RefBase
類,並且重寫了RefBase
類的成員函數onFirstRef
,因此,當一個BootAnimation
對象第一次被智能指針引用的時,這個BootAnimation
對象的成員函數onFirstRef
就會被調用。所以下面的代碼會被執行:
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
類的構造函數中創建的,如下所示
BootAnimation::BootAnimation() : Thread(false), mZip(NULL)
{
mData = new PlayData();
mSession = new SurfaceComposerClient();
}
SurfaceComposerClient類(frameworks/native/include/gui/)內部有一個實現了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來顯示第三個開機畫面。
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
sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
SurfaceComposerClient::openGlobalTransaction();
control->setLayer(0x40000000);
SurfaceComposerClient::closeGlobalTransaction();
sp<Surface> s = control->getSurface();
//原生代碼
// initialize opengl and 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;
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;
mVideoSurface = 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;
//廠商定製代碼
#ifdef INTEL_FEATURE_SHUTDOWNANIM_SUPPORT
char shutdown_value_str[PROPERTY_VALUE_MAX];
property_get(SHUTDOWN_PROP_NAME, shutdown_value_str, "1");
int shutdown_value = atoi(shutdown_value_str);
if(shutdown_value == 0 ) {
if ((encryptedAnimation &&
(access(SYSTEM_ENCRYPTED_SHUTDOWNANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(SYSTEM_ENCRYPTED_SHUTDOWNANIMATION_FILE)) != NULL)) ||
((access(OEM_SHUTDOWNANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(OEM_SHUTDOWNANIMATION_FILE)) != NULL)) ||
((access(SYSTEM_SHUTDOWNANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(SYSTEM_SHUTDOWNANIMATION_FILE)) != NULL))) {
mZip = zipFile;
}
}
else{
#endif
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;
}
#ifdef INTEL_FEATURE_SHUTDOWNANIM_SUPPORT
}
#endif
return NO_ERROR;
}
BootAnimation
類的成員函數session()
用來返回BootAnimation
類的成員變量mSession
所描述的一個SurfaceComposerClient
對象。
通過調用成員函數createSurface
可以獲得一個SurfaceControl
對象control
,createSurface
首先調用內部的Binder
代理對象mClient
來請求SurfaceFlinger
返回一個類型爲SurfaceLayer
的Binder
代理對象,接着再使用這個Binder
代理對象來創建一個SurfaceControl
對象。
創建出來的SurfaceControl
對象的成員變量mSurfaceData
就指向了從SurfaceFlinger
返回來的類型爲SurfaceLayer
的Binder
代理對象。有了這個Binder
代理對象之後,SurfaceControl
對象就可以和SurfaceFlinger
服務通信了。
調用SurfaceControl
對象control的成員函數getSurface
會返回一個Surface
對象s。這個Surface
對象s
內部也有一個類型爲SurfaceLayer
的Binder
代理對象mSurface
,Surface
對象s
也可以通過其內部的Binder
代理對象mSurface
來和SurfaceFlinger
服務通信。
OpenGL
需要通過ANativeWindow
類來間接地操作Android窗口系統。這種橋樑關係是通過EGL
庫來建立的,所有以egl爲前綴的函數名均爲EGL庫提供的接口。爲了能夠在OpenGL
和Android窗口系統之間的建立一個橋樑,我們需要一個EGLDisplay
對象display
(用來描述一個EGL顯示屏),一個EGLConfig
對象config(用來描述一個EGL幀緩衝區配置參數),一個EGLSurface
對象surface
(用來描述一個EGL繪圖表面),以及一個EGLContext
對象context
(用來描述一個EGL繪圖上下文(狀態)),EGLConfig
對象config
、EGLSurface
對象surface
和EGLContext
對象context
都是用來描述EGLDisplay
對象display
的。有了這些對象之後,就可以調用函數eglMakeCurrent
來設置當前EGL
庫所使用的繪圖表面以及繪圖上下文。
每當OpenGL
需要繪圖的時候,它就會找到前面所設置的繪圖表面,即EGLSurface
對象surface
。有了EGLSurface
對象surface
之後,就可以找到與它關聯的ANativeWindow
對象,即Surface
對象s
。有了Surface
對象s
之後,就可以通過其內部的Binder
代理對象mSurface
來請求SurfaceFlinger
服務返回幀緩衝區硬件設備的一個圖形訪問接口。這樣,OpenGL
最終就可以將要繪製的圖形渲染到幀緩衝區硬件設備中去,即顯示在實際屏幕上。屏幕的大小,即寬度和高度,可以通過函數eglQuerySurface
來獲得。
BootAnimation類的成員變量mZip是一開機動畫Zip文件路徑。如果用戶自定的的開機動畫
/system/media/bootanimation.zip
都不存在時,它的值變爲null,即當它的值等於null的時候,要顯示的開機動畫是Android系統默認的開機動畫,即要執行mp4()
函數,反之開機動畫就是由用戶自定義的開機動畫,即要執行movie()
函數。
這一步執行完成之後,用來顯示第三個開機畫面的線程的初始化工作就執行完成了,接下來,就會執行這個線程的主體函數,即BootAnimation
類的成員函數threadLoop()
。
BootAnimation
類的成員函數threadLoop的實現如下所示:
bool BootAnimation::threadLoop()
{
bool r;
// We have no bootanimation file, so we use the stock android logo
// animation.
if (mZip == NULL) {
r = mp4();
} 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;
}
首先判斷用戶定義的開機動畫zip文件是否爲空,爲空則通過mp4()
函數播放視頻文件, 不爲空則通過movie()
函數播放用戶自定義動畫。 顯示完成之後,就會銷燬前面所創建的EGLContext
對象mContext
、EGLSurface
對象mSurface
,以及EGLDisplay
對象mDisplay
等。
android默認開機動畫代碼如下:
bool BootAnimation::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);
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();
} 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循環語句來實現的
這個while循環語句會一直被執行,直到應用程序*/system/bin/bootanimation*被結束爲止。
播放用戶定義的動畫代碼流程如下:
bool BootAnimation::movie()
{
String8 desString;
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
String8 audioConf;
if (readFile("audio_conf.txt", audioConf)) {
mAudioPlayer = new AudioPlayer;
if (!mAudioPlayer->init(audioConf.string())) {
ALOGE("mAudioPlayer.init failed");
mAudioPlayer = NULL;
}
}
Animation animation;
// Parse the description file
for (;;) {
const char* endl = strstr(s, "\n");
if (endl == NULL) 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;
}
// read all the data structures
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) {
uint16_t 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);
glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
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 frameDuration = s2ns(1) / animation.fps;
Region clearReg(Rect(mWidth, mHeight));
clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height));
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 (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 (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 {
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);
}
if (!clearReg.isEmpty()) {
Region::const_iterator head(clearReg.begin());
Region::const_iterator tail(clearReg.end());
glEnable(GL_SCISSOR_TEST);
while (head != tail) {
const Rect& r2(*head++);
glScissor(r2.left, mHeight - r2.bottom,
r2.width(), r2.height());
glClear(GL_COLOR_BUFFER_BIT);
}
glDisable(GL_SCISSOR_TEST);
}
// specify the y center as ceiling((mHeight - animation.height) / 2)
// which is equivalent to mHeight - (yc + animation.height)
glDrawTexiOES(xc, mHeight - (yc + animation.height),
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;
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));
// For infinite parts, we've now played them at least once, so perhaps exit
if(exitPending() && !part.count)
break;
}
// free the textures for this part
if (part.count != 1) {
for (size_t j=0 ; j<fcount ; j++) {
const Animation::Frame& frame(part.frames[j]);
glDeleteTextures(1, &frame.tid);
}
}
}
#ifdef INTEL_FEATURE_SHUTDOWNANIM_SUPPORT
property_set(SHUTDOWN_PROP_ENABLE, "true");
char shutdown_value_str[PROPERTY_VALUE_MAX];
property_get(SHUTDOWN_PROP_NAME, shutdown_value_str, "1");
int shutdown_value = atoi(shutdown_value_str);
if(shutdown_value == 0 ) {
property_set(SHUTDOWN_PROP_NAME, "1");
do {
// wait for shutdownthread.java
sleep(100);
property_get(SHUTDOWN_PROP_NAME, shutdown_value_str, "1");
shutdown_value = atoi(shutdown_value_str);
} while (shutdown_value != 2 );
}
#endif
return false;
}
從前面BootAnimation類的成員函數readyToRun的實現可以知道,如果目標設備上存在壓縮文件mZip。無論BootAnimation類的成員變量mZip指向的是哪一個壓縮文件,這個壓縮文件都必須包含有一個名稱爲“desc.txt”的文件,用來描述用戶自定義的開機動畫是如何顯示的。
文件desc.txt的內容格式如下面的例子所示:
480 800 6
p 1 2 folder1
p 0 2 folder2
第一行的三個數字分別表示開機動畫在屏幕中的顯示寬度、高度以及幀速(fps)。剩餘的每一行都用來描述一個動畫片斷,這些行必須要以字符“p”來開頭,後面緊跟着兩個數字以及一個文件目錄路徑名稱。第一個數字表示一個片段的循環顯示次數,如果它的值等於0,那麼就表示無限循環地顯示該動畫片斷。第二個數字表示每一個片段在兩次循環顯示之間的時間間隔。這個時間間隔是以一個幀的時間爲單位的。文件目錄下面保存的是一系列png文件,這些png文件會被依次顯示在屏幕中。
接下來的第一個for循環語句分析完成desc.txt文件的內容後,就得到了開機動畫的顯示大小、速度以及片斷信息。這些信息都保存在Animation對象animation中,其中,每一個動畫片斷都使用一個Animation::Part對象來描述,並且保存在Animation對象animation的成員變量parts所描述的一個片斷列表中。而後第二個for循環中,BootAnimation類的成員函數movie再斷續將每一個片斷所對應的png圖片讀取出來。每一個png圖片都表示一個動畫幀,使用一個Animation::Frame對象來描述,並且保存在對應的Animation::Part對象的成員變量frames所描述的一個幀列表中。
獲得了開機動畫的所有信息之後,接下來的前面的一系列gl函數首先用來清理屏幕,接下來的一系列gl函數用來設置OpenGL的紋理顯示方式。
變量xc和yc的值用來描述開機動畫的顯示位置,即需要在屏幕中間顯示開機動畫,另外一個變量frameDuration的值用來描述每一幀的顯示時間,它是以納秒爲單位的。Region對象clearReg用來描述屏幕中除了開機動畫之外的其它區域,它是用整個屏幕區域減去開機動畫所點據的區域來得到的。
準備好開機動畫的顯示參數之後,就可以用最後一個for循環執行顯示開機動畫的操作了,第一層for循環用來顯示每一個動畫片斷,第二層的for循環用來循環顯示每一個動畫片斷,第三層的for循環用來顯示每一個動畫片斷所對應的png圖片。這些png圖片以紋理的方式來顯示在屏幕中。
如果Region對象clearReg所包含的區域不爲空,那麼在調用函數glDrawTexiOES和eglSwapBuffers來顯示每一個png圖片之前,首先要將它所包含的區域裁剪掉,避免開機動畫可以顯示在指定的位置以及大小中。
每當顯示完成一個png圖片之後,都要將變量frameDuration的值從納秒轉換爲毫秒。如果轉換後的值大小於,那麼就需要調用函數usleep函數來讓線程睡眠一下,以保證每一個png圖片,即每一幀動畫都按照預先指定好的速度來顯示。注意,函數usleep指定的睡眠時間只能精確到毫秒,因此,如果預先指定的幀顯示時間小於1毫秒,那麼BootAnimation類的成員函數movie是無法精確地控制地每一幀的顯示時間的。
還有另外一個地方需要注意的是,每當循環顯示完成一個片斷時,需要調用usleep
函數來使得線程睡眠part.pause * ns2us(frameDuration)毫秒,以便可以按照預先設定的節奏來顯示開機動畫。
最後一個if語句判斷一個動畫片斷是否是循環顯示的,即循環次數不等於1。如果是的話,那麼就說明前面爲它所對應的每一個png圖片都創建過一個紋理對象。現在既然這個片斷的顯示過程已經結束了,因此,就需要釋放前面爲它所創建的紋理對象。
3.啓動流程及結束流程
bootanimation的啓動在surfaceflinger初始化後,代碼如下:
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::init() {
ALOGI( "SurfaceFlinger's main thread ready to run. "
"Initializing graphics H/W...");
Mutex::Autolock _l(mStateLock);
///////////////
//省略無關代碼
//////////////
// set initial conditions (e.g. unblank default device)
initializeDisplays();
// start boot animation
startBootAnim();
}
開機動畫的結束是在SystemServer中完成的,流程如下:
enableScreenAfterBoot()(ActivityManagerService)——>enableScreenAfterBoot()(WindowManagerService)——>
performEnableScreen()(WindowManagerService) ——> binder調用SurfaceFlinger中結束動畫的函數,代碼如下:
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;
}
最終調用SurfaceFlinger中的bootFinished函數結束動畫的播放。
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");
}