44import android .text .TextUtils ;
55
66import java .io .File ;
7+ import java .io .IOException ;
78import java .util .ArrayList ;
89import java .util .Arrays ;
910import java .util .HashSet ;
1011import java .util .List ;
1112import java .util .Map ;
1213import java .util .Set ;
14+ import java .util .zip .ZipEntry ;
15+ import java .util .zip .ZipFile ;
1316
1417import backtraceio .library .common .AbiHelper ;
1518import backtraceio .library .services .BacktraceCrashHandlerRunner ;
@@ -36,9 +39,42 @@ public String getClassPath() {
3639 }
3740
3841 public List <String > getCrashHandlerEnvironmentVariables (ApplicationInfo applicationInfo ) {
39- return getCrashHandlerEnvironmentVariables (applicationInfo .sourceDir , applicationInfo .nativeLibraryDir , AbiHelper .getCurrentAbi ());
42+ final String classPathApk = applicationInfo .sourceDir ;
43+ final String nativeLibraryDirPath = applicationInfo .nativeLibraryDir ;
44+ final String arch = AbiHelper .getCurrentAbi ();
45+
46+ final List <String > environmentVariables = new ArrayList <>();
47+
48+ // system environment variables
49+ for (Map .Entry <String , String > variable : System .getenv ().entrySet ()) {
50+ environmentVariables .add (String .format ("%s=%s" , variable .getKey (), variable .getValue ()));
51+ }
52+
53+ // LD_LIBRARY_PATH
54+ File nativeLibraryDirectory = new File (nativeLibraryDirPath );
55+ File allNativeLibrariesDirectory = nativeLibraryDirectory .getParentFile ();
56+ String allPossibleLibrarySearchPaths = TextUtils .join (File .pathSeparator , new String []{
57+ nativeLibraryDirPath ,
58+ allNativeLibrariesDirectory .getPath (),
59+ System .getProperty ("java.library.path" ),
60+ "/data/local"
61+ });
62+
63+ final String backtraceNativeLibraryPath = resolveBacktraceNativeLibraryPath (applicationInfo , arch );
64+
65+ environmentVariables .add (String .format ("CLASSPATH=%s" , classPathApk ));
66+ environmentVariables .add (String .format ("%s=%s" , BACKTRACE_CRASH_HANDLER , backtraceNativeLibraryPath ));
67+ environmentVariables .add (String .format ("LD_LIBRARY_PATH=%s" , allPossibleLibrarySearchPaths ));
68+ environmentVariables .add ("ANDROID_DATA=/data" );
69+
70+ return environmentVariables ;
4071 }
4172
73+ /**
74+ * @deprecated Prefer {@link #getCrashHandlerEnvironmentVariables(android.content.pm.ApplicationInfo)} which correctly resolves split APKs on GooglePlay/AAB installs.
75+ * This method may be removed in a future release.
76+ */
77+ @ Deprecated
4278 public List <String > getCrashHandlerEnvironmentVariables (String apkPath , String nativeLibraryDirPath , String arch ) {
4379 final List <String > environmentVariables = new ArrayList <>();
4480
@@ -86,4 +122,51 @@ private String getBacktraceNativeLibraryPath(String nativeLibraryDirPath, String
86122 ? backtraceNativeLibraryPath
87123 : String .format ("%s!/lib/%s/%s" , apkPath , arch , BACKTRACE_NATIVE_LIBRARY_NAME );
88124 }
125+
126+ /**
127+ * Resolve native lib container:
128+ * extracted dir if present,
129+ * base.apk if it contains the entry,
130+ * first split that contains the entry,
131+ * fallback to base.apk path format.
132+ */
133+ private String resolveBacktraceNativeLibraryPath (ApplicationInfo appInfo , String arch ) {
134+ final String entry = "lib/" + arch + "/" + BACKTRACE_NATIVE_LIBRARY_NAME ;
135+
136+ // extracted dir if present
137+ if (appInfo .nativeLibraryDir != null ) {
138+ File extracted = new File (appInfo .nativeLibraryDir , BACKTRACE_NATIVE_LIBRARY_NAME );
139+ if (extracted .exists ()) {
140+ return extracted .getAbsolutePath ();
141+ }
142+ }
143+
144+ // base.apk if it contains the lib
145+ if (apkContains (appInfo .sourceDir , entry )) {
146+ return appInfo .sourceDir + "!/" + entry ;
147+ }
148+
149+ // first split that contains the entry
150+ if (appInfo .splitSourceDirs != null ) {
151+ for (String split : appInfo .splitSourceDirs ) {
152+ if (apkContains (split , entry )) {
153+ return split + "!/" + entry ;
154+ }
155+ }
156+ }
157+
158+ // fallback to base.apk path format
159+ return appInfo .sourceDir + "!/" + entry ;
160+ }
161+
162+ private static boolean apkContains (String apkPath , String entry ) {
163+ if (apkPath == null || apkPath .isEmpty ()) return false ;
164+ try (ZipFile zf = new ZipFile (apkPath )) {
165+ ZipEntry ze = zf .getEntry (entry );
166+ return ze != null ;
167+ } catch (IOException ignored ) {
168+ return false ;
169+ }
170+ }
171+
89172}
0 commit comments