Android系统开机动画结束进入launcher前黑屏优化
前言
最近在做一个项目,发现系统开机在开机动画结束后会有6S的黑屏才进入launcher界面,严重影响使用体验。
Luancher
1、首先想到的是排查是不是launcher本身的原因,于是写了一个demo,demo里面什么都不做,果然黑屏的时间减少了几秒,经过排查发现是luancher的mainActivity的oncreate里面做了一些耗时的操作,可以把一些耗时的操作放到子线程里面,或者移到别的地方。
FallbackHome
2、排除了launcher的影响,但是还是会有4S左右的黑屏时间,我们先来看一下系统开机时的log,经过分析过滤,发现在bootanimation结束到启动Activity的时候,有如下两条重要的log
可以看到在启动Launcher前,系统先去启动了一个叫FallbackHome的Activity,两者的时间差正好是2秒左右!
那么什么是FallbackHome呢?经过一番查找,如下这段比较靠普,说的比较明白的解释:FallbackHome是原生setting的一个activity,且配置了DirectBoot mode。launcher启动的时候会先启动到这个界面,用户解锁后,才会调用finish,结束该界面,从而进入到真正的launcher界面。
若未解锁就等待ACTION_USER_UNLOCKED广播后再去启动Launcher。非DirectBoot模式下的launcher耗时4s就是在等待finishBooting后的系统广播ACTION_USER_UNLOCKED。FallbackHome就是应DirectBoot功能而新增的一个页面。
关于什么是DirectBoot模式,这里不再进行说明,具体内容可以阅读google官方的文档:支持“直接启动”模式在setting的AndroidManifest.xml里面,配置了DirectBoot模式,这样FallbackHome就会先于Launcher加载启动了
修改过程
由于我们的产品是不需要锁屏功能的,可以直接把Launcher默认设置为DirectBoot模式,从而让Launcher优先加载启动。
作者:小妞的大熊
原文链接:https://blog.csdn.net/xl19862005/article/details/108597195
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处
开机动画结束时间
3、经过第二步的操作发现黑屏的时间变短了,但还是有1.5S左右的黑屏时间。
1.排查原因
经过一整天的对比排查最后得到的结论是开机动画结束的时间过早导致的,还没有进入Launcher就结束了开机动画,所以就会出现黑屏的现象,根据上述情况,解决的办法就是延迟开机动画的结束时间,launcher显示第一个窗口后结束开机动画。
2.进入Launcher前黑屏2秒的解决办法的相关代码
frameworks\base\cmds\bootanimation\BootAnimation.cpp
frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java
frameworks\base\services\core\java\com\android\server\am\ActivityRecord.java
3.进入Launcher前黑屏2秒的解决办法的核心功能分析和实现
首选需要在开机动画BootAnimation.cpp中增加属性来延迟结束开机动画,然后在Launcher显示的时候,再结束开机动画
3.1延时结束开机动画,用一个标志位来结束开机动画
在BootAnimation.cpp处理动画结束
frameworks\base\cmds\bootanimation\BootAnimation.cpp
中的checkExit()
bool BootAnimation::playAnimation(const Animation& animation)
{
const size_t pcount = animation.parts.size();
nsecs_t frameDuration = s2ns(1) / animation.fps;
const int animationX = (mWidth - animation.width) / 2;
const int animationY = (mHeight - animation.height) / 2;
ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms,folder count is %zu",
mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime(), pcount);
#ifdef BOOTANIMATION_EXT
scaleSurfaceIfNeeded();
#endif
for (size_t i=0 ; i<pcount ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
ALOGD("folder%zu pictures count is %zu",i,fcount);
glBindTexture(GL_TEXTURE_2D, 0);
// Handle animation package
if (part.animation != nullptr) {
playAnimation(*part.animation);
if (exitPending())
break;
continue; //to next part
}
for (int r=0 ; !part.count || r<part.count ; r++) {
// Exit any non playuntil complete parts immediately
if(exitPending() && !part.playUntilComplete)
break;
mCallbacks->playPart(i, part, r);
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);
}
int w, h;
initTexture(frame.map, &w, &h);
}
const int xc = animationX + frame.trimX;
const int yc = animationY + frame.trimY;
Region clearReg(Rect(mWidth, mHeight));
ALOGD_ANIMATION("subtractSelf Surface xc is %d, yc is %d,xc+frame.trimWidth is %d,yc+frame.trimHeight is %d",
xc, yc, (xc+frame.trimWidth),(yc+frame.trimHeight));
clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight));
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 - frame.trimHeight) / 2)
// which is equivalent to mHeight - (yc + frame.trimHeight)
glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight),
0, frame.trimWidth, frame.trimHeight);
if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
}
handleViewport(frameDuration);
eglSwapBuffers(mDisplay, mSurface);
nsecs_t now = systemTime();
nsecs_t delay = frameDuration - (now - lastFrame);
ALOGD_ANIMATION("frameDuration=%" PRId64 "ms,this frame costs time=%" PRId64 "ms, delay=%" PRId64 "ms", ns2ms(frameDuration), 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, nullptr);
} while (err<0 && errno == EINTR);
}
if (!mShuttingDown) {
checkExit();
}
}
usleep(part.pause * ns2us(frameDuration));
// For infinite parts, we've now played them at least once, so perhaps exit
if(exitPending() && !part.count && mCurrentInset >= mTargetInset)
break;
}
#ifdef BOOTANIMATION_EXT
if (mShuttingDown && mfd == -1 && mWaitForComplete) {
ALOGD("shutdown animation part1 finished, quit");
property_set("service.bootanim.end", "1");
}
#endif
}
// Free textures created for looping parts now that the animation is done.
for (const Animation::Part& part : animation.parts) {
if (part.count != 1) {
const size_t fcount = part.frames.size();
for (size_t j = 0; j < fcount; j++) {
const Animation::Frame& frame(part.frames[j]);
glDeleteTextures(1, &frame.tid);
}
}
}
#ifdef BOOTANIMATION_EXT
// SPRD: stop soundplay
if (playSoundsAllowed()) {
soundstop();
}
#endif
return true;
}
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();
mCallbacks->shutdown();
}
}
在playAnimation中负责播放开机动画,然后在播放完开机动画调用checkExit()准备退出开机动画,当EXIT_PROP_NAME为1时退出开机动画
可以看到根据exitnow的值来判断是否结束动画
所以增加自定义属性来判断是否结束动画
修改如下:
static const char EXIT_ANIM_NAME[] = "sys.bootanim.exit";//自定义属性
void BootAnimation::checkExit() {
// Allow surface flinger to gracefully request shutdown
char value[PROPERTY_VALUE_MAX];
+ char jvalue[PROPERTY_VALUE_MAX];
property_get(EXIT_PROP_NAME, value, "0");
+ property_get(EXIT_ANIM_NAME, jvalue, "0");
int exitnow = atoi(value);
+ int jexitnow = atoi(jvalue);
if (exitnow) {
+ if(jexitnow == 0) {
+ retrun;
}
requestExit();
mCallbacks->shutdown();
}
}
3.2 WindowManagerService.java 在checkBootAnimationCompleteLocked()中根据标志位判断是否结束动画
在AMS启动完毕后会调用WMS的checkBootAnimationCompleteLocked()来判断是否发送开机完成的动作
frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java
private boolean checkBootAnimationCompleteLocked() {
//if (SystemService.isRunning(BOOT_ANIMATION_SERVICE)) {
if(!"1".equals(SystemProperties.get("service.bootanim.exit", "0"))){
//判断标志位是否已经为1
mH.removeMessages(H.CHECK_IF_BOOT_ANIMATION_FINISHED);
mH.sendEmptyMessageDelayed(H.CHECK_IF_BOOT_ANIMATION_FINISHED,
BOOT_ANIMATION_POLL_INTERVAL);
if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Waiting for anim complete");
return false;
}
if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Animation complete!");
return true;
3.3在进入Launcher时增加退出系统标志位修改为1
对于activity的管理是在ActivityRecord.java中负责管理的现在看下ActivityRecord.java的相关代码
frameworks\base\services\core\java\com\android\server\am\ActivityRecord.java
public void onWindowsDrawn(boolean drawn, long timestamp) {
synchronized (mAtmService.mGlobalLock) {
mDrawn = drawn;
if (!drawn) {
return;
}
final WindowingModeTransitionInfoSnapshot info = mStackSupervisor
.getActivityMetricsLogger().notifyWindowsDrawn(getWindowingMode(), timestamp);
final int windowsDrawnDelayMs = info != null ? info.windowsDrawnDelayMs : INVALID_DELAY;
final @LaunchState int launchState = info != null ? info.getLaunchState() : -1;
mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
windowsDrawnDelayMs, launchState);
mStackSupervisor.stopWaitingForActivityVisible(this);
finishLaunchTickingLocked();
if (task != null) {
task.hasBeenVisible = true;
}
}
+// add core start
+if(shortComponentName!=null&&shortComponentName.contains("yourlauncher")){
+//判断是否已经启动你launcher
+ SystemProperties.set("persist.sys.bootanim.exit", "1");//设置启动完成标志
+}
+// add code end
}
在onWindowsDrawn负责进行Launcher首页的绘制,所以在这里判断如果shortComponentName是launcher包名就设置系统属性值退出开机动画