background

Recently, WE have been optimizing the startup of App. In order to achieve fast startup effect, we removed the SplashActivity page of our App, replaced it with the windowBackground of MainActivity, and finally replaced it with the theme of App. Give users a quick response experience.

<style name="AppWelcomeTheme" parent="BaseAppTheme">
        <item name="android:windowBackground">@drawable/flash_bg</item>
</style>
//flash_bg.xml<? xml version="1.0" encoding="utf-8"? > <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <solid android:color="#fff"/> </shape> </item> <! --> <item Android :gravity="center"
        android:top="60dp">
        <bitmap
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:src="@drawable/ic_splash_logo" />
    </item>
</layer-list>
Copy the code

AndroidManifest.xml

<activity
            android:name=".ui.main.MainActivity"
            android:theme="@style/AppWelcomeTheme"
Copy the code

Such a MainActivity starts with a preview window, giving the user a quick response experience. When your activity wants to restore the original theme, you can call setTheme(r.style.appTheme) before calling super.oncreate () and setContentView(), as follows:

public class MyMainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // Make sure this is before calling super.onCreate
    setTheme(R.style.AppTheme);
    super.onCreate(savedInstanceState);
    // ...}}Copy the code

However, there was a problem with the optimization. The startup mode of our MainActivity was SingleTask. After I removed the flash screen page, no matter how many pages were opened, I pushed the application to the background and then started it back to the MainActivity.

Troubleshoot problems

When troubleshooting the problem, I first checked whether there was such a problem in the previous version (no problem was found), and then checked my code submission record. I found that the main modification I made in Androidmanifest.xml was to remove the flash screen interface, and the homepage MainActivity was directly launched when I clicked the App. However, I thought that the problem was caused by the introduction of dynamic link. I thought that the dynamic link needed to be transferred from the startup interface. After I removed all the dynamic links, I found that the problem still existed.

I’ve been working on Android development for years. When did MainActivity change to SingleTask and why didn’t I notice it? Is it because of the latest CHANGES to the API version? Why is the new change so bad? Then I started testing with a different version of the VIRTUAL machine, or with a different targetSdkVersion, and the results were the same, MainActiivy every time. I fell into a deep thought again. Is it an illusion that MainActiviy has been using SingleTask for so many years? But why didn’t previous apps have this problem? (SplashActivity used to be a SplashActivity page, but now it’s not a SplashActivity page.)

Later, I carefully determined the content of the submission record, and found that the possible influence was that I removed the flash screen interface, but there was no such problem after I restored the flash screen page. After confirming the problem, it was the problem of whether the flash screen page was illuminated, or the problem caused by the setting of the startup interface as SingleTask. FLAG_ACTIVITY_CLEAR_TOP: Intent.flag_activity_clear_top: intent.flag_activity_clear_top: intent.flag_activity_clear_top: intent.flag_activity_clear_top Without causing MainActivity to start every time.

In-depth analysis of SingleTask related source code

But the online articles did not carefully analyze why caused the problem, I looked at some Activity start process source code analysis, only a brush with a method name, and did not analyze the process, there is no way to only do it yourself.

Start-up flow chart

You can look at the startup module in activity.startActivity in the diagram, and then look at the flow. It’s easy to see where the general method is coming from. That’s the benefit of being familiar with the startup process, as well as drawing it.

startActivityUnchecked

Say the general logic of startActivityUnchecked related code, first get a reusedActivity from getReusableIntentActivity, because this time is hot start, we have been created before the Activity, There are no new activities to insert into the stack, so the return is not null;

