0

Here's my code (simplified a little bit):

Service

public class TaskService extends Service {
// -----------------------------------------------------------------------
//
// Constants
//
// -----------------------------------------------------------------------
private static final String EXTRA_TASK = "EXTRA_TASK";

private static final String TASK_REGISTER_INSTALLATION = "TASK_REGISTER_INSTALLATION";

private static Handler sHandler = new Handler();

// -----------------------------------------------------------------------
//
// Statics
//
// -----------------------------------------------------------------------
public static void registerInstallation(Context context) {
    Intent intent = new Intent(context, TaskService.class);
    intent.putExtra(EXTRA_TASK, TASK_REGISTER_INSTALLATION);
    context.startService(intent);
}

// -----------------------------------------------------------------------
//
// Fields
//
// -----------------------------------------------------------------------
private List<BaseTask> mTasks = new ArrayList<BaseTask>();

// -----------------------------------------------------------------------
//
// Methods
//
// -----------------------------------------------------------------------
@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (intent != null)
        handleIntent(intent);
    return Service.START_NOT_STICKY;
}

private void handleIntent(Intent intent) {
    String taskType = intent.getStringExtra(EXTRA_TASK);
    if (taskType.equalsIgnoreCase(TASK_REGISTER_INSTALLATION)) {
        RegistrationTask task = new RegistrationTask("",
                "", "", "", "");
        task.setTaskListener(sHandler, mRegistrationListener);
        mTasks.add(task);
        task.start();
    }
}

@Override
public void onDestroy() {
    for (BaseTask task : mTasks)
        task.interrupt();
    mTasks.clear();
    Log.d(TaskService.class.getSimpleName(), "onDestroy");
    super.onDestroy();
}

private TaskListener<String, String, String> mRegistrationListener = new TaskListener<String, String, String>() {
    @Override
    public void onResult(String result, BaseTask task) {
        mTasks.remove(task);
    }

    @Override
    public void onProgress(String progress, BaseTask task) {
        mTasks.remove(task);
    }

    @Override
    public void onError(String error, BaseTask task) {
        Toast.makeText(TaskService.this, error, Toast.LENGTH_SHORT).show();
    }
};

public static interface TaskListener<ResultType, ProgressType, ErrorType> {
    public void onError(ErrorType error, BaseTask task);

    public void onProgress(ProgressType progress, BaseTask task);

    public void onResult(ResultType result, BaseTask task);
}
 }

Thread

public class BaseTask<ResultType, ProgressType, ErrorType> extends Thread {
protected Handler mHandler;
protected TaskListener<ResultType, ProgressType, ErrorType> mTaskListener;
protected HttpRequestFactory mRequestFactory;

public BaseTask() {
    try {
        ApacheHttpTransport.Builder builder = new ApacheHttpTransport.Builder();

        mRequestFactory = builder.doNotValidateCertificate().build()
                .createRequestFactory();
    } catch (GeneralSecurityException e) {
        e.printStackTrace();
    }
}

public void setTaskListener(Handler handler,
        TaskListener<ResultType, ProgressType, ErrorType> listener) {
    mHandler = handler;
    mTaskListener = listener;
}

protected void onError(final ErrorType error) {
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            mTaskListener.onError(error, BaseTask.this);
        }
    });
}

protected void onProgress(final ProgressType progress) {
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            mTaskListener.onProgress(progress, BaseTask.this);
        }
    });
}

protected void onResult(final ResultType result) {
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            mTaskListener.onResult(result, BaseTask.this);
        }
    });
}

  }

public class RegistrationTask extends BaseTask<String, String, String> {

public RegistrationTask(...) {
    super();
}

@Override
public void run() {
    try {
                //Simple web request executed here
    } catch (HttpResponseException e) {
        e.printStackTrace();
        onError(e.getContent());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

I also have an Activity which send Intent each time onCreate method gets called to start the task. The issue is that after several restarts I get about 300rb leaked and even if I call GC from Eclipse I didn't get freed. What am I doing wrong?

UPDATE Activity code:

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    TaskService.registerInstallation(this);
}

@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;
}

}

As you can see it is very simple.

Lingviston
  • 5,479
  • 5
  • 35
  • 67

3 Answers3

0

This answer won't give you a definitive solution, not because I'm not willing, but because it's impossible (and even harder without not just viewing your code, but knowing very well your code). But from my experience I can tell you that those kind of memory leaks doesn't occur just due to directly referenced objects - objects you declare (and keep referencing another classes/objects) in turn depends on many other classes and so on, and probably you're seeing a memory leak due to an incorrect handling of any of your instances which at the same time reference to other instances.

