background

As an Android developer, will certainly encounter such a situation, the user when playing with your app development, suddenly WeChat came the news, switch to WeChat, then still stay in WeChat watch video, chat, brush a circle of friends, and so on, you develop apps for the background, it is easy to appear at this time of mobile phone memory, App is kill the memory recovery, such as user finally talking day, brushed my circle of friends, come back to the app, for app will recover, if the developer processes is not good, would be a collapse of the situation, and definitely will be back a moment white, then displayed, so that the user experience is very bad. So how can we solve this situation? So much, our article officially began!

start

Here are the four different startup modes for an Activity on Android (just as a refresher, from the web summary) :

Standard: This is the default and standard Task mode. An Activity using this mode will construct an instance of an Activity and add it to the caller’s Task stack without any other factors affecting it. The Standard mode is definitely the best choice because it is logical and clear, so it is the default choice.

*

SingleTop: Basically the same as Standard, except when the requested Activity is at the top of the stack. Instead of adding a new instance to the Task stack, the Activity configured as a singleTop sends the new Intent to the top of the stack. The Activity at the top of the stack can handle the new Intent by overloading onNewIntent (of course, Or ignore…) . This mode reduces the overhead of repeating activities at the top of the stack, and avoids some strange behavior (imagine the user experience when several activities at the top of the stack are the same in a row, and then exit one after another…). , which is ideal for some list Activity displays that have updates. A living example is that in the Android default provided applications, Browser (BrowserBookmarkPage) bookmark Activity, is using singleTop.

*

SingleTask: An activity configured with this property has at most one instance, and it is in the root task, which we will use later in the process of being killed and restarted, which is our main interface MainActivity.

*

SingleInstance: The above singleTask is basically the same, but the singleInstance Activity is the only one in the stack. If other activities are involved, they are transferred to other tasks. In actual development, this Activity is seldom used.

Here is the activity lifecycle:





Let’s not introduce this life cycle, I believe that they are familiar with, have to understand their own Google or Baidu bar.

Focus on

Here’s our main point: What do we do if an application is killed in the background? Do you want to resume immediately or restart? Which method suits us better?

First of all, we need to know, why did the program get killed in the background? We didn’t shut it down manually.

Apps are forcibly killed in the background, forced to release when they run out of memory, and there are some nasty ROMs that forcibly kill background processes to free up the cache and improve the so-called user experience. (Note: If your code is messy, redundant, and consumes a lot of memory, your app is likely to be killed by the system when running in the background, so discipline yourself to develop good coding habits and watch out for memory leaks from now on.)

We all think Android ROM is gross, but at the same time, there are even more gross ways to get around these bottlenecks. Messy, because there is not a good constraint at the top, which is also the drawback of open source. Anyway. We still have to get our heads around these problems, or we’ll lose our jobs.

Let’s re-enact a familiar scene:

Assume: App A -> B -> C

In C activity, click the Home button to run in the background, open DDMS, select the App process, and forcibly kill.

Then select the App from “Recently opened Apps”, and the interface returned is C Activity. Assuming there are no static variables in the App, there will be no crash at this time. Click to return to B, and the INTERFACE B will be displayed after a short blank screen. But if B has a reference to a static variable and wants to get a value from that static variable, NullPointer is NullPointer.

There are a few points in the process of the above reproduction. Let’s expand it:

When an App is forcibly killed, the entire App process is killed and all variables are wiped out. Includes Application instances. Not to mention the static variables.

While the variables are cleared, Android offers some remedies. The activity stack is not cleared, that is, the A -> B -> C stack is still saved, but the ABC activity instances are not. So when YOU go back to your App, you’re still going to see page C. In addition, when an activity is killed, the system calls onSaveInstance to let you save some variables, but I personally feel that this is not enough for a large number of static variables. If I go back to B, it’s going to be blank because B is going to redraw, retrace the onCreate process, it’s going to take some time to render, so it’s going to be blank.

It’s going to be some of these points. If the App does not have static variable references, then NullPointer crash does not need to be resolved. Once you have static variables, or some global variables of Application, that’s dangerous. Such as login status, user profile and so on. These values are empty.

I’m sure some people would say, well, it doesn’t matter, why don’t we just put all our static variables in singletons? Then attach some persistent cache to it. If it is empty, fetch the cache again. Well, that’s definitely one way to do it, but it’s also a pain in the development process, requiring at least 50 percent more coding time to fully cover. Besides, there are so many teammates who help you dig holes. It’s hard to worry.

