Android TV app in strict mode initialise my AAR library like this:
public class MainApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
LibraryApplication.init(this);
}}
The library
public class LibraryApplication extends Application {
#SuppressLint("StaticFieldLeak")
private static LibraryApplication instance;
#SuppressWarnings("unused")
public static void init(Application application) {
if (instance == null) {
instance = new LibraryApplication(application);
return;
}
throw new RuntimeException("Library already initialized.");
}
private final LibraryActivityHandler activityHandler;
public LibraryApplication(final Application application) {
this.activityHandler = new LibraryActivityHandler(this);
}}
LibraryActivityHandler
class LibraryActivityHandler implements Application.ActivityLifecycleCallbacks {
private Activity currentActivity;
#Override
public void onActivityResumed(Activity activity) {
assert activity != null;
this.currentActivity = activity;
//Just to show you that activity is really not null
currentActivity.getLayoutInflater();
Log.debug("activity set");
}
boolean isActivityNull() {
Log.debug("is null: " + (currentActivity == null));
return currentActivity == null;
}
Question: In the log I can see "activity set" and later I can see "is null: true". Can someone please explain to me how current Activity might become null ?
After over 10 years with Android and Java,
I am admitting that I don't have much ideas about it.
edit: The app is also using Dagger library, and the strict mode is not causing the issue.
The issue is caused by multi processes application.
Related
I have the below stack trace from leak canary with which I am not sure how my Activity is getting leaked
static LGCobtextHelper.mLGContext
references LGContext.mContext
references
ResourcesContextWrapperFactory$WebViewContextWrapper.mBase
references
com.*.*.activity.MyActivity.networkMonitor
references
com.*.*.NetworkMonitor.mPendingResult
references
android.app.LoadedApk$ReceiverDispatcher$Args.this$0
references
LoadedAok$ReceiverDispathcer.mContext
leaks MyActivity instance
MyActivity extends BaseActivity, which registers onResume() and unregisters onPause(), so not sure which leaks the activity
NetworkMonitor.java
public class NetworkMonitor extends BroadcastReceiver {
private final WebSocketClient webSocketClient;
private final ArmingHelper armingHelper;
private final ShutdownManager shutdownManager;
private final CameraThumbnailCache cameraThumbnailCache;
private final CameraAccessManager cameraAccessManager;
private final JoustLogger joustLogger;
private Activity registeredActivity;
private String currentNetworkName;
private List<NetworkStatusChangeListener> networkChangeListeners;
public interface NetworkStatusChangeListener {
void onNetworkUp();
void onNetworkDown();
}
public NetworkMonitor(WebSocketClient webSocketClient, ArmingHelper armingHelper, ShutdownManager shutdownManager, CameraThumbnailCache cameraThumbnailCache, CameraAccessManager cameraAccessManager, JoustLogger joustLogger) {
this.webSocketClient = webSocketClient;
this.armingHelper = armingHelper;
this.shutdownManager = shutdownManager;
this.cameraThumbnailCache = cameraThumbnailCache;
this.cameraAccessManager = cameraAccessManager;
this.joustLogger = joustLogger;
networkChangeListeners = new ArrayList<>();
}
// Activities *must* call this method in onResume() in order for
// the app to watch for network changes
public void startListeningForNetworkChanges(Activity registeringActivity) {
if (!(registeringActivity instanceof NetworkStatusChangeListener)) {
throw new IllegalArgumentException("Registering Activity must implement NetworkStatusChangeListener");
}
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intentFilter.addAction(GlobalConstants.ANDROID_NET_WIFI_WIFI_STATE_CHANGED);
registeringActivity.registerReceiver(this, intentFilter);
this.registeredActivity = registeringActivity;
registerListenerForNetworkChanges((NetworkStatusChangeListener)registeringActivity);
}
// Activities *must* call this method in onPause() in order to properly
// unregister the receiver that was set in onResume()
public void stopListeningForNetworkChanges(Activity registeringActivity) {
registeringActivity.unregisterReceiver(this);
unregisterListenerForNetworkChanges((NetworkStatusChangeListener)registeringActivity);
registeredActivity = null;
}
// Fragments can use this method to register for Network change updates, call in onResume()
public void registerListenerForNetworkChanges(NetworkStatusChangeListener listener) {
networkChangeListeners.add(listener);
}
// Fragments need to unregister in onPause()
public void unregisterListenerForNetworkChanges(NetworkStatusChangeListener listener) {
networkChangeListeners.remove(listener);
}
#Override
public void onReceive(Context context, Intent intent) {
checkNetworkConnection();
}
public void checkNetworkConnection() {
if (registeredActivity != null) {
final ConnectivityManager connectivityManager = (ConnectivityManager) registeredActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnectedOrConnecting()) {
String newNetworkName = networkInfo.getTypeName();
if (currentNetworkName == null || !currentNetworkName.equals(newNetworkName)) {
Timber.d("Network(%s) Connected", newNetworkName);
// Our network was down, but now it's up. Validate the Websocket
currentNetworkName = newNetworkName;
cameraThumbnailCache.clearInternalURLPreferences();
webSocketClient.reopenWebsocketIfPossible();
cameraAccessManager.onNetworkUp();
if (ActivityBehaviorHelper.needsSecurityCountdown(registeredActivity)) {
armingHelper.startTimerIfReady();
}
for (NetworkStatusChangeListener listener : networkChangeListeners) {
listener.onNetworkUp();
}
joustLogger.onNetworkUp();
}
} else {
Timber.w("Network Down");
currentNetworkName = null;
cameraAccessManager.onNetworkDown();
joustLogger.onNetworkDown();
shutdownManager.onNetworkDown();
for (NetworkStatusChangeListener listener : networkChangeListeners) {
listener.onNetworkDown();
}
}
}
}
}
BaseActivity.java
#Override
protected void onResume() {
super.onResume();
networkMonitor.startListeningForNetworkChanges(this);
}
#Override
protected void onPause() {
networkMonitor.stopListeningForNetworkChanges(this);
super.onPause();
}
It looks like you probably don't need to be holding a reference to the Activity in that NetworkMonitor class. That's probably the source of your memory leak - the Activity reference is likely being held after the Activity is destroyed. Looks like you could just pass the context in as a parameter to the methods that need it.
Also, For a few of the spots where Activity context is being used here, like context.getSystemService(Context.CONNECTIVITY_SERVICE), you could use Application context instead and possibly avoid needing an Activity reference altogether.
I would like to check that my app shows an error message when the device it is running on has no camera. I have tried passing in a mock context but mockito gives an error when I try to mock the CameraManager class as it is declared final. Surely android has a simple solution for this? Here's my attempt:
public class CreateNewIdentityActivityUnitTest extends ActivityUnitTestCase<CreateNewIdentityActivity> {
public CreateNewIdentityActivityUnitTest() {
super(CreateNewIdentityActivity.class);
}
public void testErrorMessageDisplayedWhenNoCamerasExist() throws Exception {
// Create the mock cameramanager
// THIS LINE FAILS BECAUSE CAMERAMANAGER IS FINAL
CameraManager mockCameraManager = mock(CameraManager.class);
String[] cameraIdList = {};
when(mockCameraManager.getCameraIdList()).thenReturn(cameraIdList);
// Create the mock context
Context mockContext = mock(Context.class);
when(mockContext.getSystemService(Context.CAMERA_SERVICE)).thenReturn(mockCameraManager);
// Start the activity
Intent intent = new Intent(mockContext, CreateNewIdentityActivity.class);
Activity activity = startActivity(intent, null, null);
// Verify that the error message was made visible
TextView errorTextView = (TextView)activity.findViewById(R.id.ErrorTextView);
assertNotNull(errorTextView);
assertEquals(View.VISIBLE, errorTextView.getVisibility());
}
}
Unfortunately, you can't mock final class.
There're few options/hacks:
Try to add Robolectric library and write test with it's ShadowCamera
Move logic related to CameraManager into a separate class and inject it in Activity. Then in the Test project, you can override this injection.
Pretty similar idea - create an interface OnCameraManagerInterface
public interface OnCameraManagerInterface {
String[] getListOfCameras() throws CameraAccessException;
}
Then implement it in the Activity:
public class CreateNewIdentityActivity extends AppCompatActivity
implements OnCameraManagerInterface {
.......
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public String[] getListOfCameras() throws CameraAccessException {
return ((CameraManager)getSystemService(Context.CAMERA_SERVICE)).
getCameraIdList();
}
}
And in the code, where you check camera existence - call: if (getListOfCameras().length == 0) {}
Now, add new TestCreateNewIdentityActivity to override your CreateNewIdentityActivity:
public class TestCreateNewIdentityActivity extends CreateNewIdentityActivity {
#Override
public String[] getListOfCameras() throws CameraAccessException {
return new String[0];
}
}
In Manifest:
<activity android:name=".TestCreateNewIdentityActivity"
android:theme="#style/AppTheme.NoActionBar"/>
And test will look like:
public class CreateNewIdentityActivityUnitTest extends ActivityUnitTestCase<TestCreateNewIdentityActivity> {
public CreateNewIdentityActivityUnitTest() {
super(TestCreateNewIdentityActivity.class);
}
public void testErrorMessageDisplayedWhenNoCamerasExist() throws Exception {
// Verify that the error message was made visible
TextView errorTextView = (TextView)getActivity().findViewById(R.id.ErrorTextView);
assertNotNull(errorTextView);
assertEquals(View.VISIBLE, errorTextView.getVisibility());
}
}
I'm pretty sure, it doable even without adding the TestActivity into the main source code and to manifest(to keep it in androidTest, though I didn't look)
Hybrid variant without creation of new activity:
public class ActivityCameraManager {
private boolean isTest = false;
private CameraManager cameraManager;
public ActivityCameraManager(CameraManager cameraManager) {
this.cameraManager = cameraManager;
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public String[] getListOfCameras() throws CameraAccessException {
if (isTest) {
return new String[0];
}
return cameraManager.getCameraIdList();
}
public void setTestMode() {
isTest = true;
}
}
Then your activity is gonna look like:
public class MainActivity extends AppCompatActivity {
ActivityCameraManager activityCameraManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
activityCameraManager = new ActivityCameraManager((CameraManager) getSystemService(Context.CAMERA_SERVICE));
}
public void setActivityCameraManagerForTest() {
activityCameraManager.setTestMode();
}
}
And in test just call getActivity().setActivityCameraManagerForTest();
I hope, it helps
The answer is late but here is a mock camera example for Android.
You can set the VideoFileInputSource to mock camera from video file
textureVideoInputSource = new VideoFileInputSource(this, "mock_input_video.mp4");
or you can enable hardware camera for video stream.
textureVideoInputSource = new CameraTextureVideoInputSource(this);
You can refer the answer here.
https://stackoverflow.com/a/38456189/1053097
I'm using a singleton class SingletonA for my resources and I have a service ServiceS who uses the resources.
public class SingletonA {
private static SingletonA ourInstance = new SingletonA();
public static SingletonA getInstance() { return ourInstance; }
private SingletonA () {}
String resources;
synchronized public void importSomething() {
resources = "I have some value now";
}
}
public class ServiceS extends Handler {
private static ServiceS ourInstance = new ServiceS();
public static ServiceS getInstance() { return ourInstance; }
private ServiceS () {}
SingletonA sa = SingletonA.getInstance();
public void printResources() {
println(sa.resources);
}
}
public class MyActivity extends Activity {
SingletonA sa = SingletonA.getInstance();
#override
protected void onCreateBundle savedInstanceState) {
super.onCreate(savedInstanceState);
sa.importSomthing();
ServiceS.printResources();
}
}
-> In ServicesS class, sa value is null -> sa.printResources() causes NPE
However, since I add one more sa = SingletonA.getInstance(); into ServiceS.printResources() like this:
public void printResources() {
sa = SingletonA.getInstance();
println(sa);
}
-> It worked: sa != null and resources = "I have some value now".
Can someone explain for me why sa in ServiceS still null ? Thanks,
As the comment of Buddy:
it's due to static initialization order. The static ServiceS.ourService is initialized before SingletonA.ourInstance.
See here for more information on static initialization order. But the easy solution would be to just call SingletonA.getInstance() when necessary (vs caching in a member variable). Or to lazily initialize ServiceS.ourInstance inside getInstance
I'm trying to trace AOSP code from the grepcode site.
When I call getSystemService(Context.WIFI_P2P_SERVICE), it gets to the following code:
#Override public Object getSystemService(String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
And since WIFI_P2P_SERVICE declared as public static final String WIFI_P2P_SERVICE = "wifip2p";, if will not fall in one of the conditions and will go to the super.getSystemService(name);
Activity extends ContextThemeWrapper, the code there is:
#Override public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(mBase).cloneInContext(this);
}
return mInflater;
}
return mBase.getSystemService(name);
}
Here also, the required service name will not match, mBase is an instance of Context so the code in Context is:
public abstract Object getSystemService(String name);
which means that classes which extends from it must handle that functionality.
Well, Where my request is being treated?
As far as i know the implementation code of Context is under the package android.app with class name ContextImpl
Here is getSystemService from that class -
#Override
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
Edit -
The entry point for WIFI_P2P_SERVICE -
registerService(WIFI_P2P_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(WIFI_P2P_SERVICE);
IWifiP2pManager service = IWifiP2pManager.Stub.asInterface(b);
return new WifiP2pManager(service);
}});
I'm trying to track down a new null pointer exception which is appearing in my ACRA logs and which I can't reproduce. Here's the relevant code:
public class MyApplication extends Application {
public void onCreate() {
DataManager.instance().initializeData(this);
}
}
public class DataManager {
private static DataManger instance = new DataManger();
private List<DataModel> dataModels;
private List<I_Callback> callbacks = new ArrayList<I_Callback>();
private boolean isInitialized = false;
private DataManager(){}
public static DataManager instance() {
return instance;
}
public void initializeData(Context context) {
new DataManagerInitializer().execute(context);
}
public void setDataModels(List<DataModel> models) {
dataModels = models;
}
public void synchronized registerInitializeCallbacks(I_Callback callback) {
if (isInitialized) {
callback.executeCallback();
} else {
callbacks.add(callback);
}
}
public void synchronized setInitialized() {
isInitialized = true;
for (I_Callback callback:callbacks) {
callback.executeCallback();
}
callbacks.clear();
}
}
public class DataManagerInitializer extends AsyncTask<Context, Void, Void>{
protected Void doInBackground(Context... contexts){
List<DataModel> dataModels = new ArrayList<DataModel>();
/*various code to create DataModel objects and add to dataModels list*/
DataManager.instance().setDataModels(dataModels);
return null;
}
protected void onPostExecute(Void result) {
DataManager.instance().setInitialized();
}
}
public class ActivityA extends Activity implements I_Callback{
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.graphical_layout);
DataManager.instance().registerInitializeCallbacks(this);
}
public void executeCallback() {
/* wire up button to call Activity B */
}
}
public class ActivityB extends Activity {
public void onCreate(Bundle savedInstanceState) {
List<DataModel> dataModels = DataManager.instance().getDataModels();
/* The following line of code throws a null pointer exception
in the stack trace*/
for (int i=0; i < dataModels.size(); i++){
/* do something with the data model */
}
}
}
To break down the above more simply, the application is launched which kicks off the initializion of the data manager singleton. ActivityA, the main activity, launches and waits for the data manager to complete initialization before allowing any actions, wiring up any events, etc. From ActivityA, its not possible to get to ActivityB without the call back method executing and ActivityB is only reachable from ActivityA. The only way for the list of data models to be null in the DataManager is for it to not have been initialized, but I'm struggling to see how this is possible. Any suggestions on how my null pointer may have occurred?
private static DataManger instance = new DataManger();
...
public static DataManager instance() {
return instance;
}
Is where the problem is. So your instance variable is getting garbage collected. As it is instantiated when it is declared, it is not being appropriately re-instantiated. So, try this instead:
private static DataManger instance = null;
...
public static DataManager instance() {
if (instance == null){
instance = new DataManager();
}
return instance;
}
This will ensure the call to instance() (usually called getInstance() but this is only convention), will return a valid single instance of the datamanager. Try to avoid instantiating global variables with their declaration, to avoid this specific problem.
Let's assume that:
you are interacting with the Activity B
press the home button:
start playing with other apps (consuming memory)
at some point the so needs memory and it's gonna start garbage collecting objects, included your "instance".
If that happens when you launch your app the framework will resume the activity B and the npe will happen.
You need to re-create the instance (in the activity B) if it is null.