Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to make APP never collapse

2025-03-31 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article introduces the knowledge of "how to make APP never collapse". In the operation of actual cases, many people will encounter such a dilemma. Next, let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

Let my APP never collapse.

Now that we can intercept crashes, we just intercept all the exceptions in APP without killing the program. Isn't it a leveraged APP user experience that won't collapse?

Someone shook his head in disapproval, and this light came to me and asked me:

"buddy, the reason for the collapse is for you to solve it, not to cover it up!"

I slapped a fan a few times, a little cold but pretending to be calm and said:

"Brother, you can upload the exception to your own server for processing, you can get the cause of your crash, and the user will not crash APP because of the exception, isn't that good?"

Xiaoguang said angrily:

"there must be something wrong, but it doesn't sound reliable. Well, I'll give it a try."

Xiaoguang's experiment

So Xiaoguang wrote the following code to catch an exception according to an article written by a small blogger-Building Block on the Internet:

/ / define CrashHandlerclass CrashHandler private constructor (): Thread.UncaughtExceptionHandler {private var context: Context? = null fun init (context: Context?) {this.context = context Thread.setDefaultUncaughtExceptionHandler (this)} override fun uncaughtException (t: Thread) E: Throwable) {} companion object {val instance: CrashHandler by lazy (mode = LazyThreadSafetyMode.SYNCHRONIZED) {CrashHandler ()}} / / initialize class MyApplication in Application () {override fun onCreate () {super.onCreate () CrashHandler.instance.init (this)}} / / Activity trigger exception class ExceptionActivity: AppCompatActivity () {override fun onCreate (savedInstanceState: Bundle? ) {super.onCreate (savedInstanceState) setContentView (R.layout.activity_exception) btn.setOnClickListener {throw RuntimeException (main thread exception)} btn2.setOnClickListener {thread {throw RuntimeException (child thread exception)}

Xiaoguang wrote the whole code after one operation, and in order to verify its conjecture, he wrote two situations that triggered the exception: the child thread crash and the main thread crash.

To run, click button 2 to trigger an abnormal crash of the child thread:

"Why, it really doesn't matter. The program can continue to run normally."

Then click button 1 to trigger an abnormal crash of the main thread:

"Hey, hey, it's stuck. Click on it a few more times and ANR it directly."

"there is a problem, but why is there something wrong with the main thread? I have to figure it out before I confront the old man."

Thinking of Xiaoguang (abnormal source code analysis)

First, exceptions in java under popular science, including run-time exceptions and non-run-time exceptions:

Runtime exception. It is the exception of RuntimeException class and its subclass, and it is not checked, such as system exception or program logic exception. We often encounter NullPointerException, IndexOutOfBoundsException and so on. When this exception is encountered, Java Runtime stops the thread, prints the exception, and stops the program from running, which is what we often call a program crash.

Non-runtime exception. It belongs to the Exception class and its subclasses, is the checked exception, the exception other than RuntimeException. Such exceptions must be handled in the program, and the program cannot be compiled properly if it is not handled, such as NoSuchFieldException,IllegalAccessException.

Ok, that is, after we throw a RuntimeException exception, the thread is stopped. If this exception is thrown in the main thread, the main thread will be stopped, so the APP will be stuck and unable to operate properly, and it will ANR over time. The collapse of the child thread will not affect the operation of the main thread, that is, the UI thread, so the user can still use it normally.

That seems to make sense.

Wait, so why don't you collapse when you encounter setDefaultUncaughtExceptionHandler?

We also have to start with the abnormal source code:

In general, the threads used in an application are all in the same thread group, and whenever one thread in this thread group has an uncaught exception, the JAVA virtual machine will call the uncaughtException () method in the thread group of the current thread.

/ / ThreadGroup.java private final ThreadGroup parent; public void uncaughtException (Thread t, Throwable e) {if (parent! = null) {parent.uncaughtException (t, e);} else {Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler (); if (ueh! = null) {ueh.uncaughtException (t, e) } else if (! (e instanceof ThreadDeath)) {System.err.print ("Exception in thread\"+ t.getName () +"\ "); e.printStackTrace (System.err);}

Parent represents the parent thread group of the current thread group, so it will eventually be called into this method. Then look at the following code, get the default exception handler of the system through getDefaultUncaughtExceptionHandler, and then call the uncaughtException method. So let's look for the exception handler in the original system-- UncaughtExceptionHandler.

This starts with the startup process of APP. As mentioned before, all Android processes are derived from the zygote process fork. When a new process is started, the zygoteInit method is called. In this method, some application initialization work is carried out:

Public static final Runnable zygoteInit (int targetSdkVersion, String [] argv, ClassLoader classLoader) {if (RuntimeInit.DEBUG) {Slog.d (RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");} Trace.traceBegin (Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit"); / / log redirection RuntimeInit.redirectLogStreams (); / / generic configuration initialization RuntimeInit.commonInit () / / zygote initializes ZygoteInit.nativeZygoteInit (); / / initializes return RuntimeInit.applicationInit (targetSdkVersion, argv, classLoader) related to the application;}

As for exception handlers, it is in this general configuration initialization method:

Protected static final void commonInit () {if (DEBUG) Slog.d (TAG, "Entered RuntimeInit!"); / / set exception handler LoggingHandler loggingHandler = new LoggingHandler (); Thread.setUncaughtExceptionPreHandler (loggingHandler); Thread.setDefaultUncaughtExceptionHandler (new KillApplicationHandler (loggingHandler)) / / set time zone TimezoneGetter.setInstance (new TimezoneGetter () {@ Override public String getId () {return SystemProperties.get ("persist.sys.timezone");}}); TimeZone.setDefault (null); / / log configure LogManager.getLogManager () .reset () / / * initialized = true;}

Find it, here is set the application default exception handler-KillApplicationHandler.

Private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {private final LoggingHandler mLoggingHandler; public KillApplicationHandler (LoggingHandler loggingHandler) {this.mLoggingHandler = Objects.requireNonNull (loggingHandler);} @ Override public void uncaughtException (Thread t, Throwable e) {try {ensureLogging (t, e) / /... / / Bring up crash dialog, wait for it to be dismissed ActivityManager.getService (). HandleApplicationCrash (mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo (e));} catch (Throwable T2) {if (T2 instanceof DeadObjectException) {/ / System process is dead Ignore} else {try {Clog_e (TAG, "Error reporting crash", T2);} catch (Throwable T3) {/ / Even Clog_e () fails! Oh well. } finally {/ / Try everything to make sure this process goes away. Process.killProcess (Process.myPid ()); System.exit (10);}} private void ensureLogging (Thread t, Throwable e) {if (! mLoggingHandler.mTriggered) {try {mLoggingHandler.uncaughtException (t, e);} catch (Throwable loggingThrowable) {/ / Ignored. }}}

Seeing this, Xiaoguang smiled with relief and was caught by me. In the uncaughtException callback method, a handleApplicationCrash method is executed to handle the exception, and eventually goes to the finally to destroy the process, Try everything to make sure this process goes away. So the program crashed.

The pop-up window for the crash prompt that we usually see on our mobile phones is popped up in this handleApplicationCrash method. Not only the java crash, but also the native_crash, ANR and other exceptions we usually encounter will end up in the handleApplicationCrash method to deal with the crash.

In addition, some friends may find that a LoggingHandler is passed in the constructor, and the uncaughtException method of this LoggingHandler is also called in the uncaughtException callback method. Is this LoggingHandler the crash log that we usually see when we encounter crash problems? Go in and have a look:

Private static class LoggingHandler implements Thread.UncaughtExceptionHandler {public volatile boolean mTriggered = false; @ Override public void uncaughtException (Thread t, Throwable e) {mTriggered = true; if (mCrashing) return; if (mApplicationObject = = null & & (Process.SYSTEM_UID = = Process.myUid ()) {Clog_e (TAG, "* * FATAL EXCEPTION IN SYSTEM PROCESS:" + t.getName (), e) } else {StringBuilder message = new StringBuilder (); message.append ("FATAL EXCEPTION:") .append (t.getName ()) .append ("\ n"); final String processName = ActivityThread.currentProcessName (); if (processName! = null) {message.append ("Process:") .append (processName) .append (",") } message.append ("PID:") .append (Process.myPid ()); Clog_e (TAG, message.toString (), e);}} private static int Clog_e (String tag, String msg, Throwable tr) {return Log.printlns (Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr);}

Isn't that what it is? Some information about the crash-- such as threads, processes, process id, the cause of the crash, and so on-- is printed through Log. Let's take a crash log chart for everyone to see:

Okay, back on track, so we set up our own crash processor through the setDefaultUncaughtExceptionHandler method, and we knocked out the crash processor set by the previous application, and then we didn't do anything about it, so the natural program wouldn't crash. Let's take a summary.

Xiaoguang confronted me again.

The little light who figured it out came to me again:

"look, buddy, this is the Demo and summary information I wrote. Yours doesn't work at all. If the main thread crashes, it will GG. I'll say there's a problem."

I continued to pretend to be calm:

"Brother, I forgot to mention last time that I can't just add this UncaughtExceptionHandler. I have to add a piece of code and send it to you. Go back and try it."

Handler (Looper.getMainLooper ()). Post {while (true) {try {Looper.loop ()} catch (e: Throwable) {}

"this, uh, will it work?"

Xiaoguang's experiment again

Xiaoguang adds the above code to the program (Application-onCreate) and runs it again:

I go, really no problem, click on the main thread crash, or normal operation of app, what is the principle?

Xiaoguang's rethinking (the idea of intercepting the collapse of the main thread)

As we all know, there is a mechanism for maintaining Handler in the main thread, the Looper is created and initialized when the application is started, and the loop method is called to start the message loop processing. When the application is in use, all the operations of the main thread, such as event clicks, list slides, and so on, are processed in this loop. Its essence is to add the message to the MessageQueue queue, and then loop to extract the message from the queue and process it. If there is no message processing, it will rely on the epoll mechanism to suspend and wait for wake-up. Post my condensed loop code:

Public static void loop () {final Looper me = myLooper (); final MessageQueue queue = me.mQueue; for (;;) {Message msg = queue.next (); msg.target.dispatchMessage (msg);}}

An endless loop, constantly fetching messages to process messages. Look back at the code you just added:

Handler (Looper.getMainLooper ()) .post {while (true) {/ / main thread exception intercepts try {Looper.loop ()} catch (e: Throwable) {}

We send a runnable task to the main thread through Handler, and then add a dead loop to the runnable, where Looper.loop () is executed to read the message loop. This will cause all subsequent main thread messages to be processed in our loop method, that is, in the event of a main thread crash, exception catch can be carried out here. At the same time, because we are writing a while dead loop, after catching the exception, we will start the execution of the new Looper.loop () method. In this way, the Looper of the main thread can always read messages normally, and the main thread can always run normally.

Pictures with unclear words to help us:

At the same time, the logic of the previous CrashHandler can ensure that the child thread is not affected by the crash, so both pieces of code are added and done.

But Xiaoguang was not convinced, and he thought of a collapse situation.

Once again, Xiaoguang experimented with class Test2Activity: AppCompatActivity () {override fun onCreate (savedInstanceState: Bundle?) {super.onCreate (savedInstanceState) setContentView (R.layout.activity_exception) throw RuntimeException ("main thread exception")}}

Well, I'll throw you an exception directly in onCreate and run it:

It's dark ~ Yes, the screen is black.

The final conversation (Cockroach library idea)

Seeing this, I took the initiative to find Xiaoguang:

"this situation is really troublesome. If an exception is thrown directly during the Activity life cycle, the interface drawing cannot be completed and the Activity cannot be started correctly, and the white screen or black screen will seriously affect the user experience. It is recommended to kill APP directly, because it is likely to affect other functional modules. Or if some Activity is not very important, you can only finish this Activity."

Xiaoguang asked thoughtfully, "so how can you tell when a collapse occurs in this life cycle?"

"this is done through reflection, to borrow the idea in the Cockroach open source library. Since the life cycle of Activity is processed through the Handler of the main thread, we can replace the Callback callback in the main thread's Handler, that is, ActivityThread.mH.mCallback, through reflection, and then trycatch catch exceptions for the message corresponding to each life cycle, and then we can finishActivity or kill the process."

Main code:

Field mhField = activityThreadClass.getDeclaredField ("mH"); mhField.setAccessible (true); final Handler mhHandler = (Handler) mhField.get (activityThread); Field callbackField = Handler.class.getDeclaredField ("mCallback"); callbackField.setAccessible (true) Life cycle processing final int EXECUTE_TRANSACTION after callbackField.set (mhHandler, new Handler.Callback () {@ Override public boolean handleMessage (Message msg) {if (Build.VERSION.SDK_INT > = 28) {/ / android 28) If (msg.what = = EXECUTE_TRANSACTION) {try {mhHandler.handleMessage (msg);} catch (Throwable throwable) {/ / kill the process or Activity} return true } return false } / / Lifecycle processing before android 28 switch (msg.what) {case RESUME_ACTIVITY: / / onRestart onStart onResume callback here try {mhHandler.handleMessage (msg) } catch (Throwable throwable) {sActivityKiller.finishResumeActivity (msg); notifyException (throwable);} return true

The code is partially pasted, but the principle should be understood by replacing the Callback of the main thread Handler to catch exceptions in the declaration cycle.

The next step is to do the post-capture processing, either kill the process or kill the Activity.

The killing process should be familiar to everyone.

Process.killProcess (Process.myPid ()) exitProcess (10)

Finish without Activity

Here again to analyze the finish process of Activity, to put it simply, take the source code of android29 as an example.

Private void finish (int finishTask) {if (mParent = = null) {if (false) Log.v (TAG, "Finishing self: token=" + mToken); try {if (resultData! = null) {resultData.prepareToLeaveProcess (this) } if (ActivityTaskManager.getService () .finishActivity (mToken, resultCode, resultData, finishTask)) {mFinished = true @ Override public final boolean finishActivity (IBinder token, int resultCode, Intent resultData, int finishTask) {return mActivityTaskManager.finishActivity (token, resultCode, resultData, finishTask);}

As you can see from the finish source code of Activity, the final call is to the finishActivity method of ActivityTaskManagerService, which has four parameters, one of which identifies the parameter of Activity, that is, the most important parameter-token. So look for token~ in the source code.

Since the place we captured is in the handleMessage callback method, only one parameter, Message, can be used, so let's start with this aspect. Go back to the source code we just processed the message and see if we can find any clues:

Class H extends Handler {public void handleMessage (Message msg) {switch (msg.what) {case EXECUTE_TRANSACTION: final ClientTransaction transaction = (ClientTransaction) msg.obj; mTransactionExecutor.execute (transaction); break }} public void execute (ClientTransaction transaction) {final IBinder token = transaction.getActivityToken (); executeCallbacks (transaction); executeLifecycleState (transaction); mPendingActions.clear (); log ("End resolving transaction");}

You can see how Handler handles EXECUTE_TRANSACTION messages in the source code, gets the msg.obj object, that is, an instance of the ClientTransaction class, and then calls the execute method. In the execute method. Hey, isn't this token?

(found too fast, ha, mainly activity startup destruction of this part of the source code explanation is not the focus of today, so I will pass it over.)

If we find token, then we can destroy the Activity through reflection:

Private void finishMyCatchActivity (Message message) throws Throwable {ClientTransaction clientTransaction = (ClientTransaction) message.obj; IBinder binder = clientTransaction.getActivityToken (); Method getServiceMethod = ActivityManager.class.getDeclaredMethod ("getService"); Object activityManager = getServiceMethod.invoke (null); Method finishActivityMethod = activityManager.getClass (). GetDeclaredMethod ("finishActivity", IBinder.class, int.class, Intent.class, int.class); finishActivityMethod.setAccessible (true) This is the end of finishActivityMethod.invoke (activityManager, binder, Activity.RESULT_CANCELED, null, 0);} "how to make APP never crash". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report