Skip to content

Commit

Permalink
perf: register system callbacks on background thread
Browse files Browse the repository at this point in the history
  • Loading branch information
fractalwrench committed Jun 29, 2021
1 parent 361e2b5 commit c733178
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 204 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
* Avoid unnecessary BroadcastReceiver registration for monitoring device orientation
[#1303](https://github.com/bugsnag/bugsnag-android/pull/1303)

* Register system callbacks on background thread
[#1292](https://github.com/bugsnag/bugsnag-android/pull/1292)

## 5.9.5 (2021-06-25)

* Unity: Properly handle ANRs after multiple calls to autoNotify and autoDetectAnrs
Expand Down
30 changes: 24 additions & 6 deletions bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ public class Client implements MetadataAware, CallbackAware, UserAware {

final SessionTracker sessionTracker;

private final SystemBroadcastReceiver systemBroadcastReceiver;
final SystemBroadcastReceiver systemBroadcastReceiver;
private final ActivityBreadcrumbCollector activityBreadcrumbCollector;
private final SessionLifecycleCallback sessionLifecycleCallback;

private final Connectivity connectivity;
final Connectivity connectivity;

@Nullable
private final StorageManager storageManager;
Expand Down Expand Up @@ -220,9 +220,10 @@ public Unit invoke(String activity, Map<String, ?> metadata) {
exceptionHandler.install();
}

// register a receiver for automatic breadcrumbs
systemBroadcastReceiver = SystemBroadcastReceiver.register(this, logger, bgTaskService);
// register listeners for system events in the background.
systemBroadcastReceiver = new SystemBroadcastReceiver(this, logger);
registerComponentCallbacks();
registerListenersInBackground();

// load last run info
lastRunInfoStore = new LastRunInfoStore(immutableConfig);
Expand All @@ -231,8 +232,6 @@ public Unit invoke(String activity, Map<String, ?> metadata) {
// initialise plugins before attempting to flush any errors
loadPlugins(configuration);

connectivity.registerForNetworkChanges();

// Flush any on-disk errors and sessions
eventStore.flushOnLaunch();
eventStore.flushAsync();
Expand Down Expand Up @@ -296,6 +295,25 @@ public Unit invoke(String activity, Map<String, ?> metadata) {
this.exceptionHandler = exceptionHandler;
}

/**
* Registers listeners for system events in the background. This offloads work from the main
* thread that collects useful information from callbacks, but that don't need to be done
* immediately on client construction.
*/
void registerListenersInBackground() {
try {
bgTaskService.submitTask(TaskType.DEFAULT, new Runnable() {
@Override
public void run() {
connectivity.registerForNetworkChanges();
SystemBroadcastReceiver.register(appContext, systemBroadcastReceiver, logger);
}
});
} catch (RejectedExecutionException ex) {
logger.w("Failed to register for system events", ex);
}
}

private LastRunInfo loadLastRunInfo() {
LastRunInfo lastRunInfo = lastRunInfoStore.load();
LastRunInfo currentRunInfo = new LastRunInfo(0, false, false);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package com.bugsnag.android

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import java.util.HashMap

/**
* Used to automatically create breadcrumbs for system events
* Broadcast actions and categories can be found in text files in the android folder
* e.g. ~/Library/Android/sdk/platforms/android-9/data/broadcast_actions.txt
* See http://stackoverflow.com/a/27601497
*/
internal class SystemBroadcastReceiver(
private val client: Client,
private val logger: Logger
) : BroadcastReceiver() {

companion object {
private const val INTENT_ACTION_KEY = "Intent Action"

@JvmStatic
fun register(ctx: Context, receiver: SystemBroadcastReceiver, logger: Logger) {
if (receiver.actions.isNotEmpty()) {
val filter = IntentFilter()
receiver.actions.keys.forEach(filter::addAction)
ctx.registerReceiverSafe(receiver, filter, logger)
}
}

fun isAndroidKey(actionName: String): Boolean {
return actionName.startsWith("android.")
}

fun shortenActionNameIfNeeded(action: String): String {
return if (isAndroidKey(action)) {
action.substring(action.lastIndexOf(".") + 1)
} else {
action
}
}
}

val actions: Map<String, BreadcrumbType> = buildActions()

override fun onReceive(context: Context, intent: Intent) {
try {
val meta: MutableMap<String, Any> = HashMap()
val fullAction = intent.action ?: return
val shortAction = shortenActionNameIfNeeded(fullAction)
meta[INTENT_ACTION_KEY] = fullAction // always add the Intent Action
addExtrasToMetadata(intent, meta, shortAction)

val type = actions[fullAction] ?: BreadcrumbType.STATE
client.leaveBreadcrumb(shortAction, meta, type)
} catch (ex: Exception) {
logger.w("Failed to leave breadcrumb in SystemBroadcastReceiver: ${ex.message}")
}
}

private fun addExtrasToMetadata(
intent: Intent,
meta: MutableMap<String, Any>,
shortAction: String
) {
val extras = intent.extras
extras?.keySet()?.forEach { key ->
val valObj = extras[key] ?: return@forEach
val strVal = valObj.toString()
if (isAndroidKey(key)) { // shorten the Intent action
meta["Extra"] = "$shortAction: $strVal"
} else {
meta[key] = strVal
}
}
}

/**
* Builds a map of intent actions and their breadcrumb type (if enabled).
*
* Noisy breadcrumbs are omitted, along with anything that involves a state change.
* @return the action map
*/
private fun buildActions(): Map<String, BreadcrumbType> {
val actions: MutableMap<String, BreadcrumbType> = HashMap()
val config = client.config

if (!config.shouldDiscardBreadcrumb(BreadcrumbType.USER)) {
actions["android.appwidget.action.APPWIDGET_DELETED"] = BreadcrumbType.USER
actions["android.appwidget.action.APPWIDGET_DISABLED"] = BreadcrumbType.USER
actions["android.appwidget.action.APPWIDGET_ENABLED"] = BreadcrumbType.USER
actions["android.intent.action.CAMERA_BUTTON"] = BreadcrumbType.USER
actions["android.intent.action.CLOSE_SYSTEM_DIALOGS"] = BreadcrumbType.USER
actions["android.intent.action.DOCK_EVENT"] = BreadcrumbType.USER
}
if (!config.shouldDiscardBreadcrumb(BreadcrumbType.STATE)) {
actions["android.appwidget.action.APPWIDGET_HOST_RESTORED"] = BreadcrumbType.STATE
actions["android.appwidget.action.APPWIDGET_RESTORED"] = BreadcrumbType.STATE
actions["android.appwidget.action.APPWIDGET_UPDATE"] = BreadcrumbType.STATE
actions["android.appwidget.action.APPWIDGET_UPDATE_OPTIONS"] = BreadcrumbType.STATE
actions["android.intent.action.ACTION_POWER_CONNECTED"] = BreadcrumbType.STATE
actions["android.intent.action.ACTION_POWER_DISCONNECTED"] = BreadcrumbType.STATE
actions["android.intent.action.ACTION_SHUTDOWN"] = BreadcrumbType.STATE
actions["android.intent.action.AIRPLANE_MODE"] = BreadcrumbType.STATE
actions["android.intent.action.BATTERY_LOW"] = BreadcrumbType.STATE
actions["android.intent.action.BATTERY_OKAY"] = BreadcrumbType.STATE
actions["android.intent.action.BOOT_COMPLETED"] = BreadcrumbType.STATE
actions["android.intent.action.CONFIGURATION_CHANGED"] = BreadcrumbType.STATE
actions["android.intent.action.CONTENT_CHANGED"] = BreadcrumbType.STATE
actions["android.intent.action.DATE_CHANGED"] = BreadcrumbType.STATE
actions["android.intent.action.DEVICE_STORAGE_LOW"] = BreadcrumbType.STATE
actions["android.intent.action.DEVICE_STORAGE_OK"] = BreadcrumbType.STATE
actions["android.intent.action.INPUT_METHOD_CHANGED"] = BreadcrumbType.STATE
actions["android.intent.action.LOCALE_CHANGED"] = BreadcrumbType.STATE
actions["android.intent.action.REBOOT"] = BreadcrumbType.STATE
actions["android.intent.action.SCREEN_OFF"] = BreadcrumbType.STATE
actions["android.intent.action.SCREEN_ON"] = BreadcrumbType.STATE
actions["android.intent.action.TIMEZONE_CHANGED"] = BreadcrumbType.STATE
actions["android.intent.action.TIME_SET"] = BreadcrumbType.STATE
actions["android.os.action.DEVICE_IDLE_MODE_CHANGED"] = BreadcrumbType.STATE
actions["android.os.action.POWER_SAVE_MODE_CHANGED"] = BreadcrumbType.STATE
}
if (!config.shouldDiscardBreadcrumb(BreadcrumbType.NAVIGATION)) {
actions["android.intent.action.DREAMING_STARTED"] = BreadcrumbType.NAVIGATION
actions["android.intent.action.DREAMING_STOPPED"] = BreadcrumbType.NAVIGATION
}
return actions
}
}
Loading

0 comments on commit c733178

Please sign in to comment.