Enter if (reusedActivity! = null) {isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK); Set the startup Activity as our mStartActivity(MainActivity), so when the APP starts the Activity as MainActivity, set the startup mode as SingleTask or SingleInstance. Every time you click the app icon, you will see the interface MainActivity.

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
            ActivityRecord[] outActivity, boolean restrictedBgActivity) {.../ / get from getReusableIntentActivityActivityRecord reusedActivity = getReusableIntentActivity(); ...// Enter the loop when it is not empty
if(reusedActivity ! =null) {
            // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
            // still needs to be a lock task mode violation since the task gets cleared out and
            // the device would otherwise leave the locked task.· · · · · ·// This code path leads to delivering a new intent, we want to make sure we schedule it
// as the first operation, in case the activity will be resumed as a result of later
// operations.
//isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK) Indicates that the startup mode is SingleInstance or SingleTask
if((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) ! =0
        || isDocumentLaunchesIntoExisting(mLaunchFlags)
        || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
    final TaskRecord task = reusedActivity.getTaskRecord();

    // In this situation we want to remove all activities from the task up to the one
    // being started. In most cases this means we are resetting the task to its initial
    // state.
   // In most cases we might be ready to clear the current task or go back to its original state
    final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,
            mLaunchFlags);

    // The above code can remove {@code reusedActivity} from the task, leading to the
    // the {@code ActivityRecord} removing its reference to the {@code TaskRecord}. The
    // task reference is needed in the call below to
    // {@link setTargetStackAndMoveToFrontIfNeeded}.
    if (reusedActivity.getTaskRecord() == null) {
        reusedActivity.setTask(task);
    }

    if(top ! =null) {
      	// Whether it is the root activity
      	//boolean frontOfTask; // is this the root activity of its task?
        if (top.frontOfTask) {
            // Activity aliases may mean we use different intents for the top activity,
            // So make sure the task now has the identity of the new intent
            top.getTaskRecord().setIntent(mStartActivity);
        }
    		// The onNewIntent of the Activity will be called. Once mStartActivity is called, we see mStartActivity every time because we also set SingleTask or SingleInstancedeliverNewIntent(top); }}} ··· ··Copy the code

Let’s look at getReusableIntentActivity method, take a look at the method of annotation, will soon understand that work, so the return is not null, so will enter the above judgment logic

/** * Decide whether the new activity should be inserted into an existing task. Returns null * if not or an ActivityRecord with the task into which the new activity should be added. */
private ActivityRecord getReusableIntentActivity(a) {
    // We may want to try to place the new activity in to an existing task. We always
    // do this if the target activity is singleTask or singleInstance; we will also do
    // this if NEW_TASK has been requested, and there is not an additional qualifier telling
    // us to still place it in a new task: multi task, always doc mode, or being asked to
    // launch this as a new task behind the current one.
Copy the code

Then look at deliverNewIntent called deliverNewIntentLocked, which ultimately determines which Activity’s onNewIntent will be called, which is our mStartActivity

/** * Deliver a new Intent to an existing activity, so that its onNewIntent() * method will be called at the proper time. */
final void deliverNewIntentLocked(int callingUid, Intent intent, String referrer) {
    // The activity now gets access to the data associated with this Intent.
Copy the code

Possible problems with the first installation

During the development process, when an APP is installed, directly click to open it on the installation interface. When we enter the home page of the APP, press the home button to return to the desktop, and then click the application icon, we will find that we do not directly enter the home page, but enter the flash screen page of the APP, and then enter the home page. Repeat this step all the time. At this point, we press the back key to return to the desktop, but not directly back to the previous open multiple home page. But that wouldn’t be the case if we didn’t just open it up, but hit the app on the desktop.

The solution

If you have a splash screen, or if you don’t have a splash screen, like I did with MainActivity, then you add this code directly to the onCreate method of that splash screen. Specific analysis can be found in the following article

if (! This.istaskroot ()) {// The current class is not the root of the Task, so before launching the Intent = getIntent(); if (intent ! = null) { String action = intent.getAction(); If (intent.hascategory (intent.category_launcher) && intent.action_main.equals (action)) {// The current class starts from the desktop. // Finish opens the existing Activity return in the Task. }}}Copy the code

conclusion

Be sure not to use the splash screen in your startup screen (if you want to set the MainActivity’s WindowBackground to splash screen, remove the splash screen, Set the startup mode to “SingleTask” or “SingleInstance”. Once set, either soft or hot startup will start the App from this startup screen. Do not do this unless you need to. If you want to achieve a stack-clearing effect similar to SingleTask, you can use singleTop in combination with the corresponding Flag (note that Standard creates a new instance with any Flag).

Finally said another case, if you must want to set the start page to SingleTask/SingleInstance can’t live (not set), that also is there a way to, Is to add a splash screen interface (SplashActivity set to SingleTask/SingleInstance), and then start the MainActivity, here be sure to note that the splash screen interface (SplashActivity) be sure to turn off in time,, Also add the following code to the onCreate method of the splash screen, otherwise every time you click on the app icon it will start from the splash screen. But then the above optimization to remove the splash screen and start quickly doesn’t make sense.

@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i(TAG, "onCreate: ========="); // If (! isTaskRoot()) { Intent intent = getIntent(); if (intent ! = null) { if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(intent.getAction())) { finish();  return; } } } Intent intent = new Intent(this, MainActivity.class); startActivity(intent); finish(); }Copy the code

Refer to the article

Blog.csdn.net/Candicelijx…