Debugging memory leaks is a often a very hard work, not just because as I said above it sometimes doesn't depend directly on what you've declared, but also because finding a solution might not be trivial. The best thing you can do is what you already seem to be doing: DDMS + HPROF. I don't know how much knowledge you have, but although it's not a universal method, this link helped me so much to find memory leaks in my code.

Although it seems trivial, the best way to debug those kind of things is progresively remove portions of your code (overall, those which implies working with instances of other classes) and see how the HPROF report change.

nKn
  • 13,691
  • 9
  • 45
  • 62
  • I understand all of these but I hope someone can tell me what is wrong there. Also that's almost all of my code (it's a small demo). The only thing is missing here is actual web request which is done with Google Http Client and activity code where I've written only one line of code to send intent to the Service. – Lingviston Feb 04 '14 at 12:22
  • Have you tried debugging with DDMS as I proposed? What object is causing you a memory leak? – nKn Feb 04 '14 at 12:39
  • yes, I didn't mentioned it in first post but if I remove the thread starting code then the heap grows but explicit calling of GC in DDMS frees the retained memory. – Lingviston Feb 04 '14 at 12:48
  • GC keeps running all your app's life cycle. Just the fact it does, doesn't mean you have a memory leak. If you're experiencing very frecuent GC calls, that is not a prove you have a memory leak but it's an indicator you might have one. If you use the histogram of the DDMS, you'll easily see the potential cause of a leak (supposing you have one). – nKn Feb 04 '14 at 12:52
  • I'm just confused that explicit call of GC from eclipse frees memory in one case and doesn't in another. I don't know if eclipse forces GC to clean memory or just advices to do that. – Lingviston Feb 04 '14 at 13:21
  • Eclipse just logs when the GC is passed, it has nothing to do with the function of GC. GC is part of Java, not Eclipse It determines when is needed to clean unused object, and then recollects the garbage. If you see that it's passing too often, it means that probably something is not ok, but if it passes from time to time, that doesn't mean there is some problem, it's just it's collecting garbage. – nKn Feb 04 '14 at 13:31
  • Ok, but generally is this a correct usage of these structures? – Lingviston Feb 04 '14 at 14:23
  • I don't see anything wrong with it, but as I say in my answer, it fairly depends on how did you implement the rest of your code. However, as I comment, I don't see any issues with your code. – nKn Feb 04 '14 at 14:27
0

Your service does not destroy with your activity. Most likely, you are retaining a reference to your Activity in each thread that spawns in the service, which means it does not get destroyed.

If you need to reference your Activity, you should use a WeakReference:

How to use WeakReference in Java and Android development?

If that is not the issue, then objects like your RegistrationTask where we can't see the code are the culprits - but it sounds like you may have already tried removing code bits... you can always post more code.

Community
  • 1
  • 1
Jim
  • 10,172
  • 1
  • 27
  • 36
  • no, activity reference is not used anywhere. And yes, I've tried removing some parts. – Lingviston Feb 04 '14 at 12:47
  • based on your update and looking at your Activity code, a reference to your Activity is being passed - at the line "TaskService.registerInstallation(this);" - "this" refers to the MainActivity; if a new instance of the Activity is created, it will also be passed in... - you should probably reference the getApplicationContext() or use "startService" – Jim Feb 04 '14 at 16:52
  • the weird thing is that you are calling "startService" from within the service. To be more specific, that call should be made from the activity - so you are keeping the context reference in the service by starting the service from the service possibly... – Jim Feb 04 '14 at 16:54
  • no, this is a static method. So no calling from the Service. – Lingviston Feb 04 '14 at 16:59
  • I didn't mean instance, I meant object. Static calls to methods can easily lead to memory leaks: http://geekexplains.blogspot.com/2009/11/memory-leak-in-java-does-static-cause.html – Jim Feb 04 '14 at 17:04
  • the case described in that post is no similar to mine. As you can see I don't set up any static vars in that method. I can easily move all it's code to the activity just replacing the context var. The only approach of moving this code to static mehtod is that I don't need to make Intent "EXTRA" constants public. – Lingviston Feb 04 '14 at 17:53
  • The only static variable used by this code is Handler object. This I must set to null but it's not used in the static method above. – Lingviston Feb 04 '14 at 17:55
0

Well once again I've just made a stupid mistake) I've made a web request which will always return an error (for tests) but in error handler of my service I forgot to remove Thead from the Threads list. Now everything works as I expected.

Lingviston
  • 5,479
  • 5
  • 35
  • 67