Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: relay onLowMemory events to client Observers #1262

Merged
merged 1 commit into from
May 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
* Small performance improvements to device and app state collection
[1258](https://github.com/bugsnag/bugsnag-android/pull/1258)

* NDK: lowMemory attribute is now reported as expected
[1262](https://github.com/bugsnag/bugsnag-android/pull/1262)

## 5.9.3 (2021-05-18)

* Avoid unnecessary collection of Thread stacktraces
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.bugsnag.android;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.bugsnag.android.ObserverInterfaceTest.BugsnagTestObserver;

import android.content.ComponentCallbacks;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;

/**
* Heavily mocked test to ensure that onLowMemory events are distributed to Client Observers
*/
@RunWith(MockitoJUnitRunner.class)
public class MemoryTrimTest {

@Spy
Context context = ApplicationProvider.getApplicationContext();

@Captor
ArgumentCaptor<ComponentCallbacks> componentCallbacksCaptor;

@Test
public void onLowMemoryEvent() {
when(context.getApplicationContext()).thenReturn(context);
Client client = new Client(context, BugsnagTestUtils.generateConfiguration());

// capture the registered ComponentCallbacks
verify(context, times(1)).registerComponentCallbacks(componentCallbacksCaptor.capture());

BugsnagTestObserver observer = new BugsnagTestObserver();
client.registerObserver(observer);

ComponentCallbacks callbacks = componentCallbacksCaptor.getValue();
callbacks.onLowMemory();

assertEquals(1, observer.observed.size());
Object observedEvent = observer.observed.get(0);

assertTrue(
"observed event should be UpdateMemoryTrimEvent",
observedEvent instanceof StateEvent.UpdateMemoryTrimEvent
);

assertTrue(
"observed event should be marked isLowMemory",
((StateEvent.UpdateMemoryTrimEvent) observedEvent).isLowMemory()
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ private <T extends StateEvent> T findMessageInQueue(Class<T> argClass) {
}

static class BugsnagTestObserver implements Observer {
private final ArrayList<Object> observed;
final ArrayList<Object> observed;

BugsnagTestObserver() {
observed = new ArrayList<>(4);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import androidx.annotation.VisibleForTesting;

import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.functions.Function2;

import java.util.ArrayList;
Expand Down Expand Up @@ -224,6 +225,7 @@ public Unit invoke(String activity, Map<String, ?> metadata) {
systemBroadcastReceiver = SystemBroadcastReceiver.register(this, logger, bgTaskService);

registerOrientationChangeListener();
registerMemoryTrimListener();

// load last run info
lastRunInfoStore = new LastRunInfoStore(immutableConfig);
Expand Down Expand Up @@ -357,6 +359,18 @@ public Unit invoke(String oldOrientation, String newOrientation) {
ContextExtensionsKt.registerReceiverSafe(appContext, receiver, configFilter, logger);
}

private void registerMemoryTrimListener() {
appContext.registerComponentCallbacks(new ClientComponentCallbacks(
new Function1<Boolean, Unit>() {
@Override
public Unit invoke(Boolean isLowMemory) {
clientObservable.postMemoryTrimEvent(isLowMemory);
return null;
}
}
));
}

void setupNdkPlugin() {
String lastRunInfoPath = lastRunInfoStore.getFile().getAbsolutePath();
int crashes = (lastRunInfo != null) ? lastRunInfo.getConsecutiveLaunchCrashes() : 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.bugsnag.android

import android.content.ComponentCallbacks
import android.content.res.Configuration

internal class ClientComponentCallbacks(
val callback: (Boolean) -> Unit
) : ComponentCallbacks {
override fun onConfigurationChanged(newConfig: Configuration) {}

override fun onLowMemory() {
callback(true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ internal class ClientObservable : BaseObservable() {
notifyObservers(StateEvent.UpdateOrientation(orientation))
}

fun postMemoryTrimEvent(isLowMemory: Boolean) {
notifyObservers(StateEvent.UpdateMemoryTrimEvent(isLowMemory))
}

fun postNdkInstall(conf: ImmutableConfig, lastRunInfoPath: String, consecutiveLaunchCrashes: Int) {
notifyObservers(
StateEvent.Install(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ sealed class StateEvent {
class UpdateOrientation(val orientation: String?) : StateEvent()

class UpdateUser(val user: User) : StateEvent()

class UpdateMemoryTrimEvent(val isLowMemory: Boolean) : StateEvent()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: StateEvent is a public API and is a sealed class

}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class NativeBridge : Observer {
external fun updateUserEmail(newValue: String)
external fun updateUserName(newValue: String)
external fun getUnwindStackFunction(): Long
external fun updateLowMemory(newValue: Boolean)

/**
* Creates a new native bridge for interacting with native components.
Expand Down Expand Up @@ -134,6 +135,7 @@ class NativeBridge : Observer {
updateUserName(makeSafe(msg.user.name ?: ""))
updateUserEmail(makeSafe(msg.user.email ?: ""))
}
is StateEvent.UpdateMemoryTrimEvent -> updateLowMemory(msg.isLowMemory)
}
}

Expand Down