雙屏異顯開機動畫實現
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;
}