If there’s one thing that front-end, back-end, and even game developers have in common, it’s that we all hate bugs in our apps, especially when those bugs cause them to crash. Monitoring these increasing crashes in your application after it has been published is a very unpleasant experience.
Regardless of the business logic of the application, it is possible to have some strange crashes due to running system or platform problems. In Android, restoring an application from a background state can cause crashes — such crashes are accidental, and it’s difficult to understand the exact cause of the crash and resolve the problem just by looking at the crash log, but this article discusses such problems and how to resolve them.
The problem
As I monitored the crash logs for my products, I noticed a number of problems growing. The application seemed to work well under normal testing conditions and crashed irretrievably until the application came to the foreground from a background task.
Each Android application runs in its own process, and the operating system has allocated some memory for that process. When the user puts the application in the background while interacting with other applications, the operating system will terminate your application process if the application does not have enough available memory. This usually happens when the foreground is running another application that requires more phone memory (RAM).
When an application process is terminated, all singletons and temporary data are lost at the same time. Now, if you return to your application, a new process is created, and your application resumes the Activity by executing the Resume function from the top of the Activity stack where you left it.
Because all of your singleton objects are lost at this point, the Activity will crash and exit when it tries to access the same object with a null pointer exception.
That’s a problem. Before we move on to solutions, let’s restate the situation.
Repetition collapse
- Any application that runs using ADB run instructions (such as Android Studio) on an emulator or an actual device connected via a USB cable.
- Navigate to any page, then press the Home button.
- To get the process ID (PID) of the application, open the terminal and type the following command.
adb shell pidof com.darktheme.example
Copy the code
The syntax for this command is adb shell pidof APP_BUNDLE_ID
Make a note of the PID you see on the terminal window (this can be used to verify that the existing application process has been terminated and a new process started when we resume the application).
- Type the following terminal command to terminate your application process
adb shell am kill com.darktheme.example
Copy the code
Now, open your application from background tasks and check to see if the application crashes. If so, don’t worry, we’ll discuss how to deal with this in the next section. If not, you can breathe a sigh of relief, because you deserve it.
Note That after starting the application from the background, obtain the PID of the process to which the application belongs again. If the PID you wrote down in step 3 is equal to the new PID, the process has not been terminated.
Suggested solutions
There are two ways to solve this problem. Depending on your situation, you can decide which approach to take to advance the problem:
Solution 1:
A simple solution is to have the application check to see if our existing application process has been terminated and recreated when the user restores the application from the background. If so, you can navigate back to the startup screen to make it look like an application’s initialization screen.
You can put the following code in BaseActivity:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState ! = null) {val currentPID = Android.os.process.mypId ().toString() = savedInstanceState. Get string (PID)) {/ / if the current PID and preservation of PID are not identical, means that the new process is created, Intintent = intent (applicationContext, SplashActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK startActivity(intent) finish() } } } override fun onSaveInstanceState(bundle: Bundle) { super.onSaveInstanceState(bundle) bundle.putString(PID, android.os.Process.myPid().toString()) }Copy the code
- Through the overwrite
onSaveInstanceState()
Function, you can save your PID package. - in
onCreate()
In this method, you need to compare the current PID with the packaged saved PID. - If the current process is a recreated process, redirect the navigation to Splash Activity.
When the user navigates back to the terminated application from the background, the application is restarted from SplashActivity as if it were a new startup.
This prevents the application from accessing data that may have been lost during process rebuilding, thus preventing the application from crashing.
While this solution prevents crashes, it is essentially restarting the application rather than recovering it from the point of interruption. If you run into this problem after releasing your app and are desperate for a quick fix, this solution should help you a lot.
However, if you are just starting from scratch, solution 2 is ideal because it can restore the application from where it broke.
Solution 2:
By now, you’ve no doubt noticed that you can use “package” objects to save and access data. Like in the previous example, save all the necessary information for each Activity/Fragment.
Because we access data that is stored in a “package,” this prevents the application from crashing and the application can recover from the break. All other activities/fragments will also be recreated.
RecyclerView Fragment RecyclerView Fragment Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val recyclerView = view.findViewById(R.id.recyclerView) recyclerView.layoutManager = LinearLayoutManager(context) users = savedInstanceState? .getParcelableArrayList("userList") ? : viewModel.getUsers() rv.adapter = DataAdapter(users, this) } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putParcelableArrayList("userList", users as ArrayList) }Copy the code
- Through the overwrite
onSaveInstanceState()
Function, we can store the required information in Bundle objects. - We’ll let the application check
onViewCreated()
Whether the data in the bundle in the function is available, and if not, the data is retrieved by accessing the ViewModel method.
conclusion
On Android, application crashes due to process terminations are common. If we’re using newer versions of Android, we’ll notice that a lot of background applications are forced to stop running in order to save power.
Solution 1 is a quick fix for your existing app crashes.
However, if you are developing the application from scratch, I recommend using Solution 2, as it ensures that the system will recover the application from a previously closed location, thus providing a better user experience.
Studying the root cause of such crashes can be difficult, so I hope this article has helped you in any way possible. Please tell me what you think of the solutions discussed in the article.
Pay attention to me, share knowledge every day, you want, I have ~~~