AOSP開機動畫調測技術點(基于Android13)
開機動畫替換
-
首先,在你的計算機上創建一個名為"bootanimation"的文件夾,并將"part0"、"part1"和"desc.txt"這三個文件復制到該文件夾中。這些文件包含了開機動畫的圖像幀和描述信息。
-
在命令行中切換到bootanimation文件夾,并執行以下命令將文件夾中的內容打包成一個名為"bootanimation.zip"的壓縮文件:
zip -r -0 bootanimation.zip part0 part1 desc.txt
這個命令的含義是將當前目錄下的"part0"、"part1"和"desc.txt"三個文件打包成一個名為"bootanimation.zip"的壓縮文件。
-r
:表示遞歸地將指定目錄下的所有文件和子目錄都包含在壓縮文件中。-0
:表示使用不進行壓縮的存儲模式,即不對文件進行壓縮處理,直接存儲到壓縮包中。
- 使用ADB(Android Debug Bridge)將生成的bootanimation.zip文件復制到設備的/system/media/目錄中。請注意,這一步需要設備具有root權限以及重新掛載/system分區。你可以使用以下命令完成這一步驟:
adb root
adb remount
adb push bootanimation.zip /system/media/
- 執行以下命令來啟動新的開機動畫:
adb shell setprop service.bootanim.exit 0adb shell setprop ctl.start bootanim
- 現在你可以觀察到新的開機動畫效果。如果想退出新的開機動畫,執行以下命令:
adb shell setprop service.bootanim.exit 1
Android開機動畫(desc.txt)格式
開機動畫分為2個階段的圖片資源加載,part0和part2, 加載規則在desc.txt文件中進行描述
desc.txt part0 part1
part0與part1中的圖片需要按照數字大小順序標記。
desc.txt內容如下:
通用參數
第一行定義了動畫的一般參數:
WIDTH HEIGHT FPS [PROGRESS]
WIDTH
: 動畫的寬度(像素)HEIGHT
: 動畫的高度(像素)FPS
: 每秒的幀數,例如60PROGRESS
: 是否在最后一部分顯示進度百分比- 百分比將以"x"坐標為基準,在動畫高度的1/3處顯示。
動態著色屬性
如果使用動態著色功能,則提供一個可選的行來指定動態著色屬性的格式。如果不使用動態著色,則可以跳過此行。
動畫部分
接下來,根據以下格式提供多行來定義動畫的各個部分:
TYPE COUNT PAUSE PATH [FADE [#RGBHEX [CLOCK1 [CLOCK2]]]]
TYPE
: 單個字符,表示此動畫段的類型:p
– 該部分會播放,除非在啟動結束之前被中斷c
– 該部分將播放到完成,無論如何f
– 與p
類似,但在繼續播放的同時,指定的幀數正在淡出。只有第一個被中斷的f
部分會被淡出,其他后續的f
部分會被跳過。
COUNT
: 播放動畫的次數,或者為0以無限循環,直到啟動完成PAUSE
: 此部分結束后延遲的幀數PATH
: 包含此部分幀圖片的目錄(例如part0
)FADE
: (僅適用于f
類型) 被中斷時要淡出的幀數,其中0
表示立即淡出,使f ... 0
的行為類似于p
,并且不將其視為淡出部分RGBHEX
: (可選) 背景顏色,表示為#RRGGBB
CLOCK1, CLOCK2
: (可選) 繪制當前時間(用于手表)的坐標:- 如果只提供
CLOCK1
,則它是時鐘的y坐標,x坐標默認為c
- 如果同時提供
CLOCK1
和CLOCK2
,則CLOCK1
是x坐標,CLOCK2
是y坐標 - 值可以是正整數、負整數或
c
c
– 將文本居中n
– 將文本定位到距起始位置的n像素處;在x軸上為左邊緣,在y軸上為底部邊緣-n
– 將文本定位到距結束位置的n像素處;在x軸上為右邊緣,在y軸上為頂部邊緣
- 示例:
-24
或c -24
將文本定位到距離屏幕頂部24像素處,水平居中16 c
將文本定位到距離屏幕左側16像素處,垂直居中-32 32
將文本定位到距離屏幕邊緣向上32像素,向左32像素的位置
- 如果只提供
此外,還有一個特殊的類型 $SYSTEM
,它加載并播放 /system/media/bootanimation.zip
。
以上是關于Android開機動畫的desc.txt
配置文件的格式說明。該文件定義了動畫的屬性和各個部分的行為。
源碼分析
frameworks/base/cmds/bootanimation
.
├── Android.bp
├── audioplay.cpp
├── audioplay.h
├── BootAnimation.cpp
├── BootAnimation.h
├── bootanimation_main.cpp
├── BootAnimationUtil.cpp
├── BootAnimationUtil.h
├── bootanim.rc
├── FORMAT.md
└── OWNERS
定義服務
開啟動畫啟動規則定義在bootanim.rc
中
service bootanim /system/bin/bootanimationclass core animationuser graphicsgroup graphics audiodisabledoneshotioprio rt 0task_profiles MaxPerformance
開機動畫的服務配置文件字段解釋如下:
service
: 指明服務的名稱為"bootanim",即開機動畫服務。/system/bin/bootanimation
: 指定了開機動畫的執行文件路徑為"/system/bin/bootanimation"。class core animation
: 表示此服務屬于核心服務,并且是與動畫相關的服務。user graphics
: 指定服務運行的用戶為"graphics"。group graphics audio
: 指定服務運行的組為"graphics audio",表示具有這兩個組的權限。disabled
: 表示此服務當前處于禁用狀態,不會自動啟動。oneshot
: 表示此服務只執行一次,完成任務后即退出。ioprio rt 0
: 設置了I/O調度優先級。task_profiles MaxPerformance
: 指定了任務的性能規格為最大性能。
根據你提供的信息,開機動畫服務當前處于禁用狀態,不會自動啟動。
啟動開機動畫
開機動畫在SurfaceFlinger初始化完成之后播放
//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::init() {............//啟動mStartPropertySetThread線程播放開機動畫mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable);if (mStartPropertySetThread->Start() != NO_ERROR) {ALOGE("Run StartPropertySetThread failed!");}
}
那么看看StartPropertySetThread
線程中具體如何運行
//frameworks/native/services/surfaceflinger/StartPropertySetThread.cpp
bool StartPropertySetThread::threadLoop() {// Set property service.sf.present_timestamp, consumer need check its readinessproperty_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");// Clear BootAnimation exit flagproperty_set("service.bootanim.exit", "0");property_set("service.bootanim.progress", "0");// Start BootAnimation if not startedproperty_set("ctl.start", "bootanim");// Exit immediatelyreturn false;
}
在StartPropertySetThread
中通過設置屬性方式啟動動畫播放。
所以如果需要手動運行bootanimation, 需要通過下面命令完成:
setprop service.bootanim.exit 0
setprop ctl.start bootanim
接下來我們進入bootanimation實現的代碼分析動畫播放的具體流程。
動畫播放流程
- 首先從main函數分析,開機動畫運行在一個線程中
//frameworks/base/cmds/bootanimation/bootanimation_main.cpp
int main()
{
sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks());
waitForSurfaceFlinger();
boot->run("BootAnimation", PRIORITY_DISPLAY);
}
- 找尋開機動畫文件
//frameworks/base/cmds/bootanimation/BootAnimation.cpp
void BootAnimation::onFirstRef() {......preloadAnimation();......
}
bool BootAnimation::preloadAnimation() {//查找開機動畫findBootAnimationFile();if (!mZipFileName.isEmpty()) {//加載開機動畫mAnimation = loadAnimation(mZipFileName);return (mAnimation != nullptr);}return false;
}
//找尋開機動畫文件主要流程即在該函數中完成
void BootAnimation::findBootAnimationFile() {......//加密設備開機動畫路徑static const std::vector<std::string> encryptedBootFiles = {PRODUCT_ENCRYPTED_BOOTANIMATION_FILE, SYSTEM_ENCRYPTED_BOOTANIMATION_FILE,};// 啟動動畫路徑static const std::vector<std::string> bootFiles = {APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE,OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE}; ......
}
- 加載開機動畫文件并解析desc文件
//frameworks/base/cmds/bootanimation/BootAnimation.cpp
BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) {if (mLoadedFiles.indexOf(fn) >= 0) {SLOGE("File \"%s\" is already loaded. Cyclic ref is not allowed",fn.string());return nullptr;}ZipFileRO *zip = ZipFileRO::open(fn);if (zip == nullptr) {SLOGE("Failed to open animation zip \"%s\": %s",fn.string(), strerror(errno));return nullptr;}ALOGD("%s is loaded successfully", fn.string());//解析bootanimation.zip并填充AnimationAnimation *animation = new Animation;animation->fileName = fn;animation->zip = zip;animation->clockFont.map = nullptr;mLoadedFiles.add(animation->fileName);//解析desc.txt文件并填充Animation對象parseAnimationDesc(*animation);if (!preloadZip(*animation)) {releaseAnimation(animation);return nullptr;}mLoadedFiles.remove(fn);return animation;
}
- 初始化顯示參數
//frameworks/base/cmds/bootanimation/BootAnimation.cpp
status_t BootAnimation::readyToRun() {//設置分辨率、創建Surface等
ui::Size resolution = displayMode.resolution;resolution = limitSurfaceSize(resolution.width, resolution.height);// create the native surfacesp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),resolution.getWidth(), resolution.getHeight(), PIXEL_FORMAT_RGB_565);}// initialize opengl and eglEGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);eglInitialize(display, nullptr, nullptr);
- 進入最終播放流程
//frameworks/base/cmds/bootanimation/BootAnimation.cpp
bool BootAnimation::threadLoop() {bool result;initShaders();// 初始化著色器// 為啟動視頻功能進行初始化mStartbootanimaTime = 0;mBootVideoTime = -1;//Android還支持播放視頻文件,這樣可以方便廣告植入,開展開機廣告業務if (mVideoAnimation) {result = video();} else {// 如果沒有啟動動畫文件,那么使用默認的安卓logo動畫。if (mZipFileName.isEmpty()) {ALOGD("No animation file");result = android();} else {result = movie();}}// 關閉回調mCallbacks->shutdown();eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);eglDestroyContext(mDisplay, mContext);eglDestroySurface(mDisplay, mSurface);mFlingerSurface.clear();mFlingerSurfaceControl.clear();eglTerminate(mDisplay);eglReleaseThread();IPCThreadState::self()->stopProcess();return result;
}bool BootAnimation::movie() {if (mAnimation == nullptr) {mAnimation = loadAnimation(mZipFileName);}......playAnimation(*mAnimation);......releaseAnimation(mAnimation);......
}bool BootAnimation::playAnimation(const Animation& animation) {const size_t pcount = animation.parts.size();nsecs_t frameDuration = s2ns(1) / animation.fps;for (size_t i=0 ; i<pcount ; i++) {const Animation::Part& part(animation.parts[i]);const size_t fcount = part.frames.size();// Handle animation packageif (part.animation != nullptr) {playAnimation(*part.animation);if (exitPending())break;continue; //to next part}/////使用gl繪制每一幀的圖像//第2輪及以后的播放if (r > 0) {glBindTexture(GL_TEXTURE_2D, frame.tid);} else {//第一輪播放需要初始化glGenTextures(1, &frame.tid);glBindTexture(GL_TEXTURE_2D, frame.tid);int w, h;// Set decoding option to alpha unpremultiplied so that the R, G, B channels// of transparent pixels are preserved.initTexture(frame.map, &w, &h, false /* don't premultiply alpha */);}////}
}
動畫退出時機
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private void performEnableScreen() {////if (!mBootAnimationStopped) {Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);// 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.SystemProperties.set("service.bootanim.exit", "1");mBootAnimationStopped = true;}///
}