Bugsnag 5.12.0

pull/205/head
M66B 4 years ago
parent d72090a8b8
commit d2890c6808

@ -299,7 +299,7 @@ dependencies {
def dnsjava_version = "2.1.9" def dnsjava_version = "2.1.9"
def openpgp_version = "12.0" def openpgp_version = "12.0"
def badge_version = "1.1.22" def badge_version = "1.1.22"
def bugsnag_version = "5.11.0" def bugsnag_version = "5.12.0"
def biweekly_version = "0.6.6" def biweekly_version = "0.6.6"
def relinker_version = "1.4.3" def relinker_version = "1.4.3"
def markwon_version = "4.6.2" def markwon_version = "4.6.2"

@ -20,8 +20,9 @@ internal class AppDataCollector(
private val sessionTracker: SessionTracker, private val sessionTracker: SessionTracker,
private val activityManager: ActivityManager?, private val activityManager: ActivityManager?,
private val launchCrashTracker: LaunchCrashTracker, private val launchCrashTracker: LaunchCrashTracker,
private val logger: Logger private val memoryTrimState: MemoryTrimState
) { ) {
var codeBundleId: String? = null var codeBundleId: String? = null
private val packageName: String = appContext.packageName private val packageName: String = appContext.packageName
@ -51,8 +52,10 @@ internal class AppDataCollector(
val map = HashMap<String, Any?>() val map = HashMap<String, Any?>()
map["name"] = appName map["name"] = appName
map["activeScreen"] = sessionTracker.contextActivity map["activeScreen"] = sessionTracker.contextActivity
map["memoryUsage"] = getMemoryUsage() map["lowMemory"] = memoryTrimState.isLowMemory
map["lowMemory"] = isLowMemory() map["memoryTrimLevel"] = memoryTrimState.trimLevelDescription
populateRuntimeMemoryMetadata(map)
bgWorkRestricted?.let { bgWorkRestricted?.let {
map["backgroundWorkRestricted"] = bgWorkRestricted map["backgroundWorkRestricted"] = bgWorkRestricted
@ -63,13 +66,14 @@ internal class AppDataCollector(
return map return map
} }
/** private fun populateRuntimeMemoryMetadata(map: MutableMap<String, Any?>) {
* Get the actual memory used by the VM (which may not be the total used
* by the app in the case of NDK usage).
*/
private fun getMemoryUsage(): Long {
val runtime = Runtime.getRuntime() val runtime = Runtime.getRuntime()
return runtime.totalMemory() - runtime.freeMemory() val totalMemory = runtime.totalMemory()
val freeMemory = runtime.freeMemory()
map["memoryUsage"] = totalMemory - freeMemory
map["totalMemory"] = totalMemory
map["freeMemory"] = freeMemory
map["memoryLimit"] = runtime.maxMemory()
} }
/** /**
@ -86,22 +90,6 @@ internal class AppDataCollector(
} }
} }
/**
* Check if the device is currently running low on memory.
*/
private fun isLowMemory(): Boolean? {
try {
if (activityManager != null) {
val memInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memInfo)
return memInfo.lowMemory
}
} catch (exception: Exception) {
logger.w("Could not check lowMemory status")
}
return null
}
fun setBinaryArch(binaryArch: String) { fun setBinaryArch(binaryArch: String) {
this.binaryArch = binaryArch this.binaryArch = binaryArch
} }

@ -62,6 +62,9 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
@NonNull @NonNull
final BreadcrumbState breadcrumbState; final BreadcrumbState breadcrumbState;
@NonNull
final MemoryTrimState memoryTrimState = new MemoryTrimState();
@NonNull @NonNull
protected final EventStore eventStore; protected final EventStore eventStore;
@ -162,7 +165,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
DataCollectionModule dataCollectionModule = new DataCollectionModule(contextModule, DataCollectionModule dataCollectionModule = new DataCollectionModule(contextModule,
configModule, systemServiceModule, trackerModule, configModule, systemServiceModule, trackerModule,
bgTaskService, connectivity, storageModule.getDeviceId()); bgTaskService, connectivity, storageModule.getDeviceId(), memoryTrimState);
dataCollectionModule.resolveDependencies(bgTaskService, TaskType.IO); dataCollectionModule.resolveDependencies(bgTaskService, TaskType.IO);
appDataCollector = dataCollectionModule.getAppDataCollector(); appDataCollector = dataCollectionModule.getAppDataCollector();
deviceDataCollector = dataCollectionModule.getDeviceDataCollector(); deviceDataCollector = dataCollectionModule.getDeviceDataCollector();
@ -337,10 +340,21 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
clientObservable.postOrientationChange(newOrientation); clientObservable.postOrientationChange(newOrientation);
return null; return null;
} }
}, new Function1<Boolean, Unit>() { }, new Function2<Boolean, Integer, Unit>() {
@Override @Override
public Unit invoke(Boolean isLowMemory) { public Unit invoke(Boolean isLowMemory, Integer memoryTrimLevel) {
clientObservable.postMemoryTrimEvent(isLowMemory); memoryTrimState.setLowMemory(Boolean.TRUE.equals(isLowMemory));
if (memoryTrimState.updateMemoryTrimLevel(memoryTrimLevel)) {
leaveAutoBreadcrumb(
"Trim Memory",
BreadcrumbType.STATE,
Collections.<String, Object>singletonMap(
"trimLevel", memoryTrimState.getTrimLevelDescription()
)
);
}
memoryTrimState.emitObservableEvent();
return null; return null;
} }
} }
@ -383,6 +397,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
contextState.addObserver(observer); contextState.addObserver(observer);
deliveryDelegate.addObserver(observer); deliveryDelegate.addObserver(observer);
launchCrashTracker.addObserver(observer); launchCrashTracker.addObserver(observer);
memoryTrimState.addObserver(observer);
} }
void removeObserver(StateObserver observer) { void removeObserver(StateObserver observer) {
@ -394,6 +409,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
contextState.removeObserver(observer); contextState.removeObserver(observer);
deliveryDelegate.removeObserver(observer); deliveryDelegate.removeObserver(observer);
launchCrashTracker.removeObserver(observer); launchCrashTracker.removeObserver(observer);
memoryTrimState.removeObserver(observer);
} }
/** /**
@ -403,6 +419,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
metadataState.emitObservableEvent(); metadataState.emitObservableEvent();
contextState.emitObservableEvent(); contextState.emitObservableEvent();
userState.emitObservableEvent(); userState.emitObservableEvent();
memoryTrimState.emitObservableEvent();
} }
/** /**

@ -1,13 +1,13 @@
package com.bugsnag.android package com.bugsnag.android
import android.content.ComponentCallbacks import android.content.ComponentCallbacks2
import android.content.res.Configuration import android.content.res.Configuration
internal class ClientComponentCallbacks( internal class ClientComponentCallbacks(
private val deviceDataCollector: DeviceDataCollector, private val deviceDataCollector: DeviceDataCollector,
private val cb: (oldOrientation: String?, newOrientation: String?) -> Unit, private val cb: (oldOrientation: String?, newOrientation: String?) -> Unit,
val callback: (Boolean) -> Unit val memoryCallback: (Boolean, Int?) -> Unit
) : ComponentCallbacks { ) : ComponentCallbacks2 {
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
val oldOrientation = deviceDataCollector.getOrientationAsString() val oldOrientation = deviceDataCollector.getOrientationAsString()
@ -18,7 +18,11 @@ internal class ClientComponentCallbacks(
} }
} }
override fun onTrimMemory(level: Int) {
memoryCallback(level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE, level)
}
override fun onLowMemory() { override fun onLowMemory() {
callback(true) memoryCallback(true, null)
} }
} }

@ -8,10 +8,6 @@ internal class ClientObservable : BaseObservable() {
updateState { StateEvent.UpdateOrientation(orientation) } updateState { StateEvent.UpdateOrientation(orientation) }
} }
fun postMemoryTrimEvent(isLowMemory: Boolean) {
updateState { StateEvent.UpdateMemoryTrimEvent(isLowMemory) }
}
fun postNdkInstall( fun postNdkInstall(
conf: ImmutableConfig, conf: ImmutableConfig,
lastRunInfoPath: String, lastRunInfoPath: String,

@ -17,7 +17,8 @@ internal class DataCollectionModule(
trackerModule: TrackerModule, trackerModule: TrackerModule,
bgTaskService: BackgroundTaskService, bgTaskService: BackgroundTaskService,
connectivity: Connectivity, connectivity: Connectivity,
deviceId: String? deviceId: String?,
memoryTrimState: MemoryTrimState
) : DependencyModule() { ) : DependencyModule() {
private val ctx = contextModule.ctx private val ctx = contextModule.ctx
@ -34,7 +35,7 @@ internal class DataCollectionModule(
trackerModule.sessionTracker, trackerModule.sessionTracker,
systemServiceModule.activityManager, systemServiceModule.activityManager,
trackerModule.launchCrashTracker, trackerModule.launchCrashTracker,
logger memoryTrimState
) )
} }

@ -66,7 +66,7 @@ internal class DefaultDelivery(
return DeliveryStatus.UNDELIVERED return DeliveryStatus.UNDELIVERED
} catch (exception: IOException) { } catch (exception: IOException) {
logger.w("IOException encountered in request", exception) logger.w("IOException encountered in request", exception)
return DeliveryStatus.UNDELIVERED return DeliveryStatus.FAILURE
} catch (exception: Exception) { } catch (exception: Exception) {
logger.w("Unexpected error delivering payload", exception) logger.w("Unexpected error delivering payload", exception)
return DeliveryStatus.FAILURE return DeliveryStatus.FAILURE

@ -1,6 +1,7 @@
package com.bugsnag.android package com.bugsnag.android
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
@ -8,6 +9,7 @@ import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.content.res.Resources import android.content.res.Resources
import android.os.BatteryManager import android.os.BatteryManager
import android.os.Build
import android.provider.Settings import android.provider.Settings
import java.io.File import java.io.File
import java.util.Date import java.util.Date
@ -19,6 +21,7 @@ import java.util.concurrent.RejectedExecutionException
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import android.os.Process as AndroidProcess
internal class DeviceDataCollector( internal class DeviceDataCollector(
private val connectivity: Connectivity, private val connectivity: Connectivity,
@ -41,6 +44,7 @@ internal class DeviceDataCollector(
private val cpuAbi = getCpuAbi() private val cpuAbi = getCpuAbi()
private val runtimeVersions: MutableMap<String, Any> private val runtimeVersions: MutableMap<String, Any>
private val rootedFuture: Future<Boolean>? private val rootedFuture: Future<Boolean>?
private val totalMemoryFuture: Future<Long?>? = retrieveTotalDeviceMemory()
private var orientation = AtomicInteger(resources.configuration.orientation) private var orientation = AtomicInteger(resources.configuration.orientation)
init { init {
@ -68,7 +72,7 @@ internal class DeviceDataCollector(
checkIsRooted(), checkIsRooted(),
deviceId, deviceId,
locale, locale,
calculateTotalMemory(), totalMemoryFuture.runCatching { this?.get() }.getOrNull(),
runtimeVersions.toMutableMap() runtimeVersions.toMutableMap()
) )
@ -77,7 +81,7 @@ internal class DeviceDataCollector(
checkIsRooted(), checkIsRooted(),
deviceId, deviceId,
locale, locale,
calculateTotalMemory(), totalMemoryFuture.runCatching { this?.get() }.getOrNull(),
runtimeVersions.toMutableMap(), runtimeVersions.toMutableMap(),
calculateFreeDisk(), calculateFreeDisk(),
calculateFreeMemory(), calculateFreeMemory(),
@ -216,29 +220,58 @@ internal class DeviceDataCollector(
} }
/** /**
* Get the amount of memory remaining that the VM can allocate * Get the amount of memory remaining on the device
*/ */
private fun calculateFreeMemory(): Long { private fun calculateFreeMemory(): Long? {
val runtime = Runtime.getRuntime() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
val maxMemory = runtime.maxMemory() val freeMemory = appContext.getActivityManager()
?.let { am -> ActivityManager.MemoryInfo().also { am.getMemoryInfo(it) } }
?.availMem
return if (maxMemory != Long.MAX_VALUE) { if (freeMemory != null) {
maxMemory - runtime.totalMemory() + runtime.freeMemory() return freeMemory
} else {
runtime.freeMemory()
} }
} }
return runCatching {
@Suppress("PrivateApi")
AndroidProcess::class.java.getDeclaredMethod("getFreeMemory").invoke(null) as Long?
}.getOrNull()
}
/** /**
* Get the total memory available on the current Android device, in bytes * Attempt to retrieve the total amount of memory available on the device
*/ */
private fun calculateTotalMemory(): Long { private fun retrieveTotalDeviceMemory(): Future<Long?>? {
val runtime = Runtime.getRuntime() return try {
val maxMemory = runtime.maxMemory() bgTaskService.submitTask(
return when { TaskType.DEFAULT,
maxMemory != Long.MAX_VALUE -> maxMemory Callable {
else -> runtime.totalMemory() calculateTotalMemory()
} }
)
} catch (exc: RejectedExecutionException) {
logger.w("Failed to lookup available device memory", exc)
null
}
}
private fun calculateTotalMemory(): Long? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
val totalMemory = appContext.getActivityManager()
?.let { am -> ActivityManager.MemoryInfo().also { am.getMemoryInfo(it) } }
?.totalMem
if (totalMemory != null) {
return totalMemory
}
}
// we try falling back to a reflective API
return runCatching {
@Suppress("PrivateApi")
AndroidProcess::class.java.getDeclaredMethod("getTotalMemory").invoke(null) as Long?
}.getOrNull()
} }
/** /**

@ -0,0 +1,41 @@
package com.bugsnag.android
import android.content.ComponentCallbacks2
internal class MemoryTrimState : BaseObservable() {
var isLowMemory: Boolean = false
var memoryTrimLevel: Int? = null
val trimLevelDescription: String get() = descriptionFor(memoryTrimLevel)
fun updateMemoryTrimLevel(newTrimLevel: Int?): Boolean {
if (memoryTrimLevel == newTrimLevel) {
return false
}
memoryTrimLevel = newTrimLevel
return true
}
fun emitObservableEvent() {
updateState {
StateEvent.UpdateMemoryTrimEvent(
isLowMemory,
memoryTrimLevel,
trimLevelDescription
)
}
}
private fun descriptionFor(memoryTrimLevel: Int?) = when (memoryTrimLevel) {
null -> "None"
ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> "Complete"
ComponentCallbacks2.TRIM_MEMORY_MODERATE -> "Moderate"
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> "Background"
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> "UI hidden"
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> "Running critical"
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> "Running low"
ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE -> "Running moderate"
else -> "Unknown ($memoryTrimLevel)"
}
}

@ -429,4 +429,11 @@ public class NativeInterface {
public static void setAutoDetectAnrs(boolean autoDetectAnrs) { public static void setAutoDetectAnrs(boolean autoDetectAnrs) {
getClient().setAutoDetectAnrs(autoDetectAnrs); getClient().setAutoDetectAnrs(autoDetectAnrs);
} }
/**
* Marks the launch period as complete
*/
public static void markLaunchCompleted() {
getClient().markLaunchCompleted();
}
} }

@ -7,7 +7,7 @@ import java.io.IOException
*/ */
class Notifier @JvmOverloads constructor( class Notifier @JvmOverloads constructor(
var name: String = "Android Bugsnag Notifier", var name: String = "Android Bugsnag Notifier",
var version: String = "5.11.0", var version: String = "5.12.0",
var url: String = "https://bugsnag.com" var url: String = "https://bugsnag.com"
) : JsonStream.Streamable { ) : JsonStream.Streamable {

@ -62,5 +62,9 @@ sealed class StateEvent { // JvmField allows direct field access optimizations
class UpdateUser(@JvmField val user: User) : StateEvent() class UpdateUser(@JvmField val user: User) : StateEvent()
class UpdateMemoryTrimEvent(@JvmField val isLowMemory: Boolean) : StateEvent() class UpdateMemoryTrimEvent(
@JvmField val isLowMemory: Boolean,
@JvmField val memoryTrimLevel: Int? = null,
@JvmField val memoryTrimLevelDescription: String = "None"
) : StateEvent()
} }

Loading…
Cancel
Save