0%

安卓应用崩溃的时候发生了什么

安卓应用崩溃大致上可以分为java调用时的崩溃和native代码调用时的崩溃(我个人这样分)。下面我们分别看看这两种情况下,具体是如何崩溃的。

java 调用崩溃

我们看下面这样一段代码,在没有为 mButton 绑定view的情况下,为他设置一个 onClickListener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainActivity extends AppCompatActivity {

private static final String TAG = "testapp";
Button mButton = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG,"onCreate");
}

public void click(View view) {
mButton.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Log.d(TAG,"clicked");
}
});
}
}

调用到 click 这个函数,正如预期的那样,应用崩溃了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
03-27 23:36:32.936 32641 32641 D testapp : onCreate
03-27 23:36:55.858 32641 32641 E AndroidRuntime: FATAL EXCEPTION: main
03-27 23:36:55.858 32641 32641 E AndroidRuntime: Process: com.example.crashinfo, PID: 32641
03-27 23:36:55.858 32641 32641 E AndroidRuntime: java.lang.IllegalStateException: Could not execute method for android:onClick
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:414)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at android.view.View.performClick(View.java:7448)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at android.view.View.performClickInternal(View.java:7425)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at android.view.View.access$3600(View.java:810)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at android.view.View$PerformClick.run(View.java:28305)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:938)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:99)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at android.os.Looper.loop(Looper.java:223)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:7656)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: Caused by: java.lang.reflect.InvocationTargetException
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:409)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: ... 12 more
03-27 23:36:55.858 32641 32641 E AndroidRuntime: Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
03-27 23:36:55.858 32641 32641 E AndroidRuntime: at com.example.crashinfo.MainActivity.click(MainActivity.java:23)
03-27 23:36:55.858 32641 32641 E AndroidRuntime: ... 14 more

通过前面的 PID 可以知道,实际上这些崩溃信息是应用的进程打出来的。也就是说,崩溃信息打印出来的时候,应用的进程还没有退出。

通过在系统里面加log可以看到,崩溃的信息是这里打印出来的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// ANDROID_ROOT/frameworks/base/core/java/com/android/internal/os/RuntimeInit.java
/**
* Logs a message when a thread encounters an uncaught exception. By
* default, {@link KillApplicationHandler} will terminate this process later,
* but apps can override that behavior.
*/
private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
public volatile boolean mTriggered = false;

@Override
public void uncaughtException(Thread t, Throwable e) {
mTriggered = true;

// Don't re-enter if KillApplicationHandler has already run
if (mCrashing) return;

// mApplicationObject is null for non-zygote java programs (e.g. "am")
// There are also apps running with the system UID. We don't want the
// first clause in either of these two cases, only for system_server.
if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
} else {
StringBuilder message = new StringBuilder();
// The "FATAL EXCEPTION" string is still used on Android even though
// apps can set a custom UncaughtExceptionHandler that renders uncaught
// exceptions non-fatal.
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);
}
}
}

并且我们在同一个文件下面不远处可以看到这样一段代码

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
......
} catch (Throwable t2) {
......
} finally {
// Try everything to make sure this process goes away.
Process.killProcess(Process.myPid());
System.exit(10);
}
}

Process.killProcess(Process.myPid());这样传入了当前进程的 PID ,查看 Process.killProcess(); 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//frameworks/base/core/java/android/os/Process.java
/**
* Kill the process with the given PID.
* Note that, though this API allows us to request to
* kill any process based on its PID, the kernel will
* still impose standard restrictions on which PIDs you
* are actually able to kill. Typically this means only
* the process running the caller's packages/application
* and any additional processes created by that app; packages
* sharing a common UID will also be able to kill each
* other's processes.
*/
public static final void killProcess(int pid) {
sendSignal(pid, SIGNAL_KILL);
}

这里的SIGNAL_KILL的值是9。到这里其实我们大概能猜出来了,给当前的 PID 的进程发送了信号 9 。kernel接受到这个信号之后将会直接杀死对应的进程。
sendSignal的实现如下

1
2
3
4
5
6
7
8
//frameworks/base/core/java/android/os/Process.java
/**
* Send a signal to the given process.
*
* @param pid The pid of the target process.
* @param signal The signal to send.
*/
public static final native void sendSignal(int pid, int signal);

可以看到这里实际上通过jni调用到了一个native上的接口,顺着找下去,可以看到

1
2
3
4
5
6
7
8
//frameworks/base/core/jni/android_util_Process.cpp
void android_os_Process_sendSignal(JNIEnv* env, jobject clazz, jint pid, jint sig)
{
if (pid > 0) {
ALOGI("Sending signal. PID: %" PRId32 " SIG: %" PRId32, pid, sig);
kill(pid, sig);
}
}

kill的原型是int kill(int pid,int sig);,是Linux的一个用于向指定 PID 的进程或进程组发送信号的函数。

综上所述,安卓应用调用java代码发生异常而崩溃时,实际上是应用自己调用 kill 来使应用所在的进程直接被杀死。相应的,应用可以自己捕捉到java代码上的异常来防止应用崩溃(闪退)。

native 调用时的崩溃

native调用时的崩溃与Linux上的C/C++程序崩溃的行为很像,我们写这样一个简单的jni调用来让安卓应用崩溃