Say it one last time! ! Do not set SingleTask/SingleInstance on your App startup interface

background

Recently, I am doing App startup optimization. In order to achieve the effect of fast startup, we removed the splash screen page of our App (SplashActivity displays a fixed picture), replaced it with the MainActivity background (windowBackground), and finally replaced it with the App theme. 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>

AndroidManifest.xml

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

When such a MainActivity starts, it will first display a preview window to give users a quick response experience. When the activity wants to restore the original theme, it can be called before super.onCreate()and setContentView()by setTheme(R.style.AppTheme), 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);
    // ...
  }
}

However, there was a problem with optimization . The startup mode used by our MainActivity was SingleTask. After I removed the splash screen, no matter how many pages I opened, I pushed the application to the background and started it back to the main page (MainActivity) . This is a serious problem. Fortunately, the problem was discovered in a timely manner.

Troubleshooting

When troubleshooting, first check whether the previous version has the problem (and no problem was found), and then check my code submission record, I found that the main modification I made in AndroidManifest.xml removed the splash screen interface, and clicked App The main activity is launched directly, but the pitfall is that I thought it was a problem caused by the introduction of dynamic link, and thought that the dynamic link needs to be passed sequentially from the startup interface. After I removed all the dynamic links, I found that the problem still exists. Troubleshoot the problem.

As a result, I fell into self-doubt. I have been doing Android development for several years. When (MainActivity) is set to SingleTask, there will be such a change, why I haven't noticed it? Could it be the modification brought about by the changes of the latest api version, why is the new modification so cheating? Then I started to test with different versions of virtual machines, or set different targetSdkVersion to test, the results were the same, each time it was MainActiivy. I fell into deep thought again. Is it an illusion that the SingleTask that MainActiviy has used for so many years? But why didn't the previous apps have this problem. (Actually, there was a SplashActivity page before, but there is no SplashActivity anymore)

Later, I carefully determined the content of the submission record, and found that I removed the splash screen interface that may affect it. The effect of restoring the splash screen page did not have this problem. After determining the problem, it was the problem of whether the splash screen page was photographed or not. The problem is caused by the startup interface being set to SingleTask. Later, I saw some solutions on the Internet, mainly by setting the startup mode to standard or SingleTop, and then adding Flag to Intent.FLAG_ACTIVITY_CLEAR_TOP to solve, or to achieve the stack clearing effect similar to this SingleTask without causing every startup It is MainActivity.

In-depth analysis of SingleTask related source code

However, all the articles on the Internet did not carefully analyze why the problem was caused. I also read some source code analysis of Activity's startup process. I only took a certain method name and did not analyze the process. I had no choice but to do it myself.

Start flow chart

You can see the startup module in Activity.startActivity in the figure, and then take a look at the process. It is easy to see where the general method appears. This is the benefit of being familiar with the startup process and the benefit of drawing.

startActivityUnchecked

Let me talk about the general logic of startActivityUnchecked related code. Get a reusedActivity from getReusableIntentActivity. Because this is a hot start, our Activity has been created before, and there is no new Activity to be inserted into the stack, so the return is not empty;

Enter the if (reusedActivity != null) { judgment logic, the following isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)conditions are also established, and then enter the next logical judgment, and then judge whether it is the root Activity, set the started Activity to our mStartActivity (MainActivity), so when the APP start Activity is MainActivity, set the start mode to SingleTask or SingleInstance, the interface you see every time you click the app icon is 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) {
  ···
  //从getReusableIntentActivity中获取  
  ActivityRecord reusedActivity = getReusableIntentActivity();  
  ···
//不为空时进入该循环   
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)表示启动模式为或者SingleInstance或者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.
   //大多数情况下我们可能准备清空当前task或者回到task的初始状态
    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) {
      	//是否为根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.		 //设置启动Activity为根Activity
            top.getTaskRecord().setIntent(mStartActivity);
        }
    		//将会调用该Activity的onNewIntent,一旦调用了mStartActivity,因为我们也设置了SingleTask或者SingleInstance,所以我们每次看到的都是mStartActivity
        deliverNewIntent(top);
    }
}
} 
···
···
}

Let's take a look at the getReusableIntentActivity method first, look at the annotations of the method, and soon understand the role, so the return is not null, so it 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() {
    // 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.

Let's take a look at the deliverNewIntentLocked that is called in deliverNewIntent, and finally decide 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.

Problems that may be caused by the first installation

During the development process, when an app is installed, click to open it directly on the installation interface. We entered the home page of the app. At this time, we press the home button to return to the desktop, and then click the application icon. We will find that instead of directly entering the home page, we first enter the splash screen page of the app, and then enter the home page. Repeat this step forever. At this time, we press the back key to return, and found that we did not return to the desktop directly, but returned to the multiple homepages that we opened before. But if we don't open it directly at the beginning of the installation, but click on the application to enter on the desktop.

solution

In your splash screen interface, or there is no splash screen interface, if the startup interface above is MainActivity directly, then you can directly add the following code in the onCreate method of the interface. The specific analysis can be seen in the following article

if (!this.isTaskRoot()) { // 当前类不是该Task的根部,那么之前启动
            Intent intent = getIntent();
            if (intent != null) {
                String action = intent.getAction();
                if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(action)) { // 当前类是从桌面启动的
                    finish(); // finish掉该类,直接打开该Task中现存的Activity
                    return;
                }
            }
        }

to sum up

Be careful not to set the startup mode to SingleTask or SingleInstance in your startup interface (if you want to set the windowbackground of MainActivity as a splash screen interface, remove the splash screen page, and start MainActivity directly to give users a feeling of quick startup), Once set, regardless of soft start or hot start, start the App from the start interface, unless you have special requirements, don't set it like this. If you want to achieve a stack clearing effect similar to SingleTask, you can use standard or singleTop in combination with the corresponding Flag.

Finally, let me talk about another situation. If you must set to SingleTask/SingleInstance on the startup page (you can't live without setting), there is also a way to add a splash screen interface (SplashActivity is set to SingleTask/SingleInstance), and then Start MainActivity, please pay attention here, the splash screen interface (SplashActivity) must be closed in time, and at the same time add the following code to the onCreate method of the splash screen page, otherwise every time you click the app icon, the splash screen page will start to display Up. But the above optimization of removing the splash screen page and quick start is meaningless.

@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();
    }

notes

[360°all-round performance tuning]

In this note, I have integrated the knowledge points of Android-360° all-round performance optimization, as well as the practical experience of hundreds of millions of user apps such as WeChat, Taobao, Douyin, Toutiao, Gaode Maps, Youku, etc. in performance optimization. A set of system knowledge notes PDF, from theory to practice, involving all the knowledge points of Android performance optimization, up to 721 pages of e-book! I believe that after reading this document, you will have a more systematic and in-depth understanding of the Android performance tuning knowledge system and various solutions.

You can download it directly for free on my GitHub after you like + follow .

Reference article

blog.csdn.net/Candicelijx…

Guess you like

Origin blog.csdn.net/Androiddddd/article/details/109999522