Why does my WeakReference gets NULL inside my AsyncTask? - android

I have spent a lot of hours trying to reproduce and understand the cause of this problem, with no success in either of these goals.
I have tried to leave only the code related to the problem, but I believe a few minutes are still necessary to understand the problem and context. I hope that someone will be able to spot the problem in my implementation or at least help me understand the cause.
Description of the application:
Word game where you play against the computer. After the computer has laid a word on the board, the definition of this word is fetched online in an AsyncTask and displayed in a TextView
How I discovered the issue:
I use ACRA for crash and error reporting (great free tool by the way). It sends me reports for each unexpected situtation (this one does not lead to a crash). I have been receiving many reports of errors 1,2,3 and 4 (see code)
Some bad reviews on Google Play tend to show that some users do not see the definition even though they are connected to Internet. (I am pretty sure this functional bug is related to the previously mentioned errors, though I cannot prove it)
A word on the code design:
After reading a lot on memory leaks in Android, I have decided to make the AsyncTask that retrieves the definition online a static inner class (even though my main activty currently does not support rotations, which are the main causes of leaks: I put in my Manifest android:screenOrientation="portrait").
I need access to the parent Activity from this AsyncTask because I retrieve strings from the resources, and perform some changes on the UI in onPostExecute().
Hence, I use a WeakReference in the AsyncTask which is pointing to the parent Activity. This should prevent memory leaks in case the Activity is recreated or killed while theAsyncTask` is still running.
What exactly is the problem:
The WeakReference or the return of its get() method is null in
some unexplained situations (I suspect it impacts more than 1% of the games or
players) (see code)
All kinds of devices and Android versions are impacted, and I often see several occurences coming from the same device)
I have never been able to reproduce these errors (the most obvious try was exiting the activity while the definition is being downloaded, but this didn't cause any error)
Meaningful parts of my code:
public class GameActivity extends Activity {
private TextView _definition; //inflated from XML in onCreate()
private ProgressDialog _pDialog; //created in onCreate()
private Handler _handlerToDelayDroidMove = new Handler();
private Handler _handlerToDelayProgressDialog = new Handler();
private Handler _handlerToDelayDefinitionClosure = new Handler();
public void onClickValidatePlayerMoveAndTriggerDroidMove(View v) {
int score = _arbitre.validatePlayerMoveAndReturnScore(_listOfLetters);
toast(String.format(getResources().getString(R.string.player_word_score), score));
// ***** Only start Droid move when previous toast has been displayed ****
timedDroidPlayWithSpinner();
}
private void timedDroidPlayWithSpinner() {
_handlerToDelayProgressDialog.removeCallbacks(_droidThinkingDialogRunnable);
_handlerToDelayDroidMove.removeCallbacks(_droidPlayRunnable);
_handlerToDelayProgressDialog.postDelayed(_droidThinkingDialogRunnable, 1500);
_handlerToDelayDroidMove.postDelayed(_droidPlayRunnable, 1500 + DUMMY_DELAY);
}
private Runnable _droidThinkingDialogRunnable = new Runnable() { //Show a "Droid is thinking spinner dialog"
public void run() {
_pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
_pDialog.setMessage(getResources().getString(R.string.droid_thinking));
_pDialog.setCancelable(false);
_pDialog.show();
}
};
private Runnable _droidPlayRunnable = new Runnable() {
public void run() {
String word = playBestMoveAndUpdateGUI(); // Droid move (CPU intensive, can take several seconds)
saveGameStateToPrefs();
_pDialog.dismiss(); //Hide "Thinking dialog")
new SearchDefinitionTask(GameActivity.this).execute(word);
}
};
private Runnable _hideDefinitionRunnable = new Runnable() {
public void run() {
_definition.startAnimation(_slideUpAnim);
_definition.setVisibility(View.GONE);
}
};
// Made static so we are sure if does not reference the Activity (risk of leak)
public static class SearchDefinitionTask extends AsyncTask<String, Void, String[]> {
private WeakReference<GameActivity> weakRefToGameActivity;
public SearchDefinitionTask(GameActivity context) { //Save a weak reference to the Activity
super();
weakRefToGameActivity = new WeakReference<GameActivity>(context);
}
protected String[] doInBackground(String... words) {
try {
DefFetcherInterface defFetcher = null;
Language l = weakRefToGameActivity.get()._dictionaryId;
defFetcher = new OnlineDefinitionFetcher(l);
return defFetcher.getDefinition(words[0]);
} catch (Exception e) { // Typical exceptions are due to lack of internet connectivity
Log.e("Definition fetch error: ", e.toString());
String[] ret = { "", "" };
ret[0] = mots[0];
if (weakRefToGameActivity == null) { // !!! This occurs in ~0.3% of the games !!!
ErrorReporter.getInstance().handleSilentException(new Exception("Silent ERROR 1: weakRef is NULL"));
return ret;
}
if (weakRefToGameActivity.get() == null) { !!! This occurs in ~1% of the games !!!
ErrorReporter.getInstance().handleSilentException(new Exception("Silent ERROR 2: weakRef.get() is NULL"));
return ret;
}
// If we get here we still have a reference on our Activit/context, so let's show a decent error message
ret[1] = weakRefToGameActivity.get().getResources().getString(R.string.no_connection);
return ret;
}
}
protected void onPostExecute(String[] result) {
if (result[0] != "") { //Don't send another error report if WeakRef was already NULL in doInBackground()
if (weakRefToGameActivity == null) { !!! This occurs in ~0.5% of the games !!!
ErrorReporter.getInstance().handleSilentException(new Exception("Silent ERROR 3: weakRef is NULL"));
} else if (weakRefToGameActivity.get() == null) { !!!!!!!! This occurs in ~1% of the games !!!!!!!!
ErrorReporter.getInstance().handleSilentException(new Exception("Silent ERROR 4: weakRef.get() is NULL"));
} else {
// Everything is fine, show a box with the definition of the word for a few seconds
//(with animation to make the box appearing from the top of the screen)
weakRefToGameActivity.get()._definition.setVisibility(ImageView.VISIBLE);
weakRefToGameActivity.get()._handlerToDelayDefinitionClosure.removeCallbacks(weakRefToGameActivity.get()._hideDefinitionRunnable);
weakRefToGameActivity.get()._definition.setText(Html.fromHtml("<b>" + result[0].toUpperCase() + "</b> " + result[1]));
weakRefToGameActivity.get()._definition.startAnimation(weakRefToGameActivity.get()._slideDownAnim);
weakRefToGameActivity.get()._handlerToDelayDefinitionClosure.postDelayed(weakRefToGameActivity.get()._hideDefinitionRunnable,
DURATION_OF_DEFINITION);
}
}
}
}
}
Any idea of what could go wrong or how to reproduce?

Sebastien, maybe you can try to check the onDestroy is never called for your Activity... The activity can be restarted when the screen is rotated (which you already handle), but there are other configuration changes that may cause the same behavior.
Another pretty common one is to take the keyboard out on some phones, but there are others that are even more obscure to me. You can see the list there
Beside that, I really don't see anything wrong in your code and cannot imagine what else could cause your trouble.
The worst ones are your errors 1 and 3. Can you check in the constructor that weakRefToGameActivity is not null after it is created? (and if it is null, what about the context argument).
Please post updates once you find the root cause of your problem.
Bonne chance.

Related

Android, application stopped

I have an android (4.1) application which reportedly (can't recreate) chrashes with the message "app as stopped". The problem, however, is that the user has to press "OK" in the alert that pops up. The chrash only occur when the app is not active (on screen). This indicates that Android kills of my app because of memory or naughtiness. I'f been investigating for memory leaks, because i handle bitmaps in the app, that did not pay off.
I have a catch and log all default handler like this:
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
public void uncaughtException(Thread thread, Throwable ex) {
Log.e(StaticData.LogTag, "Unhandled exception app", ex);
}
});
To log all exceptions. Afterwards i call original exceptionhandler. This handler is put on the Apps main activity. The method is never called when the "stopped" chrash happens, but is in other cases.
My app uses IntentService to send data to a server in background. This is not a long running service, 1-10s. I will try to put a default exception handler on the service as well. I mention the service because the app is killed when "off screen", so I thought that might have a connection to the problem, but the cause evades me.
Furthermore I use BroadcastReceiver to notify the apps main activity about network connection changes, because the app is used in turbulent network conditions. This is relevant because I'f seen BroadcastReceiver mentioned when people talk about possible memory leak issues. My implementation of BradcastReceiver goes like this:
Serivce side:
sendOrderedBroadcast(uploadedIntent, null);
Activity side:
public static class NetworkStateReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent in) {
// super.onReceive(context, intent);
Log.d(StaticData.LogTag, "Network connectivity change");
if (in.getExtras() != null) {
NetworkInfo ni = (NetworkInfo) in.getExtras().get(ConnectivityManager.EXTRA_NETWORK_INFO);
if (ni != null && ni.getState() == NetworkInfo.State.CONNECTED) {
Log.i(StaticData.LogTag, "Network " + ni.getTypeName() + " connected");
...
}
}
if (in.getExtras().getBoolean(ConnectivityManager.EXTRA_NO_CONNECTIVITY, Boolean.FALSE)) {
Log.d(StaticData.LogTag, "There's no network connectivity");
}
}
}
As i mentioned in the beginning, the problem is mainly an annoyance for the user because he has to press ok on a popup when looking at mails or taking a call. The app is robust enough to handle that it gets killed from time to time, however, I would like to figure out why my app is knocked out.
PS. I have tried to get the users to send be bug reports via mx log logcollector, but no cigar.
I seem to have solved this problem. I think the culprit was a static reference to the main activity in the application. I was using this static to get a reference to the applications Context. This however this is unnecessary in Service and BroadcastReceiver, because they have their own ref to a "Context".
Why the long face?
A static reference to a Android view, like Activity, is kept in memory and can't be freed by the garbage collector. This was the idea behind making it static. I wanted to have a future reference to the object, but is the wrong approach.
When changing orientation (eg.), Android recreates the current activity (and children views), and the old is freed for GC. When you have a static ref to a view, its not collected, and now you have 2 instances of the Activity. Do the math and realize that this obviously fills memory over time (leak) and Android will nuke the app at some point.
When facing this problem you have to realize that you have to carry your data/states over to the new Activity, for example by using shared memory, a local database or a savedInstanceState:
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
int value = savedInstanceState.getInteger("key");
}
}
#Override
protected void onSaveInstanceState(final Bundle outState) {
outState.putInteger("key", value);
}

Need help to understand memory leak in my Android app

My app runs fine until I interrupt the initialization process at the very first start after installation by exiting and launching the app several times as long as the initialization process has not yet been finished. The processing logic and the AsyncTask can handle this pretty well, so I don't get any inconsistencies, but I have a problem with the heap. It increasing more and more while I do this disturbing exits and launches at app setup, which will lead to OutOfMemory error. I already found a leak by analyzing the heap with MAT but I still have another leak which I can't isolate yet.
For background info: I store the application context, a list and a timestamp in a static class to be able to access it from classes anywhere in my application without using tedious passing references by constructor.
Anyway, there must be something wrong with this static class (ApplicationContext) since it causes a memory leak due to the list of zones. Zone objects are processed GeoJSON data. This is how this class looks like:
public class ApplicationContext extends Application {
private static Context context;
private static String timestamp;
private static List<Zone> zones = new ArrayList<Zone>();
public void onCreate() {
super.onCreate();
ApplicationContext.context = getApplicationContext();
}
public static Context getAppContext() {
return ApplicationContext.context;
}
public static List<Zone> getZones() {
return zones;
}
public static void setData(String timestamp, List<Zone> zones) {
ApplicationContext.timestamp = timestamp;
ApplicationContext.zones = zones;
}
public static String getTimestamp() {
return timestamp;
}
}
I already tried to store the zones like this
ApplicationContext.zones = new ArrayList(zones);
but it had no effect. I already tried to put the zones attribute into another static class since ApplicationContext is loaded before all other classes (due to an entry in AndroidManifest) which could lead to such behavior but this isn't the problem too.
setData is invoked in my "ProcessController" twice. Once in doUpdateFromStorage, and once in doUpdateFromUrl(String). This class looks like this:
public final class ProcessController {
private HttpClient httpClient = new HttpClient();
public final InitializationResult initializeData() {
String urlTimestamp;
try {
urlTimestamp = getTimestampDataFromUrl();
if (isModelEmpty()) {
if (storageFilesExist()) {
try {
String localTimestamp = getLocalTimestamp();
if (isStorageDataUpToDate(localTimestamp, urlTimestamp)) {
return doDataUpdateFromStorage();
}
else {
return doDataUpdateFromUrl(urlTimestamp);
}
}
catch (IOException e) {
return new InitializationResult(false, Errors.cannotReadTimestampFile());
}
}
else {
try {
createNewFiles();
return doDataUpdateFromUrl(urlTimestamp);
}
catch (IOException e) {
return new InitializationResult(false, Errors.fileCreationFailed());
}
}
}
else {
if (isApplicationContextDataUpToDate(urlTimestamp)) {
return new InitializationResult(true, "");
}
else {
return doDataUpdateFromUrl(urlTimestamp);
}
}
}
catch (IOException e1) {
return new InitializationResult(false, Errors.noTimestampConnection());
}
}
private String getTimestampDataFromUrl() throws IOException {
if (ProcessNotification.isCancelled()) {
throw new InterruptedIOException();
}
return httpClient.getDataFromUrl(FileType.TIMESTAMP);
}
private String getJsonDataFromUrl() throws IOException {
if (ProcessNotification.isCancelled()) {
throw new InterruptedIOException();
}
return httpClient.getDataFromUrl(FileType.JSONDATA);
}
private String getLocalTimestamp() throws IOException {
if (ProcessNotification.isCancelled()) {
throw new InterruptedIOException();
}
return PersistenceManager.getFileData(FileType.TIMESTAMP);
}
private List<Zone> getLocalJsonData() throws IOException, ParseException {
if (ProcessNotification.isCancelled()) {
throw new InterruptedIOException();
}
return JsonStringParser.parse(PersistenceManager.getFileData(FileType.JSONDATA));
}
private InitializationResult doDataUpdateFromStorage() throws InterruptedIOException {
if (ProcessNotification.isCancelled()) {
throw new InterruptedIOException();
}
try {
ApplicationContext.setData(getLocalTimestamp(), getLocalJsonData());
return new InitializationResult(true, "");
}
catch (IOException e) {
return new InitializationResult(false, Errors.cannotReadJsonFile());
}
catch (ParseException e) {
return new InitializationResult(false, Errors.parseError());
}
}
private InitializationResult doDataUpdateFromUrl(String urlTimestamp) throws InterruptedIOException {
if (ProcessNotification.isCancelled()) {
throw new InterruptedIOException();
}
String jsonData;
List<Zone> zones;
try {
jsonData = getJsonDataFromUrl();
zones = JsonStringParser.parse(jsonData);
try {
PersistenceManager.persist(jsonData, FileType.JSONDATA);
PersistenceManager.persist(urlTimestamp, FileType.TIMESTAMP);
ApplicationContext.setData(urlTimestamp, zones);
return new InitializationResult(true, "");
}
catch (IOException e) {
return new InitializationResult(false, Errors.filePersistError());
}
}
catch (IOException e) {
return new InitializationResult(false, Errors.noJsonConnection());
}
catch (ParseException e) {
return new InitializationResult(false, Errors.parseError());
}
}
private boolean isModelEmpty() {
if (ApplicationContext.getZones() == null || ApplicationContext.getZones().isEmpty()) {
return true;
}
return false;
}
private boolean isApplicationContextDataUpToDate(String urlTimestamp) {
if (ApplicationContext.getTimestamp() == null) {
return false;
}
String localTimestamp = ApplicationContext.getTimestamp();
if (!localTimestamp.equals(urlTimestamp)) {
return false;
}
return true;
}
private boolean isStorageDataUpToDate(String localTimestamp, String urlTimestamp) {
if (localTimestamp.equals(urlTimestamp)) {
return true;
}
return false;
}
private boolean storageFilesExist() {
return PersistenceManager.filesExist();
}
private void createNewFiles() throws IOException {
PersistenceManager.createNewFiles();
}
}
Maybe it's another helpful information, that this ProcessController is invoked by my MainActivity's AsyncTask at the app setup:
public class InitializationTask extends AsyncTask<Void, Void, InitializationResult> {
private ProcessController processController = new ProcessController();
private ProgressDialog progressDialog;
private MainActivity mainActivity;
private final String TAG = this.getClass().getSimpleName();
public InitializationTask(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
ProcessNotification.setCancelled(false);
progressDialog = new ProgressDialog(mainActivity);
progressDialog.setMessage("Processing.\nPlease wait...");
progressDialog.setIndeterminate(true); //means that the "loading amount" is not measured.
progressDialog.setCancelable(true);
progressDialog.show();
};
#Override
protected InitializationResult doInBackground(Void... params) {
return processController.initializeData();
}
#Override
protected void onPostExecute(InitializationResult result) {
super.onPostExecute(result);
progressDialog.dismiss();
if (result.isValid()) {
mainActivity.finalizeSetup();
}
else {
AlertDialog.Builder dialog = new AlertDialog.Builder(mainActivity);
dialog.setTitle("Error on initialization");
dialog.setMessage(result.getReason());
dialog.setPositiveButton("Ok",
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
mainActivity.finish();
}
});
dialog.show();
}
processController = null;
}
#Override
protected void onCancelled() {
super.onCancelled();
Log.i(TAG, "onCancelled executed");
Log.i(TAG, "set CancelNotification status to cancelled.");
ProcessNotification.setCancelled(true);
progressDialog.dismiss();
try {
Log.i(TAG, "clearing files");
PersistenceManager.clearFiles();
Log.i(TAG, "files cleared");
}
catch (IOException e) {
Log.e(TAG, "not able to clear files.");
}
processController = null;
mainActivity.finish();
}
}
Here is the body of the JSONParser. (UPDATE: I set the method none static but the problem persists.) I omit the object creations from the JSON objects since I don't think that this is the error:
public class JsonStringParser {
private static String TAG = JsonStringParser.class.getSimpleName();
public static synchronized List<Zone> parse(String jsonString) throws ParseException, InterruptedIOException {
JSONParser jsonParser = new JSONParser();
Log.i(TAG, "start parsing JSON String with length " + ((jsonString != null) ? jsonString.length() : "null"));
List<Zone> zones = new ArrayList<Zone>();
//does a lot of JSON parsing here
Log.i(TAG, "finished parsing JSON String");
jsonParser = null;
return zones;
}
}
Here is the heap dump which shows the problem:
This is the details list which shows that this problem has something to do with the arraylist.
Any ideas what's wrong here? Btw: I don't know what's the other leak since there is no details information.
Maybe important: This diagram show the status when I don't start and stop the application over and over again. It's a diagram of a clean start. But when I start and stop several times it could lead to problems due to lack of space.
Here is a diagram of a real crash. I started and stopped the app while initialization several times:
[UPDATE]
I narrowed it down a bit by not storing the Android context into my ApplicationContext class and making PersistenceManager non-static. The problem hasn't changed, so I'm absolutely sure that it is not related to the fact that I store the Android context globally. It's still "Problem Suspect 1" of the graph above. So I have to do something with this huge list, but what? I already tried to serialize it, but unseralizing this list takes much longer than 20secs, so this is not an option.
Now I tried something different. I kicked out the whole ApplicationContext so I don't have any static references anymore. I tried to hold the ArrayList of Zone objects in MainActivity. Although I refactored at least the parts I need to make the application run, so I didn't even pass the Array or the Activity to all classes where I need it, I still have the same problem in a different manner, so my guess is that the Zone objects itself are somehow the problem. Or I cannot read the heap dump properly. See the new graphs below. This is the result of a simple app start without interference.
[UPDATE]
I came to the conclusion that there is no memory leak, because "the memory is accumulated in one instance" doesn't sound like a leak. The problem is that starting and stopping over and over again starts new AsyncTasks, as seen on one graph, so the solution would be to not start new AsyncTask. I found a possible solution on SO but it doesn't work for me yet.
First of all, I have to agree with Emile:
The "..tedious passing references by constructor" is what helps avoid
issues like this. Honestly, using statics in this way is certainly one
way to create memory leaks like this, especially with a static
reference to your context.
This also applies to all those other static methods in your code. static methods are not really different from global functions. You are building a big spaghetti plate full of static methods there. Especially when they start sharing some state it will sooner or later crash or create obscure results which you wouldn't get with a proper design, especially in the presence of a highly multi-threadable platform as Android.
What also catched my eye is, please note that the onCancelled method of the AsyncTask will not be called before doInBackground has finished. So your global cancelation flag (ProcessNotification.isCancelled()) is more or less worthless (if only used in the shown code passages).
Also from the memory images you posted, the zones list contains "only" 31 items. How much is it supposed to hold? By how much does it increase? If it actually increases, the culprint might be in the JsonStringParser.parse method, which is again static. If it holds a list of items in some cache and the control logic is not working correctly (for example in the presence of multiple threads accessing it at the same time), it might add items to that cache each time it is called.
Guess 1: As the parsing method is static, this data is not (necessarily) cleaned when the application is shut down. statics are initialized once and for the purpose of this case never de-initialized until the (physical vm-)process is stopped. Android does not guarantee that the process is killed however, even if the application is stopped (see for example a wonderful explanation here). Therefore you might accumulate some data in some static part of your (maybe parsing) code.
Guess 2: Since you are re-starting your application several times, you have the background thread running several times in parallel (assumption: each time you restart the application a new thread is spawned. Note that your code shows no guards against this.) This the first parsing is still running, another one is started since the global zones variables still holds no values. The global function parse might not be thread-safe and put several data multiple times into the list which is eventually returned, yielding a bigger and bigger list. Again this is generally avoided by not having static methods (and be aware of multi-threading).
(The code is not complete, therefore guesses, there might even be other things lurking there.)
Inside your AsyncTask, you own a reference on a Context : MainActivity. When you start several AsyncTask, they are gonna be queued by an ExecutorService. So all the AsyncTask, if they are long running, will be "alive" (not garbage collected). And each of them will keep a reference on an Activity. Consequently, all you activities are gonna be kept alive as well.
This is a real memory leak as Android will want to garbage collect an Activity that is not displayed any more. And your AsyncTasks will prevent that. All the activities are kept in memory.
I encourage you to try RoboSpice Motivations to learn more about this problem. In this app we explain why you should not use AsyncTasks for long running operations. There are still a few work around that enable you to use them, but they are difficult to implement.
One way to get rid of this problem is to use WeakReference to point to your Activities inside your AsyncTask class. If you use them carefully, you can then avoid your activities not to be garbage collected.
Actually, RoboSpice is a library that allows to execute Network requests inside a service. This approach is quite interesting has it will create a context (a service) that is not linked to your activities. Thus, your request can take as long as they want and don't interfere with the garbage collection behavior of Android.
There are two modules of RoboSpice that you can use to deal with REST request. One for Spring Android and the other one for Google Http Java Client. Both libs will ease JSON parsing.
I assume you fixed the reference to MainActivity, but I'd like to mention another problem ...
You state that the parsing takes 20sec. And if you "interrupt" the app, this processing does not go away.
From the code you show here it seems 99% of that 20sec is spent inside JsonStringParser.parse().
If I look at your comment "does a lot of JSON parsing here", I assume your app makes a call into JSONParser.something() that stays away for 20sec. Even though JsonStringParser is static, each call to JsonStringParser.parse() creates a new copy of JSONParser() and my guess is that uses a lot of memory.
A background process that takes 20sec is a really big task, and in what I have seen with JSON parsers, in this time a lot of objects get created and destroyed and a lot of cycles get consumed.
So I think your root cause here is that you start a second (or third or fourth) copy of JSONParser.something(), because each of them will execute independently and try to allocate many chunks of memory, and stay running even longer than 20sec because they will have to share the CPU cycles. The combined memory allocation of multiple JSONParser objects is what kills your system.
To summarize:
Do not start another JsonStringParser.parse() until the first one
is killed or completed.
This means you must find a way to stop JsonStringParser.parse()
when you "interrupt" the app, or reuse the running copy when you
restart the app.
THink i see how it might be possible, my eyes have gone crossed eyed looking though.
Check that your not loading the data from your local storage, adding more data to it and then saving it back to local disk.
Something around the following methods in combination with other parts of your program.
If the following was called, and then you call getDatafromURL for some reason, then i believe you'd continually grow your data set.
That would be my starting point at least. Loading, appending and saving.
ApplicationContext.setData(getLocalTimestamp(), getLocalJsonData());
private List<Zone> getLocalJsonData() throws IOException, ParseException {
if (ProcessNotification.isCancelled()) {
throw new InterruptedIOException();
}
return JsonStringParser.parse(PersistenceManager.getFileData(FileType.JSONDATA));
}
Otherwise i think the problem lies in either your Parsing code, or perhaps one of the static classes your using to save the data.
MY FINAL SOLUTION
I found a solution on my own now. It runs stable and doesn't produce memory leaks when I start and stop the application a lot of times. Another advantage with this solution is that I was able to kick out all this ProcessNotification.isCancelled() parts.
The key is to hold a reference to my InitializationTask in my ApplicationContext. With this approach I can resume the running AsyncTask in a new MainActivity when I start a new one. This means that I never start more than one AsyncTask but I attach every new MainActivity instance to the currently running task. The old Activity will be detached. This looks like this:
new methods in ApplicationContext:
public static void register(InitializationTask initializationTask) {
ApplicationContext.initializationTask = initializationTask;
}
public static void unregisterInitializationTask() {
initializationTask = null;
}
public static InitializationTask getInitializationTask() {
return initializationTask;
}
MainActivity
(I have to put the progressDialog in here, otherwise it wouldn't be shown if I stop and start a new Activity):
#Override
protected void onStart() {
super.onStart();
progressDialog = new ProgressDialog(this);
progressDialog.setMessage("Processing.\nPlease wait...");
progressDialog.setIndeterminate(true); // means that the "loading amount" is not measured.
progressDialog.setCancelable(true);
progressDialog.show();
if (ApplicationContext.getInitializationTask() == null) {
initializationTask = new InitializationTask();
initializationTask.attach(this);
ApplicationContext.register(initializationTask);
initializationTask.execute((Void[]) null);
}
else {
initializationTask = ApplicationContext.getInitializationTask();
initializationTask.attach(this);
}
}
MainActivity's "onPause" contains initializationTask.detach(); and progressDialog.dismiss();. finalizeSetup(); dismisses the dialog too.
InitializationTask contains two more methods:
public void attach(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
public void detach() {
mainActivity = null;
}
onPostExecute of the task invokes ApplicationContext.unregisterInitializationTask();.

Android Rotation with IntentService

I have an application that uses IntentService to run a background task where I pull data from a website, parse the data out, and create calendar events based on the results. Everything seems to be working create, except I'm running into an issue with rotation.
Using the code below, when I rotate the screen, the ProgressDialog box stays visible, but is never updated with new text when the process is updated, and never goes away once the call is completed. I'm using an IntentService instead of an ASyncTask because the user can also schedule the IntentService to run at other times without having to interface with the app. Any help is appreciated, thanks!
public void onCreate(Bundle savedInstanceState) {
Object retained = getLastNonConfigurationInstance();
if (retained instanceof CalendarHandler) {
// CH is a class level variable defined at the top which references my IntentService, aptly named CalendarHandler
ch = (CalendarHandler) retained;
ch.setActivity(this);
} else {
ch = null;
}
activity = this;
btnLogin.setOnClickListener(OnClickListener(View view) {
ch = new CalendarHandler();
ch.setActivity(MyTlc.this);
// Do other stuff, like run the intent service
}
}
public Handler handler = new Handler() {
public void handleMessage(Message message) {
// We read the information from the message and do something with it
// based on what the result code is
String result = message.getData().getString("status");
if (result.equals("ERROR")) {
activity.removeDialog(PROGRESS_DIALOG);
results.setText(message.getData().getString("error"));
} else if (result.equals("DONE")) {
activity.removeDialog(PROGRESS_DIALOG);
int count = message.getData().getInt("count", 0);
activity.results.setText("Added " + count + " shifts to the calendar");
} else {
activity.pDialog.setMessage(result);
}
super.handleMessage(message);
}
};
From what I understand, this should work, and like I said the ProgressDialog box does stay properly, I just can't seem to pass information to the dialog box after rotating.

Android AsyncTask strangely publishing duplicates

I think this is not an easy question.
I'll be brief and give a little example of what is happening.
Let's say we have a source of data in file Byron.txt:
SHE walks in beauty, like the night
Of cloudless climes and starry skies;
And all that 's best of dark and bright
Meet in her aspect and her eyes:
Thus mellow'd to that tender light
Which heaven to gaudy day denies.
And this code execute inside an AsyncTask:
final ArrayList<Record> poem = new ArrayList<Record>();
final Object objectLock = new Object();
private Record rec = new Record();
#Override
protected Void doInBackground(Void... args) {
String line = null;
int i;
int last;
try {
process = Runtime.getRuntime().exec("cat Byron.txt");
bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()), 8192);
synchronized (objectLock) {
poem.clear();
last = i = poem.size() - 1;
}
while(line = bufferedReader.readLine()) != null) {
rec.setString(line);
synchronized (objectLock) {
last++;
poem.add(last, rec);
}
while(!bPause && i < last) {
i++;
publishProgress(poem.get(i));
}
}
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected synchronized void onProgressUpdate(Record... m) {
if(m.length > 0) {
mContext.mTable.appendRow(m[0]);
}
}
where there is a TableLayout in the UI and each time we get a new line we add a new TableRow to it.
And this is the output we see in the UI:
SHE walks in beauty, like the night
Of cloudless climes and starry skies;
Of cloudless climes and starry skies;
Of cloudless climes and starry skies;
Thus mellow'd to that tender light
Thus mellow'd to that tender light
And we go into the debugger and we see why it happens.
Sometimes the synchronized (objectLock) is skipped and the loop continues.
There is no publishing because i already catched last.
Later the block is executed as many times as it was skipped,
, but the original line is lost and the current line is added instead to poem several times
Then, all the new lines are published until i catches last again.
So you see that I followed the code and I can explain what's happening, the question here is: Why the block is skipped?, Why?
I expected the synchronized block to stall until it can be executed.
At least this how I understood the function of synchronized (objectLock)
even without using wait() and notify()
I don't pretend to open a discussion here (although if you want we can open one in the chat area)
If you see some fault in the code, then, answer the question to let me know.
NOTES:
synchronized is needed because somewhere else in the app, the user may want to email the lines he got so far.
The user may pause the publishing (bpause); that's the while loop and i follows last only when bPause is false.
I decided to publish the answer. Even though I feel very embarrassed by its simplicity.
I discovered it only after I had already dug deep into AsyncTask class and message handling and whatnot.
I publish it in hope it will help people to check the basic things before jumping to
conclusions, and that someone out there will save himself half a day debugging because of
this post.
The Record rec was the same one each time. The poem ArrayList had the same element id for each entry. And the content changed on all of them at once, since they were all the same.
When the progress was published immediately it printed the right string, the last one. But if some delay cause the progress to publish later, then retrieving the poem.get(i) records retrieved a different entry but with the same pointer, thus, the same content.
The solution was to create a new Record each loop.
Do the synchronization for last object.
synchronized (last) {
last++;
poem.add(last, line);
}

Android AndEngine - How can I update ChangeableText value

I have a little problem with ChangeableText in AndEngine. I want to know how to update it's text without freezing the screen? For now I'm using this way, but it's freezing my phone for maybe 2-3 seconds :
private void removeFace(final Sprite face) {
hm = getIconNames();
if(face.getUserData().equals("petrol")){
elapsedText.setText(hm.get(25));
final PhysicsConnector facePhysicsConnector = this.mPhysicsWorld.getPhysicsConnectorManager().findPhysicsConnectorByShape(face);
this.mPhysicsWorld.unregisterPhysicsConnector(facePhysicsConnector);
this.mPhysicsWorld.destroyBody(facePhysicsConnector.getBody());
this.mScene.unregisterTouchArea(face);
this.mScene.detachChild(face);
} else {
}
System.gc();
}
Any ideas how to do that?
Remember that when you detachChild you should do this in thread because if you don't it can causes errors. Use this construction
runOnUpdateThread(new Runnable(){
#Override
public void run() {
if(yourSprite.hasParent())
scene.detachChild(yourSprite);
}});
You can put there all code if you want then your phone shouldn't freez
private void removeFace(final Sprite face) {
runOnUpdateThread(new Runnable(){
#Override
public void run() {
hm = getIconNames();
if(face.getUserData().equals("petrol")){
elapsedText.setText(hm.get(25));
final PhysicsConnector facePhysicsConnector = this.mPhysicsWorld.getPhysicsConnectorManager().findPhysicsConnectorByShape(face);
this.mPhysicsWorld.unregisterPhysicsConnector(facePhysicsConnector);
this.mPhysicsWorld.destroyBody(facePhysicsConnector.getBody());
this.mScene.unregisterTouchArea(face);
this.mScene.detachChild(face);
} else {
}
System.gc();
}});
}
Thats probably because you are fetching some information while setting the text.
What you should do is, get your
String hm = hm.get(25); //What ever the correct object is or variable. im guessing its a string or int.
Then
pass it to the Changeable text to be set.
elapsedText.setText(hm); //If its a int make sure you do String.valueOf(hm);
The only 3 methods here that have the possibility to take long are getIconNames() and get(), and System.gc()
The others are usually methods that return immediately, or have a very low complexity. For example, getPhysicsConnectorManager() returns immediately. findPhysicsConnectorByShape, unregisterPhysicsConnector, unregisterTouchArea and detachChild all have complexity of O(n), (And most of the others methods also have complexity of O(1) or O(n)).
I recommend you to look in the LogCat and when System.gc() is called, you will see a Log.i (blue) message of the tag of dalvikvm which will begin with GC_EXPLICIT and will give you some information about how long did the garbage collection took, etc....
If that GC call isn't taking the time, it must be your 2 methods, getIconNames() and hm.get(). You can put a Log.d message after each code line, which will write the last code line executed. This way you can follow the times.

Categories

Resources