I have an android app with a non-UI thread that needs to be responsive, but which needs to occasionally perform a long running task. To do this, I make it spawn a thread to run the task. This thread takes a reference to an object. This object appears to leak. I run out of memory amazingly fast.
I anticipate lots of people telling me to use AsyncTask. I find AsyncTask irritating and harder to use than a plain ol' thread, and I am not in the UI thread here. I will listen to advice, but I'd appreciate it if the advice explained why!
I have written a very simple program to demonstrate the problem, which runs a thread that, every second, updates a textview with the value of an integer. Having done so, it spawns a thread which increments the integer by one. The thread also, for no good reason, takes a reference to an object called "BigFatBlob". It leaks like a sieve. I don't understand why. After the program code I have attached a screen shot of part of the MAT analysis of the heap showing the leak and the incoming references.
The function to look at below is "increment()". If anyone can tell me why my program is leaking I'd be very grateful - I'm a bit lost here.
public class MainActivity extends Activity {
TextView text_;
int value_;
boolean ready_;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text_ = (TextView)findViewById(R.id.textView);
value_ = 0;
ready_ = false;
watcher();
increment(new BigFatBlob());
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
/** Updates the text view object to show the current value */
public void updateText() {
Runnable r = new Runnable() {
@Override
public void run() {
text_.setText("" + value_);
}
};
runOnUiThread(r);
}
/** runs forever, checking for new values every second */
private void watcher() {
Runnable r = new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
if (pollReady()) {
updateText();
increment(new BigFatBlob());
}
}
}
};
Thread t = new Thread(r);
t.start();
}
/** runs once - increments the value by 1 and flags "ready" */
public void increment(final BigFatBlob junk) {
Runnable r = new Runnable() {
private BigFatBlob j;
@Override
public void run() {
setJ(junk);
++value_;
setReady(true);
}
public BigFatBlob getJ() {
return j;
}
public void setJ(BigFatBlob j) {
this.j = j;
}
};
Thread t = new Thread(r);
t.start();
}
/** ready is synchronized */
private synchronized void setReady(boolean state) {
ready_ = state;
}
/** get the ready value and, if it was true, make it false again */
private synchronized boolean pollReady() {
if (ready_) {
ready_ = false;
return true;
}
return false;
}
}
public class BigFatBlob {
byte[] blob_;
public BigFatBlob() {
blob_ = new byte[1000];
}
}