Last active
April 25, 2021 04:42
-
-
Save sandin/43e09910ef118e19690441d74efc5bf3 to your computer and use it in GitHub Desktop.
native crash handler patch for unity 2019
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.unity3d.player; | |
import android.util.Log; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Method; | |
public final class UnityNativeCrashHandlerPatch { | |
public static void setupNativeCrashHandler() { | |
try { | |
Object unityPlayer = getUnityPlayer(); | |
if (unityPlayer != null) { | |
Class<?> unityPlayerCls = unityPlayer.getClass(); | |
// String originLaunchUrl = unityPlayer.getLaunchURL(); | |
Method getLaunchURLMethod = unityPlayerCls.getDeclaredMethod("getLaunchURL"); | |
getLaunchURLMethod.setAccessible(true); | |
String originLaunchUrl = (String) getLaunchURLMethod.invoke(unityPlayer); | |
// unityPlayer.nativeSetLaunchURL(originLaunchUrl); | |
Method nativeSetLaunchURLMethod = unityPlayerCls.getDeclaredMethod("nativeSetLaunchURL", String.class); | |
nativeSetLaunchURLMethod.setAccessible(true); | |
nativeSetLaunchURLMethod.invoke(unityPlayer, originLaunchUrl); // will invoke JNI_METHOD_IMPL -> sigaction() to setup native crash handler for unity | |
Log.d("UnityUtils", "nativeSetLaunchURL success, launchUrl=" + originLaunchUrl); | |
} | |
} catch (Throwable e) { | |
Log.e("UnityUtils", e.getMessage()); | |
} | |
} | |
private static Object getUnityPlayer() { | |
try { | |
Object unityPlayerActivity = getUnityPlayerActivity(); | |
if (unityPlayerActivity != null) { | |
Class<?> unityPlayerActivityCls = Class.forName("com.unity3d.player.UnityPlayerActivity"); | |
if (unityPlayerActivityCls.isInstance(unityPlayerActivity)) { | |
Field unityPlayerField = unityPlayerActivityCls.getDeclaredField("mUnityPlayer"); | |
unityPlayerField.setAccessible(true); | |
return unityPlayerField.get(unityPlayerActivity); | |
} else { | |
Log.e("UnityUtils", unityPlayerActivity + " is not a instanceof com.unity3d.player.UnityPlayerActivity, maybe it's not a Unity Application?"); | |
} | |
} | |
} catch (Throwable e) { | |
Log.e("UnityUtils", e.getMessage()); | |
} | |
return null; | |
} | |
private static Object getUnityPlayerActivity() { | |
try { | |
Class<?> unityPlayerCls = Class.forName("com.unity3d.player.UnityPlayer"); | |
Field unityPlayerActivityField = unityPlayerCls.getField("currentActivity"); | |
return unityPlayerActivityField.get(null); | |
} catch (Throwable e) { | |
Log.e("UnityUtils", e.getMessage()); | |
} | |
return null; | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
风险说明
NOTE: 需要特别注意 的是如果今后升级Unity引擎,Unity新版本中涉及到的函数实现逻辑发生变化时,则可能引起副作用(包括 可能会引起启动崩溃 ),故升级引擎后需观察日志确认该补丁是否工作正常。
背景说明
很多Unity项目的native crash会显示
java.lang.Error
,这是因为Unity引擎会优先捕捉到native crash的信号量,并将其封装成一个Java异常抛出,因此其他崩溃信号处理器则会捕捉到一个Java异常,但其堆栈确是native堆栈,并且只有函数地址,并且因为此时捕捉到的已经被Unity封装过的Java异常了,并没有详细崩溃dump信息了,因此无法使用符号文件对函数堆栈再次解析符号。在Unity 2019之前,Unity会在
UnityPlayerActivity.onCreate()
方法中实例化new UnityPlayer()
, 在该构造器中Unity会调用JNI方法,并注册崩溃信号处理器,因此在一般情况下,其他崩溃信号处理器只需要在其后注册,则可以先捕捉到崩溃信号,而如果让Unity先捕捉到,则Unity会将Native Crash包装成一个Java Exception抛出到上层进行捕捉,而在这个过程中,会使得其他崩溃信号处理器无法得到原始的崩溃dump信息,因此无法用符号文件去解析堆栈信息。而在Unity 2019之后,Unity不会在
new UnityPlayer()
的构造器中注册崩溃信号处理器,而是在UnityPlayerActivity.onResume()
函数中再通过handler异步去注册,因此无法准确掌握其注册时机,导致无法确保在其后面注册以便先捕捉到崩溃信号。因为C#的虚拟机也使用了崩溃信号来实现了C#的异常机制,因此必须在Unity注册之后,Mono注册之前注册信号处理器才不会引起副作用。补丁说明
因此针对特定的Unity版本,需要在初始化崩溃处理器之前,主动通过反射来让Unity先注册崩溃信号处理器,以便绕过这个问题,但是该解决方案必须在对Unity注册信号处理的行为完全掌握的情况下(一般情况下需要拥有Unity源码),才能准确的通过Java反射来调用Unity的底层API来实现,并不产生任何的其他副作用。
因为Unity是在第一次调用JNI的native方法时注册信号处理器的,因此这里补丁的办法就是在
new UnityPlayer()
之后,主动通过JAVA反射来调用UnityPlayer
的一个JNI native方法,这里选择的是nativeSetLaunchURL
方法,该方法是用来设置 Application.absoluteURL 的值,在游戏启动的时候,该值默认为空,因此将其重新设置一下不会引起其他副作用的目的。该补丁通过反射执行的Java代码如下:
该代码的逻辑是先通过GET方法获取launchURL(此时为null),然后再通过SET方法来重新设置一遍launchURL,因为SET方法是native方法,因此会触发Unity引擎注册native崩溃信号处理器的逻辑。
因此这个补丁的唯一要求:
getLaunchURL()
和nativeSetLaunchURL(String url)
这2个方法。(可通过反编译apk来确认这2个方法是否存在于该Unity版本中)使用说明
在
UnityPatch.java
文件拷贝到Unity导出的Android工程中(默认可放在com.unity3d.player
包名下,也可以修改源码第一行的package名称后放置在任何包名下),并在UnityPlayerActivity.onCreate()
方法之后,初始化其他崩溃捕捉器(如Crasheye,Bugly等)之前调用该补丁即可。NOTE:必须确保调用补丁代码的时机,必须在
new UnityPlayer()
之后,初始化其他崩溃捕捉器(如Crasheye,Bugly等)之间的时间段内才能有效。代码示例: