Sunday, December 4, 2011

Android AsyncTask great idea BAD implementation!

I got so tired of Memory leaks in my android application which was caused mainly by the AsyncTask object of the Android framework, so I've implemented my own as part of my "Cyborg" project, this works for me...

I can use the same instance of the task over and over and over, and to publish to the UI on a UI thread...

Shame on you Android and shame on you Google... :(

No warranty... use at your own risk!!!

I've updated this on the 16-03-2012, previous version had a design flaw.



package com.nu.art.software.android.core;

import java.lang.ref.WeakReference;

import android.os.Handler;
import android.os.Message;

import com.nu.art.software.android.log.AndroidLogImpl;
import com.nu.art.software.android.log.Logger;

public abstract class AsyncTaskModel<Model, Progress, Result>
  extends Handler
  implements Logger {


 @Override
 @SuppressWarnings("unchecked")
 public void handleMessage(Message msg) {
  switch (msg.what) {
   case ProgressUpdate :
    onProgressUpdate((Progressmsg.obj);
    break;
   case ExecutionCompleted :
    onExecuteCompleted((Resultmsg.obj);
    break;
   case ExecutionCancelled :
    onExecutionCancelled((Resultmsg.obj);
    break;
   case Dispose :
    model = null;
    threadReference = null;
    break;
  }
  super.handleMessage(msg);
 }

 private static final int ProgressUpdate = 1;

 private static final int ExecutionCompleted = 2;

 private static final int ExecutionCancelled = 3;

 protected static final int Dispose = 4;

 protected Model model;

 private final String name;

 private WeakReference<Thread> threadReference;

 private volatile boolean cancelled;

 protected AsyncTaskModel(String name) {
  super();
  this.name = name;
 }

 public final boolean isRunning() {
  return threadReference != null;
 }

 public final void execute(Model model) {
  if (isRunning())
   throw new TaskInProcessException("Task is already running");
  this.model = model;
  cancelled = false;
  onPreExecute();
  Runnable r = new Runnable() {

   @Override
   public void run() {
    Result result = doInBackgroundImpl();
    Message message;
    if (cancelled)
     message = obtainMessage(ExecutionCancelled);
    else
     message = obtainMessage(ExecutionCompleted);
    message.obj = result;
    message.sendToTarget();
    message = obtainMessage(Dispose);
    message.sendToTarget();
   }
  };
  Thread thread = new Thread(r, name);
  thread.start();
  threadReference = new WeakReference<Thread>(thread);
 }

 protected abstract Result doInBackgroundImpl();

 protected abstract void onProgressUpdate(Progress progress);

 public final void cancel() {
  cancelled = true;
  cancelImpl();
  if (threadReference != null)
   threadReference.get().interrupt();
 }

 @SuppressWarnings("unused")
 protected void onExecutionCancelled(Result result) {}

 protected void cancelImpl() {}

 @SuppressWarnings("unused")
 protected void onExecuteCompleted(Result result) {}

 protected void onPreExecute() {}

 public void publishProgress(Progress progress) {
  if (cancelled)
   return;
  Message message = obtainMessage(ProgressUpdate);
  message.obj = progress;
  message.sendToTarget();
 }

 public boolean wasCancelled() {
  return cancelled;
 }

 @Override
 public void logDebug(String debug) {
  AndroidLogImpl.LogImpl.logDebug(debug);
 }

 @Override
 public void logError(String error) {
  AndroidLogImpl.LogImpl.logError(error);
 }

 @Override
 public void logError(String error, Throwable e) {
  AndroidLogImpl.LogImpl.logError(error, e);
 }

 @Override
 public void logError(Throwable e) {
  AndroidLogImpl.LogImpl.logError(e);
 }

 @Override
 public void logInfo(String info) {
  AndroidLogImpl.LogImpl.logInfo(info);
 }

 @Override
 public void logVerbose(String verbose) {
  AndroidLogImpl.LogImpl.logVerbose(verbose);
 }


 @Override
 public void logWarning(String warning) {
  AndroidLogImpl.LogImpl.logWarning(warning);
 }

 @Override
 public void logWarning(String warning, Throwable e) {
  AndroidLogImpl.LogImpl.logWarning(warning, e);
 }

}
Java2html

3 comments:

  1. Kenny Singer,

    FYI, the thing the original AsyncTask is missing, is an under management of the:

    public void disconnect ()
    {
    if (mActivity != null) {
    mActivity = null;
    Log.d ("Demo", "RandomColorsTask has successfully disconnected from the activity.");
    }
    } // end disconnect

    Mentioned in 'RandomColorTask.java' file...

    Together with a field of the UI component associated with the Task.

    ReplyDelete
  2. Hey!! Sorry to revive this post but... why the model is never used? you put in the execute method as a parameter and then you only dispose it.

    ReplyDelete
    Replies
    1. doInBackgroundImpl() implementation in the inheriting Class would have access to the model and once the call to that method ends, it would nullify the model reference, thus preventing the Memory leak.

      Delete