Memory leak in CardboardActivity - android

While profiling my Google Cardboard application, I found out a very large memory leak (15Mb!) each time I left the activity with the 3D graphics.
After a long and grievous investigation, I found out that the source of the problem was a Context leak that happened each time I closed my CardboardActivity subclass.
The solution can be found in the accepted answer*
* wow... this is awkward... Note for any kind (and experienced) reviewer: I am writing a question to whom I know the answer already: am I supposed to do something for style, like add some fake suspense ("will our heroes prevail?! Find out in the accepted answer!"), like in a old Batman TV series or something?

After dicing and slicing my CardboardActivity subclass, until nothing else but the base class remained, I had to conclude that the base class itself was leaking the context.
I searched the web and found this post explaining how the activity in question leaked the context by failing to un-register a listener with a private instance of a class.
Upon trying to invoke said method manually (using reflection), I found out that in the current version of the Cardboard SDK (0.5.4 at the time of writing), the field is not present anymore.
Long story short: all sensors are now handled by an undocumented (yet public) SensorConnection class instantiated in CardboardActivity as a sensorConnection field, which is still plagued by the bug detailed in my first link.
This led me to this solution:
get the sensorConnection field in the CardboardActivity by reflection
use it to get the magneticSensor field, again by reflection
invoke the setOnCardboardTheaterListener with null argument, to clear the binding holding the reference to the Context in the Activity onDestroy method.
this boils down to the following code:
private void workAroundLeak() {
try {
// Get the sensor Connection
Class<?> c1 = Class.forName("com.google.vrtoolkit.cardboard.CardboardActivity");
Field sensorsField = c1.getDeclaredField("sensorConnection");
sensorsField.setAccessible(true);
SensorConnection sc = (SensorConnection) sensorsField.get(this);
if(sc == null) return;
// Get the magnetSensor
Class<?> c2 = Class.forName("com.google.vrtoolkit.cardboard.sensors.SensorConnection");
Field magnetField = c2.getDeclaredField("magnetSensor");
magnetField.setAccessible(true);
MagnetSensor ms = (MagnetSensor) magnetField.get(sc);
if(ms == null) return;
ms.setOnCardboardTriggerListener(null);
} catch(Exception e) {}
}
#Override
protected void onDestroy() {
workAroundLeak();
super.onDestroy();
}
which solved the problem entirely.
A word to the wise: since this solution relies on reflection, it might break (without consequences other than doing nothing, likely) as soon as Google will update the SDK (possibly fixing the issue in a clean way).
Hope this helps someone

Related

Unregistering a service in application that uses NSD

I'm making an app that uses Network Service Discovery, let's call it a "Wi-fi Chat". And at some point I want to unregister a service created earlier in order to avoid creation of countless copies of it. But the problem is, when I cal;
mNsdManager.unregisterService(mRegistrationListener);
I get "listener is not registered" error. To make sure that I have STILL THE SAME object of that listener I even initialized it in a class that extends Application class and still I get this error. So, the question is: how to unregister a service properly? Thank you in advance.
Also, I looked through "NsdChat" sample application, and it crashes at the same point with the same error!
Well, I kinda found a solution. Much thanks to this Wizard who fixed NsdChat Google example.
The solution is: in tearDown() method, inside which we call unregisterService(RegistrationListener listener) we should do this
public void tearDown() {
if (mRegistrationListener != null) {
try {
mNsdManager.unregisterService(mRegistrationListener);
} finally {
}
mRegistrationListener = null;
}
}
Though I still have no clue how actually this works, so if you have any thoughts regarding this puzzle, please post an answer

MediaRouter connect second time