Since the App has been forcibly killed, why not go back to the first startup process, instead of sending the App back to D, start the App to A, so that all variables are initialized in the normal way, and there is no null pointer, right? Some people say the user experience is not good at all. But is it better to go back to the process or a NullPointer? Good to communicate, I believe that the product will not embarrass you. Of course, you can also use the example of iOS killing an App in a recently opened App, re-clicking the App, and the process will continue.

If you said the user has opened the C interface, so reopen is back to the C interface, so that the user experience will be better, if you think so, then you are a lot of time to prevent the recovery time don’t let your app crash, and in this way, it is better to let the app to go the whole process, so that more save time, And wouldn’t the user experience be better without having to worry about crashing at any moment?

So let’s think about how do we get it not to go back to C but to go back to the flow? So if you interrupt C’s initialization and go back to A, and press back, you don’t go back to C, B. Think about it.

Let’s instantiate this scenario first. A is the startup page of the App, B is the home page, and C is the secondary page

Set the home page launchMode to singleTask. The function of singleTask has been introduced in the introduction of the activity launchMode above.

In the BaseActivity onCreate to determine whether the App is forced to kill, strong kill does not go down, directly retrace the App process.

The home page plays a role to undertake or transfer, and all cross-level jumps need to be completed through the home page.

As a reminder, the solution to the above scenario can also be used to solve other related problems: Quit the App on any page and return to the home page on any page. The most important thing to know is launchMode

The specific implementation

AppStatusConstant

public static final int STATUS_FORCE_KILLED = - 1; // The application was killed in the background
public static final int STATUS_NORMAL = 2;  // Intent to MainActivity specifies the intent of the intent
public static final String KEY_HOME_ACTION = "key_home_action";// Return to the main page
public static final int ACTION_BACK_TO_HOME = 0; / / the default value
public static final int ACTION_RESTART_APP = 1;/ / be strong to killCopy the code

AppStatusManager

public int appStatus= AppStatusConstant.STATUS_FORCE_KILLED; // The initial value of APP state is not started and not in foreground state
public static AppStatusManager appStatusManager;
private AppStatusManager(a) {}public static AppStatusManager getInstance(a) {   
    if (appStatusManager == null{        
        appStatusManager = new AppStatusManager();
    }   
    return appStatusManager;
}
public int getAppStatus(a) {    
    return appStatus;
}
public void setAppStatus(int appStatus) {    
    this.appStatus = appStatus;
}Copy the code

BaseActivity (general content)

switch (AppStatusManager.getInstance().getAppStatus()) {
case AppStatusConstant.STATUS_FORCE_KILLED:        
    restartApp(); 
    break;    
case AppStatusConstant.STATUS_NORMAL:             
    setUpViewAndData();
    break;
}
protected abstract void setUpViewAndData(a);
protected void restartApp(a) {
    Intent intent = new Intent(this, MainActivity.class);    
    intent.putExtra(AppStatusConstant.KEY_HOME_ACTION,AppStatusConstant.ACTION_RESTART_APP);    
    startActivity(intent);
}Copy the code
Each activity that inherits from the parent activity should not implement interface initialization and data initialization in onCreat, because if killed, it will return to the normal life flow.

StartPageActivity configuration (configured in the oncreat() method, and before super()) :

AppStatusManager.getInstance(a).setAppStatus(AppStatusConstant.STATUS_NORMAL); // Enter the application initialization setting to the unlogged stateCopy the code

MainActivity (configures the main interface of singleTask)

@Override
protected void restartApp(a) {
    super.restartApp();
    Toast.makeText(getApplicationContext(),"The application was reclaimed and restarted",Toast.LENGTH_LONG).show();    
    startActivity(new Intent(this, StartPageActivity.class));    
    finish();
}
@Override
protected void onNewIntent(Intent intent) {    
    super.onNewIntent(intent);
    int action = intent.getIntExtra(AppStatusConstant.KEY_HOME_ACTION,AppStatusConstant.ACTION_BACK_TO_HOME); 
    switch (action) {        
    case AppStatusConstant.ACTION_RESTART_APP:            
        restartApp();            
        break; }}Copy the code

When the app is open, start StartPageActivity and set the status of the app to Normal. Remember, make sure it’s set because the default is killed.

When the app is killed, all data will be reclaimed, so the app status set before will also be set to the default state, that is, the killed state, so when the app is opened again, the status is killed, and the process of restart will be started. Why jump to MainActivity first? Because MainActivity is configured to Sing tasks, when jumping to this screen, MainActivity will be placed at the top of the Activity Task. Other activities will be destroyed by default. Use this technique to destroy other activities. The last step is to restart StartPageActivity. That’s the process.

The general implementation is described above, I advocate the purpose is to spend the least time, write the best code, achieve the best experience! Before also referred to a lot of online gods to achieve the way, but I think the above implementation should be a more complete one.