双屏异显开机动画实现
1.整體設計思路
基于原生開機動畫流程上,背屏開機動畫在bootanim服務起來之后,啟動主屏開機動畫線程bootanimation時,同時啟動一條新增加的背屏開機動畫線程BackBootAnimation,然后在背屏開機動畫線程BackBootAnimation中, 按照主屏開機動畫控制流程一樣,依次實現從preload分區中加載背屏開機動畫資源,解析動畫文件資源,初始化EGL,創建用于背屏繪圖的Surface,通過Surface創建EGL Surface,創建EGLContext 上下文,調用eglMakeCurrent綁定eglSurface, context,display,最終調用eglSwapBuffer函數進行繪制.
背屏開機動畫結束流程,也與主屏相同,不停的檢查service.bootanim.exit屬性是否被寫值為1,寫值為1的時候線程停止.
2.流程圖
?
對于如上流程圖中,針對背屏開機動畫主要修改在SurfaceFlinger和bootanim服務中,SurfaceFlinger中主要是增加了一開機就對背屏diaplayState初始化流程,新增了一些bootanim服務中需要用到的接口.
bootanim服務中的修改,主要針對背屏開機動畫流程修改.理論上我們是需要新增加一個類似Bootanimation.cpp類播放背屏開機動畫,這樣改動比較大,我們選擇了修改Bootanimation.cpp的構造方法,增加一個參數標記當前線程是背屏還是主屏.
?
3.bootanim服務中的修改點
3.1修改 BootAnimation的構造方法
frameworks/base / cmds/bootanimation/BootAnimation.h
....
?explicit BootAnimation(sp<Callbacks> callbacks, bool shuttingDown, bool isBackDisplay);//修改構造方法,isBackDisplay新增參數,標記背屏還是主屏
....
?bool ???????mBackDisplay; //新增參數,標記背屏還是主屏,會在實現類BootAnimation.cpp中用到
....
?
3.2 增加背屏開機動畫線程
frameworks/base/cmds/bootanimation/bootanimation_main.cpp
int main(int argc, char** argv)
{
????....
????????sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks(true), isShutdown, false);//創建按主屏開機動畫線程
????????sp<BootAnimation> backBoot = new BootAnimation(audioplay::createAnimationCallbacks(false), isShutdown, true);//創建背屏開機動畫線程
BootAnimation的構造方法的三個參數意義:
//audioplay::createAnimationCallbacks(false)//標記是否開機播放鈴音
// isShutdown 標記是開機還是關機
//
????????waitForSurfaceFlinger();
?
????????boot->run("BootAnimation", PRIORITY_DISPLAY);
????????backBoot->run("BackBootAnimation", PRIORITY_DISPLAY);//啟動背屏線程
????????ALOGV("Boot animation set up. Joining pool.");
?
????????IPCThreadState::self()->joinThreadPool();
????}
????return 0;
}
?
3.3?增加是否播放開機鈴音控制邏輯
frameworks/base / cmds/bootanimation/audioplay.h
android::sp<android::BootAnimation::Callbacks> createAnimationCallbacks(bool needAudioPlay);//修改audioplay構造方法,增加 needAudioPlay參數,標記是否播放鈴音,在audioplay.cpp使用
?
3.4 控制開機鈴音播放邏輯
frameworks/base / cmds/bootanimation/audioplay.cpp
增加構造方法,增加全局變量 mNeedAudioPlay,
????bool mNeedAudioPlay = true;
????AudioAnimationCallbacks(bool needAudioPlay){
???????mNeedAudioPlay = needAudioPlay;
????}
????~AudioAnimationCallbacks(){}
?
playPart方法播放開機鈴聲方法.
void playPart(int partNumber, const Animation::Part& part, int playNumber) override {
????????// only play audio file the first time we animate the part
????????if (playNumber == 0 && part.audioData && playSoundsAllowed() && mNeedAudioPlay)???{//播放鈴音時增加判斷條件
????????????audioplay::playClip(part.audioData, part.audioLength);
????????}
????};
?
3.5 修改開機動畫播放控制邏輯
platform/frameworks/base / cmds/bootanimation/BootAnimation.cpp
這個文件中主要修改有三點:
3.5.1 構造方法中初始化 mBackDisplay,全局用其區分主屏還是背屏線程
BootAnimation::BootAnimation(sp<Callbacks> callbacks, bool shuttingDown, bool isBackDisplay)
????mBackDisplay = isBackDisplay;//在構造方法中初始化 mBackDisplay,全局用 mBackDisplay區分是主屏線程還是背屏線程
}
?
3.5.2修改 readyToRun(),通過 mBackDisplay區分創建不同的Surface,標記背屏Surface的layerStack=4096?往背屏送圖
status_t BootAnimation::readyToRun() {
????mAssets.addDefaultAssets();
????if(mBackDisplay){
????????mDisplayToken = SurfaceComposerClient::getBackDisplayToken();// getBackDisplayToken()是新增的接口,后面說明
????}else{
????????mDisplayToken = SurfaceComposerClient::getInternalDisplayToken();
????}
//獲取對應屏幕的displayToken,displayToken中封裝的有屏幕所有信息,后面會使用 mDisplayToken中的寬高創建對應的Surface
????if (mDisplayToken == nullptr)
????????return -1;
????DisplayInfo dinfo;
????status_t status = SurfaceComposerClient::getDisplayInfo(mDisplayToken, &dinfo);//從 mDisplayToken中取出信息存儲 dinfo
????// create the native surface
????sp<SurfaceControl> control;
???if(mBackDisplay){
???????control = session()->createSurface(String8("BackBootAnimation"),
???????????????????????dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);//用dinfo寬高創建SurfaceControl,最終會用SurfaceControl獲取Surface,實際上 dinfo.w, dinfo.h最終給了surface,而client端創建一個surface,對應在server端,SurfaceFlinger中創建一個一模一樣信息的Layer,SurfaceFlinger中的Layer,可理解為當前surface
?
???????SurfaceComposerClient::Transaction t;
???????????t.setLayer(control, 0x40000000)
?????????????.setLayerStack(control,4096)//這一句標記當前的Layer將會送給哪個顯示設備,默認為主屏,所以主屏開機動畫中是沒有這一句的.
?????????????.apply();
???}else{
???????control = session()->createSurface(String8("BootAnimation"),
???????????????????????dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
???????SurfaceComposerClient::Transaction t;
???????????t.setLayer(control, 0x40000000)
????????????.apply();
???}
????sp<Surface> s = control->getSurface();
????EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);// EGL_DEFAULT_DISPLAY 值為0,并且EGL庫中就只支持一個 EGL_DEFAULT_DISPLAY
????eglInitialize(display, nullptr, nullptr);
????eglChooseConfig(display, attribs, &config, 1, &numConfigs);
?
????surface = eglCreateWindowSurface(display, config, s.get(), nullptr);
?
????context = eglCreateContext(display, config, nullptr, nullptr);
????eglQuerySurface(display, surface, EGL_WIDTH, &w);
????eglQuerySurface(display, surface, EGL_HEIGHT, &h);
?
????if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
????????return NO_INIT
}
?
3.5.3 通過 mBackDisplay標記,查找不同的資源文件
void BootAnimation::findBootAnimationFile() {
????const bool playDarkAnim = android::base::GetIntProperty("ro.boot.theme", 0) == 1;
????static const char* bootFiles[] =
????????{APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE,
?????????OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE};
????static const char* shutdownFiles[] =
????????{PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE, ""};
?
????static const char* backBootFiles[] =
????????{PRODUCT_BACK_BOOTANIMATION_FILE};//背屏開機動畫文件
?
????static const char* backShutdownFiles[] =
????????{PRODUCT_BACK_SHUTDOWNANIMATION_FILE};//背屏關機動畫文件
?
????if(mBackDisplay){
????????for (const char* f : (!mShuttingDown ? backBootFiles : backShutdownFiles)) {
????????????if (access(f, R_OK) == 0) {
????????????????mZipFileName = f;
????????????????return;
????????????}
????????}
????}else{
????????for (const char* f : (!mShuttingDown ? bootFiles : shutdownFiles)) {
????????????if (access(f, R_OK) == 0) {
????????????????mZipFileName = f;
????????????????return;
????????????}
????????}
????}
}
?
4.新增接口說明
4.1 新增接口SurfaceComposerClient::getBackDisplayToken(),在Bootanimation.cpp的 readyToRun()方法中會用到,返回DisplayToken
4.1.1 frameworks/native / libs/gui/include/gui/SurfaceComposerClient.h
定義兩個接口:
//niexu add for back Screen boot animation?
?static std::optional<PhysicalDisplayId> getBackDisplayId();
????static sp<IBinder> getBackDisplayToken();
?
4.1.2 frameworks/native / libs/gui/SurfaceComposerClient.cpp?接口實現類,調用服務端的對應接口
std::optional<PhysicalDisplayId> SurfaceComposerClient::getBackDisplayId() {//這個接口其實在bootanim服務中沒用上
????return ComposerService::getComposerService()->getBackDisplayId();
}
sp<IBinder> SurfaceComposerClient::getBackDisplayToken() {
????return ComposerService::getComposerService()->getBackDisplayToken();//最終會調用到ISurfaceComposer.h中的getBackDisplayToken();
}
?
4.1.3 frameworks/native / libs/gui/include/gui/ISurfaceComposer.h?接口真正的實現類
????std::optional<PhysicalDisplayId> getBackDisplayId() const {
????????const auto displayIds = getPhysicalDisplayIds();//系統方法,默認返回數組,背屏back(),主屏front(),
????????return displayIds.empty() ? std::nullopt : std::make_optional(displayIds.back());
????}
?
????sp<IBinder> getBackDisplayToken() const {
????????????const auto displayId = getBackDisplayId();
????????????return displayId ? getPhysicalDisplayToken(*displayId) : nullptr;
????????}
5.SurfaceFlinger中的修改
SurfaceFlinger中主要修改點,在SurfaceFlinger的init初始化方法中,原生代碼,只是在此處初始化了主屏的displayState,背屏的displayState初始化流程貌似從Frameworks中的DisplayManagerService往下初始化的(這個猜測,還未仔細研究),這就導致了背屏開機動畫播放開始播放的時間點比主屏開機動畫播放的時間點晚(實際上背屏開機線程已經起來,送圖已經開始了一段時間,但dispalyState未初始化完成,導致背屏開機動畫part0中的圖片未顯示).為了解決這個問題,在SurfaceFlinger的init方法中,初始化主屏的displayState時,同時初始化背屏的displayState,即可實現同步播放.
5.1 SurfaceFlinger中初始化背屏displayState
frameworks/native / services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::onInitializeDisplays() {
????const auto display = getDefaultDisplayDeviceLocked();
????if (!display) return;
????const sp<IBinder> token = display->getDisplayToken().promote();
????LOG_ALWAYS_FATAL_IF(token == nullptr);
?
????// reset screen orientation and use primary layer stack
????Vector<ComposerState> state;
????Vector<DisplayState> displays;
????DisplayState d;
????d.what = DisplayState::eDisplayProjectionChanged |
?????????????DisplayState::eLayerStackChanged;
????d.token = token;
????d.layerStack = 0;//主屏display id
????d.orientation = DisplayState::eOrientationDefault;
????d.frame.makeInvalid();
????d.viewport.makeInvalid();
????d.width = 0;
????d.height = 0;
????displays.add(d);//add 到 DisplayState中
?
????//bug? add for sub screen boot animation?
????const auto backDisplay = getExternalDisplayDeviceLocked();//新增接口,通過hw層的背屏物理id,獲取hw層的PhysicalDisplayToken,再通過PhysicalDisplayToken獲取displayToken
????SLOGD("SurfaceFlinger backDisplay: %d", backDisplay? 1 : 0 );
????if(backDisplay){
????????const sp<IBinder> backToken = backDisplay->getDisplayToken().promote();//拿到背屏的displayToken,使用其封裝的屏幕信息,初始化背屏displayState
?
????????LOG_ALWAYS_FATAL_IF(backToken == nullptr);
?
????????DisplayState backDisplayState;
????????backDisplayState.what = DisplayState::eDisplayProjectionChanged |
?????????????????DisplayState::eLayerStackChanged;
????????backDisplayState.token = backToken;
????????backDisplayState.layerStack = 4096;//背屏的display id
????????backDisplayState.orientation = DisplayState::eOrientation90;//強制轉90度
????????backDisplayState.frame.makeInvalid();
????????backDisplayState.viewport.makeInvalid();
????????backDisplayState.width = 0;
????????backDisplayState.height = 0;
????????displays.add(backDisplayState);//將背屏DisplayState add到 displays中
????????setPowerModeInternal(backDisplay, HWC_POWER_MODE_NORMAL);
?????}
????setTransactionState(state, displays, 0, nullptr, mPendingInputWindowCommands, -1, {}, {});
????setPowerModeInternal(display, HWC_POWER_MODE_NORMAL);
????const nsecs_t vsyncPeriod = getVsyncPeriod();
????mAnimFrameTracker.setDisplayRefreshPeriod(vsyncPeriod);
?
????// Use phase of 0 since phase is not known.
????// Use latency of 0, which will snap to the ideal latency.
????DisplayStatInfo stats{0 /* vsyncTime */, vsyncPeriod};
????setCompositorTimingSnapped(stats, 0);
}
?
5.2 SurfaceFlinger中使用到的新增接口說明
frameworks/native / services/surfaceflinger/SurfaceFlinger.h
????sp<const DisplayDevice> getExternalDisplayDeviceLocked() const {//SurfaceFlinger.cpp中調用該方法,接著會往下調用自身的 getExternalDisplayDeviceLocked
????????????return const_cast<SurfaceFlinger*>(this)->getExternalDisplayDeviceLocked();
????????}
?
????sp<DisplayDevice> getExternalDisplayDeviceLocked() {
?????????if (const auto token = getExternalDisplayTokenLocked()) {
?????????????return getDisplayDeviceLocked(token);//返回供上層使用的displayToken
?????????}
?????????return nullptr;
????}
????sp<IBinder> getExternalDisplayTokenLocked() const {
????//返回背屏hw層的PhysicalDisplayToken
????????????const auto displayId = getExternalDisplayIdLocked();
????????????return displayId ? getPhysicalDisplayTokenLocked(*displayId) : nullptr;
????????}
????std::optional<DisplayId> getExternalDisplayIdLocked() const {
?????//返回hw層背屏物理id
????????????const auto hwcExternalDisplayId = getHwComposer().getExternalHwcDisplayId();
????????????return hwcExternalDisplayId ? ????getHwComposer().toPhysicalDisplayId(*hwcExternalDisplayId) : std::nullopt;
????????}
總結
以上是生活随笔為你收集整理的双屏异显开机动画实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 宅米网性能优化实践(内附小强点评)
- 下一篇: 第十六周 DYM 每日一题