Needle

Multithreading library for Android.

View the Project on GitHub ZsoltSafrany/needle

Introduction

Needle makes multithreading simple on Android.

Are you struggling with Thread, Handler, Looper, AsyncTask, ThreadPoolExecutor, FutureTask, View.post() or Activity.runOnUiThread()?

Needle.onMainThread().execute(new Runnable() {
    @Override
    public void run() {
        // e.g. change one of the views
    }
});

You might be better off using Needle.

Needle supports Android 1.5 (API Level 3) and above; and it behaves the same on all platform versions.

Features

Do work on the UI/main thread

Needle.onMainThread().execute(new Runnable() {
    @Override
    public void run() {
        // e.g. change one of the views
    }
});

Do work on a background thread

Needle.onBackgroundThread().execute(new Runnable() {
    @Override
    public void run() {
        // something cpu-intensive and/or not UI-related
    }
});

The executor returned by onBackgroundThread() has a thread pool with 3 (Needle.DEFAULT_POOL_SIZE) threads. If all threads in the pool are busy executing code then Runnable instances are enqueued and executed as soon as possible.

This way you can make sure you never bog down the CPU with a lot of concurrent threads; number of concurrent threads is bounded.

Do work on a background thread with custom thread pool size

Needle.onBackgroundThread().withThreadPoolSize(6).execute(new Runnable() {
    @Override
    public void run() {
        // something that blocks often thus can have a larger thread pool,
        // for instance, downloading a file (with blocking IO)
    }
});

Subsequent executors obtained with

Needle.onBackgroundThread()
      .withThreadPoolSize(poolSize)

with the same poolSize parameter will use the same thread pool.

Needle.onBackgroundThread() is equivalent to

Needle.onBackgroundThread()
      .withThreadPoolSize(Needle.DEFAULT_POOL_SIZE)

Do work on a background thread serially

Needle.onBackgroundThread().serially().execute(new Runnable() {
    @Override
    public void run() {
        // something that we want to do serially but not on the main thread
    }
});

Needle.onBackgroundThread().serially() is equivalent to

Needle.onBackgroundThread()
      .withThreadPoolSize(1)

Set different levels of concurrency for different task types

Needle
    .onBackgroundThread()
    .withTaskType("file-downloading")
    .execute(new Runnable() {
        @Override
        public void run() {
            // downloading a file in a thread pool 
            // different from thread pool of "image-processing"
        }
});

Needle
    .onBackgroundThread()
    .withTaskType("image-processing")
    .execute(new Runnable() {
        @Override
        public void run() {
            // processing an image in a thread pool 
            // different from thread pool of "file-downloading"
        }
});
Needle
    .onBackgroundThread()
    .withTaskType("cpu-inensive-but-has-to-be-serial")
    .withThreadPoolSize(1)
    .execute(new Runnable() {
        @Override
        public void run() {
            // something that we want to do serially but not on the main thread
        }
});

Needle
    .onBackgroundThread()
    .withTaskType("something-else-that-has-to-be-serial")
    .serially()
    .execute(new Runnable() {
        @Override
        public void run() {
            // something else that we want to do serially 
            // and also not on the main thread
            // and should not interfere with 
            // "cpu-inensive-but-has-to-be-serial" tasks
        }
});

Tasks with different types will be executed independently from each other, i.e. each task type will have its own, separated thread pool.

To be more precise, in case of these two calls

Needle.onBackgroundThread()
      .withThreadPoolSize(poolSizeA)
      .withTaskType(taskTypeA)
      .execute(runnableA)

and

Needle.onBackgroundThread()
      .withThreadPoolSize(poolSizeB)
      .withTaskType(taskTypeB)
      .execute(runnableB)

tasks runnableA and runnableB will be executed in the same thread pool if, and only if, poolSizeA equals poolSizeB and taskTypeA equals taskTypeB.

Needle.onBackgroundThread().withTaskType(taskType) is equivalent to

Needle.onBackgroundThread()
      .withTaskType(taskType)
      .withThreadPoolSize(Needle.DEFAULT_POOL_SIZE)

Needle.onBackgroundThread().withThreadPoolSize(poolSize) is equivalent to

Needle.onBackgroundThread()
      .withTaskType(Needle.DEFAULT_TASK_TYPE)
      .withThreadPoolSize(poolSize)

Calculate something on a background thread and then use the result on the UI/main thread

Needle.onBackgroundThread().execute(new UiRelatedTask<Integer>() {
    @Override
    protected Integer doWork() {
        int result = 1+2;
        return result;
    }

    @Override
    protected void thenDoUiRelatedWork(Integer result) {
        mSomeTextView.setText("result: " + result);
    }
});

Callback thenDoUiRelatedWork(Result result) is executed on the UI/main thread.

Calculate something and publish progress updates on a background thread and then use the result on the UI/main thread

Needle.onBackgroundThread().execute(new UiRelatedProgressTask<String, Integer>() {
    @Override
    protected String doWork() {
        int result = 0;
        for (int i = 0; i < 10; i++) {
            result += 1;
            publishProgress(result);
        }
        return "The result is: " + result;
    }

    @Override
    protected void thenDoUiRelatedWork(String result) {
        mSomeTextView.setText(result);
    }

    @Override
    protected void onProgressUpdate(Integer progress) {
        mSomeTextView.setText("progress: " + progress);
    }
});             

Both thenDoUiRelatedWork(Result result) and onProgressUpdate(Progress progress) are executed on the UI/main thread.

Cancel tasks

CancelableTask task = new CancelableTask() {
    @Override
    protected void doWork() {
        // will be never executed
    }
};
task.cancel();
Needle.onBackgroundThread().execute(task);

Passing a task to .execute() that is not canceled doesn't necessarily mean that it will be executed. If the task gets canceled while it is waiting it the thread pool queue (because all threads are busy to process it) then it won't be executed either (ever).

CancelableTask task = new CancelableTask() {
    @Override
    protected void doWork() {
        int lotOfIterations = ...;
        for (int i=0; i < lotOfIterations; i++) {
            if (isCanceled()) {
                break;
            } else {
                // work on current iteration
            }
        }
    }
};

Needle.onBackgroundThread().execute(task);
// a little time goes by
task.cancel();

In case your task takes a very long time to execute you may want to check inside doWork() whether the task has been canceled - to stop execution.

Classes CancelableTask, UiRelatedTask and UiRelatedProgressTask are all cancelable.

Download

↓ Latest JAR

The source code of Needle is available on GitHub.

Maven

<dependency>
  <groupId>com.zsoltsafrany</groupId>
  <artifactId>needle</artifactId>
  <version>1.0.0</version>
</dependency>

Gradle

compile 'com.zsoltsafrany:needle:1.0.0'

License

The MIT License (MIT)

Copyright (c) 2014 Zsolt Safrany

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.