Android system startup animation ends and black screen optimization occurs before entering the launcher

Optimization of black screen before entering launcher after Android system startup animation

Foreword
I was working on a project recently and found that after the system startup animation ends, there will be a 6S black screen before entering the launcher interface, which seriously affects the user experience.

Luancher
1. The first thing I thought of was to check whether the problem was with the launcher itself, so I wrote a demo. Nothing was done in the demo. As expected, the black screen time was reduced by a few seconds. After investigation, I found that it was the oncreate of the launcher’s mainActivity that did some expensive things. For time-consuming operations, you can put some time-consuming operations into sub-threads or move them to other places.

FallbackHome

2. The influence of the launcher has been eliminated, but there will still be a black screen time of about 4 seconds. Let’s first take a look at the log when the system is started. After analysis and filtering, we found that there are the following two important logs from the end of bootanimation to the start of Activity.

You can see that before starting Launcher, the system first starts an Activity called FallbackHome. The time difference between the two is exactly about 2 seconds!
So what is FallbackHome? After some searching, the following paragraph is a relatively common and clear explanation: FallbackHome is an activity of the native setting and is configured with DirectBoot mode. When the launcher starts, it will first launch to this interface. After the user unlocks it, finish will be called to end the interface and enter the real launcher interface.
If it is not unlocked, wait for the ACTION_USER_UNLOCKED broadcast before starting the Launcher. The launcher in non-DirectBoot mode takes 4 seconds to wait for the system to broadcast ACTION_USER_UNLOCKED after finishBooting. FallbackHome is a new page added in response to the DirectBoot function.
Regarding what DirectBoot mode is, I will not explain it here. For specific content, you can read Google’s official documentation: Support “direct startup” mode. In the AndroidManifest.xml of the setting, the DirectBoot mode is configured, so that FallbackHome will be loaded and started before Launcher.


Modification process
Since our product does not require the lock screen function, you can directly set the Launcher to DirectBoot mode by default, so that the Launcher can be loaded and started first.

Author: Little Girl’s Big Bear
Original link: https://blog.csdn.net/xl19862005/article/details/108597195
Copyright belongs to the author. For commercial reprinting, please contact the author for authorization. For non-commercial reprinting, please indicate the source.

Start-up animation end time
3. After the second step of operation, it is found that the black screen time has become shorter, but there is still a black screen time of about 1.5S.
1. Check the cause
After a whole day of comparison and investigation, the final conclusion is that the startup animation ends too early. The startup animation ends before entering the Launcher, so a black screen will appear. According to the above situation, the solution is to delay the startup. The end time of the animation. The launcher ends the startup animation after displaying the first window.
2. Relevant code to solve the problem of black screen for 2 seconds before entering Launcher

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. Core function analysis and implementation of the solution to the problem of black screen for 2 seconds before entering Launcher
The first option is to add attributes to the boot animation BootAnimation.cpp to delay the end of the boot animation, and then end the boot animation when the Launcher is displayed.
3.1 Delay the end of the boot animation and use a flag to end the boot animation
Handle animation end in BootAnimation.cpp
frameworks\base\cmds\bootanimation\BootAnimation.cpp
checkExit() in

bool BootAnimation::playAnimation(const Animation & amp; 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 & amp; 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() & amp; & amp; !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 & amp; & amp; (!exitPending() || part.playUntilComplete) ; j + + ) {<!-- -->
            
                const Animation::Frame & amp; frame(part.frames[j]);
                nsecs_t lastFrame = systemTime();

                if (r > 0) {<!-- -->
            
                    glBindTexture(GL_TEXTURE_2D, frame.tid);
                } else {<!-- -->
            
                    if (part.count != 1) {<!-- -->
            
                        glGenTextures(1, & amp;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, & amp;w, & amp;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_SCISSO_TEST);
                    while (head != tail) {<!-- -->
            
                        const Rect & amp; r2(*head + + );
                        glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height());
                        glClear(GL_COLOR_BUFFER_BIT);
                    }
                    glDisable(GL_SCISSO_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 & amp; & amp; mTimeIsAccurate & amp; & amp; 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 & amp; & amp; 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() & amp; & amp; !part.count & amp; & amp; mCurrentInset >= mTargetInset)
                break;
        }
#ifdef BOOTANIMATION_EXT
        if (mShuttingDown & amp; & amp; mfd == -1 & amp; & amp; 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 & amp; 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 & amp; frame(part.frames[j]);
                glDeleteTextures(1, & amp;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();
}
}

Responsible for playing the startup animation in playAnimation, and then calling checkExit() after playing the startup animation to prepare to exit the startup animation. When EXIT_PROP_NAME is 1, exit the startup animation.
You can see that the value of exitnow is used to determine whether to end the animation.
So add custom attributes to determine whether to end the animation
Modify as follows:

static const char EXIT_ANIM_NAME[] = "sys.bootanim.exit";//Custom attribute
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 determines whether to end the animation based on the flag bit in checkBootAnimationCompleteLocked()
After AMS is started, WMS’s checkBootAnimationCompleteLocked() will be called to determine whether to send the boot completion action.

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"))){<!-- -->
            //Determine whether the flag is already 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 Add the exit system flag and change it to 1 when entering Launcher
The management of activity is managed in ActivityRecord.java. Now look at the relevant code of 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 & amp; & amp;shortComponentName.contains("yourlauncher")){<!-- -->
     + //Determine whether your launcher has been started
     + SystemProperties.set("persist.sys.bootanim.exit", "1");//Set the startup completion flag
    + }
     + // add code end
 }

OnWindowsDrawn is responsible for drawing the Launcher homepage, so it is judged here that if shortComponentName is the launcher package name, set the system property value to exit the startup animation.