I am using Android's MediaRouter / Presentation API (the support.v7 version).
Everything works fine so far. The only thing that doesn't is:
When I quit my activity (e.g.teardown & remove the callbacks), everything still works fine.
However, when starting this activity (the previous mediarouter-activity was forcefully finished, thus onPause/onDestroy was called FOR SURE => so those callbacks in there are gone too, as also shown in my debug messages) again at some later point in time, the callbacks get created and added and everything. Just, that there is no more onRouteAdded called, only onProviderChanged (With the default provider and thus useless).
It does always work like that (with wifi display [miracast], emulated secondary display, chromecast secondary display..). Are there any resolutions which are not in the examples?
Would you like to look at some code? Which special cases? (Can't post it all..)
I couldn't find anything so far, thanks for your help, in advance.
If you change the Google Cast sample app to support MediaRouter.Callback:
https://github.com/googlecast/CastPresentation-android
Then I'm getting the onRouteAdded called every time.
Using getSelectedRoute()instead of the RouteInfo (which is provided by the callbacks) did the job for me.
MediaRouter.RouteInfo selectedRoute = getHelper().getMediaRouter().getSelectedRoute();
if(provider != null && getCurrentRoute() != null && getCurrentRoute().equals(selectedRoute)){
Log.d(TAG, "only provider changes, dont do anything");
return false;
}
if (selectedRoute != null) {
setCurrentRoute(selectedRoute);
}
return updateContents();
this is definetly weird (as the rest of the code looks exactly as in the provided google android developer samples), but it works.
I know this problem was resolved over 1 year ago, but probably it isn't the perfect solution. Maybe it will be useful for somebody else.
I had similar problem with exactly the same symptoms (no more onRouteAdded called). In my situation it was caused by improperly implemented deactivation of MediaRouter: to deactivate it properly you should not only remove all of callbacks, but select default MediaRoute as well.
if (!mMediaRouter.getDefaultRoute().isSelected()) {
mMediaRouter.getDefaultRoute().select();
}

Java - Android : Thread being called (run) twice

I would like some help regarding Java - Android MultiThreading
While learning to develop my app in a multi-threading way in order to take advantage of the ever-growing multi-core devices market share (most devices are quad core now, some even octo-core), I ran in a situation where my threads are either being calling twice or running twice.
I just don't why and how.
[EDIT 3]
Alright, I narrowed down the issue : I called the AsyncTask from the onResume() method. Although my app did not lost focus (which would mean a call to onPause() then back to onResume() upon return of focus in which case my threads would be run twice) during the tests, I solved the issue by moving away the call to FetchFriendsList to another place.
So far so good, but since in my tests the app did not loose focus or perhaps it did but I could not witness it (!), I think there is another reason behind so I'd say my problem is not entirely solved ... at least for the moment. It does work though. Perhaps I did solve the issue but I do not know how :(
[end of EDIT 3]
I am implementing last Facebook SDK and I am using it to fetch the end-user friends list, which seems to do the work.
Since I am running this operation in an AsyncTask, I am not using request.executeAsync().
Instead I am using request.executeAndWait(). Facebook JavaDoc does state that this method must only be used if I am not in a the Main UI Thread which is my case otherwise I would get a NetworkOnMainThreadException.
Anyway, this is where the weird behavior is happening.
private final ArrayList<GraphUser> userFriendsList = new ArrayList<GraphUser>();
public final void fetchFriendsList() {
if (this.session != null && this.session.isOpened()) {
final Request requestUserFriendsList = Request.newMyFriendsRequest(
this.session, new Request.GraphUserListCallback()
public final void onCompleted(final List<GraphUser> users, final Response response) {
if (users != null && users.size() > 0) {
Log.v("Retrieved Friends List -> ", String.valueOf(users.size()));
userFriendsList.addAll(users);
}
}
}
);
if (this.asyncFlag)
requestUserFriendsList.executeAsync();
else
requestUserFriendsList.executeAndWait();
}
}
In my case, asyncFlag is set to false because I need to do stuff synchronously in that specific order :
Fetch User Friends List (not on the Main (UI) Thread)
Save friends list on device (separate new thread)
Save friends list on a server (separate new thread)
Following this pattern, the line userFriendsList.addAll(users); is called twice.
In the logcat, the Log.vis showed twice as well, and finally looking with the debugger, the content of the user friends list is made of duplicates.
But that's not all ... step 2 and 3 are indeed two separate threads which are both created and spawned within the same method : public final void asyncSaveFacebookFriendsList().
And guess what, this method is even called twice !
just why ?
At the beginning I was calling the method for step 2 and 3 like this :
[...]
userFriendsList.addAll(users);
asyncSaveFacebookFriendsList(); // it was private before
[...]
This is where the issue started as both line were running twice.
So I thought, alright, I'll call it later like this :
[...]
fetchFriendsList();
asyncSaveFacebookFriendsList(); // it is now public
[...]
But the issue remains still.
If I don't call public final void asyncSaveFacebookFriendsList(), then nothing is run twice.
Why does this issue happen ? Is there something I did not get in Java Threads ?
I do not think this is somehow related to the Facebook SDK because following the same pattern (and doing it also at the same time), I have the same issues when fetching and storing the end-user Twitter friends list.
So I do believe I am doing something wrong. Does someone have any idea in what possible case a thread is called twice ?
Note : all threads are started this way : thread.start(). I am not using any ThreadPool nor the ExecutorService.
In case you need more background context :
Content of AsyncTask : (no need to wonder why Void and Long, I remove the irrelevant code related to it)
private final class FetchFriendsLists extends AsyncTask<Long, Integer, Void> {
protected final Void doInBackground(final Long... params) {
if (params[0] != Long.valueOf(-1)) {
[...]
twitterAPI.fetchUserFriendsList();
publishProgress(1, -1);
}
if (params[1] == Long.valueOf(0)) {
[...]
facebookAPI.fetchFriendsList();
publishProgress(-1, 0);
}
return null;
}
protected final void onProgressUpdate(Integer... flags) {
super.onProgressUpdate(flags);
if (flags[0] != -1)
twitterAPI.asyncSaveFacebookFriendsList();
if (flags[1] == 0)
facebookAPI.asyncSaveFacebookFriendsList();
}
}
As you can see, I start step 2 and 3 in onPublishProgress() which runs on the Main UI Thread. Brefore it was in the doInBackground() method : the issue happens in both cases!
[EDIT]
After further test, it would seem any kind of code is in fact running twice.
I created a simple method called test in which in print a counter. The counter incremente twice as well !
Why you use onProgressUpdate?¿?
onProgressUpdate(Progress...), [...]. This method is used to display any form of progress in the
user interface while the background computation is still executing.
For instance, it can be used to animate a progress bar or show logs in
a text field.
This is used not at the finish of the petition, but when progress increased.
Read this:
http://developer.android.com/reference/android/os/AsyncTask.html
You need to use:
protected void onPostExecute(Long result) {

In App Billing v3 IllegalArgumentException using IabHelper

I've had in app billing v3 implemented in my app for about a week now. I used a lot of android's sample code to simplify the integration. I've been logging a crash fairly often that I can't seem to reproduce:
Exception Type: java.lang.RuntimeException
Reason: Unable to destroy activity {[package].billing.BillingActivity}: java.lang.IllegalArgumentException: Service not registered: [package].billing.util.IabHelper$1#40646a70
It seems to be breaking on this line:
if (mContext != null) mContext.unbindService(mServiceConn);
I'm binding this service in my onCreate method and disposing it in my onDestroy method (which is where this error is logged). Any pointers?
You could replace the line you mentioned:
if (mContext != null) mContext.unbindService(mServiceConn);
by this line
if (mContext != null && mService != null) mContext.unbindService(mServiceConn);
This should do the trick
I checked out the latest version of the sample project and up to today my recommendation is to currently to NOT use IabHelper. It is massively flawed.
To give you an idea:
1.) the async methods of IabHelper start a new thread. If IabHelper.dispose() is called while a thread is running you will always get various exceptions you cannot even handle.
2.) If the connection to the billing service goes down, they set it to null. But apart from that they never check if mService is null before accessing the methods. So it will always crash with NullPointerException in this case.
public void onServiceDisconnected(ComponentName name) {
logDebug("Billing service disconnected.");
mService = null;
and this is just the tip of the ice berg. Seriously I do not understand how somebody can publish this as reference code.
I just encountered the same issue but on android emulator. Billing v3 requires that Google Play app should be launched at least once and since the emulator lack of Google Play app it cannot set up helper and cannot dispose it in onDestroy().
My personal workaround is just skipping that error in try/catch:
#Override
protected void onDestroy() {
super.onDestroy();
if (bHelper != null){
try {
bHelper.dispose();
}catch (IllegalArgumentException ex){
ex.printStackTrace();
}finally{}
}
bHelper = null;
}
Add this in every onDestroy() where you dispose helper. Works fine for me.
The IabHelper class is working in a normal way.
What you need to do is:
when you call startSetup for the helper, you need to pass a callback IabHelper.OnIabSetupFinishedListener which will tell you the result of starting setup. If you get failure in the callback, the service connection with the google play services was not established.
You should handle future calls to IabHelper depending upon the result received in IabHelper.OnIabSetupFinishedListener. You can surely keep a boolean field to know what was the status.
The answer sam provided is actually a trick (in his own words). The helper classes aren't supposed to throw exceptions so that the user of those classes can implement some task in such scenarios.
And of-course, try/catch is best way if you don't want to go in details (whenever anything breaks due to exception, surely first thing which comes in mind is to put that in a try/catch block).

Detect if an android app is running on background

I want to check if my app is running on a background mode.
The problem is that i have many activities(list activities, map activities etc.). Initially I have tried in the life cycle's resume and pause(or the onUserLeaveHint) methods to set a static boolean as true or false and work with this way. But this obviously can't work because when I move from one activity to another, the previous one get paused.
Also, I've read here on stackoverflow that the getRunningTasks() should be used only for debugging purposes. I did a huge research but I can't find a solution. All I want to do is to be able to detect if a the app is running on a background. Can anyone propose me a way, or express any thought on how can I do that?
You can try the same mechanism (a boolean attribute) but on application side rather than activity side. Create a class which extends Application, declare it in the manifest file under <application android:name=YourClassApp>.
EDIT: I assume you know that activities aren't intended for background processing, if not you should take a look at the Services.
I don't know if this will help but you can use
getApplicaton().registerActivityLifecycleCallbacks(yourClass);
To get a birds eye view of how your activities are displayed in the FG. (For older s/w you can use this)
If your Application has a Service you could have a static get/set which accesses a static variable. Do not do this in Activities though, it causes mem leaks.
But realistically speaking there is no tidy way of tracking if your application is running or not.
I had the same problemen when overwriting the Firebase push messaging default behavior (show notifications only when in the background) I checked how Firebase did this by looking in the .class file com.google.firebase.messaging.zzb:53 (firebase-messaging:19.0.1) which appears to us getRunningAppProcesses. Mind you FireBase is created by Google them self. So I'm assuming it's pretty save to use. Cleaned up version:
List<ActivityManager.RunningAppProcessInfo> runningApps;
boolean isInForeground =false;
if ((runningApps = ((ActivityManager)this.getApplication().getSystemService(Context.ACTIVITY_SERVICE)).getRunningAppProcesses()) != null) {
Iterator runningApp = runningApps.iterator();
int myPid = Process.myPid();
while(runningApp.hasNext()) {
ActivityManager.RunningAppProcessInfo processInfo;
if ((processInfo = (ActivityManager.RunningAppProcessInfo)runningApp.next()).pid == myPid) {
isInForeground = processInfo.importance == 100;
break;
}
}
}

Categories

Resources