I'm trying to extract specific files and visualise them in a list/grid. For that purpose I'm starting a Service to do the background job, and when it finishes it fires an Intent which will be captured by a Fragment/Activity. The files are extracted in an singletone container which feeds the adapter when the broadcast is received.
I want to do this in the fastest way possible, so I'm using many threads.
The service:
import android.app.Service;
import android.content.Intent;
import android.os.Environment;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import java.io.File;
import java.io.FileFilter;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class ExtractFilesService extends Service {
public static String FILE_SCAN_COMPLETE = "ExtractFilesService#FILE_SCAN_COMPLETE";
private CopyOnWriteArrayList<File> extractedFilesList;
private ExecutorService threadPool;
private AtomicInteger threadCounter = new AtomicInteger(0);
private FileFilter mFilenameFilter = new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory()
|| pathname.getName().endsWith(".pdf")
|| pathname.getName().endsWith(".epub");
}
};
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
extractedFilesList = new CopyOnWriteArrayList<>();
// What is the optimal pool size ?
// It should differ on various devices, so
// how to adjust the value based on the device?
// Are there any other best practices for executing unknown number of threads simultaneously?
threadPool = Executors.newFixedThreadPool(10);
// getting the root directory and start to iterate recursively
extractFiles(Environment.getExternalStorageDirectory());
return super.onStartCommand(intent, flags, startId);
}
private void extractFiles(final File dir) {
// Counting the threads so we know when the last finishes
threadCounter.incrementAndGet();
threadPool.execute(new Runnable() {
@Override
public void run() {
File[] files = dir.listFiles(mFilenameFilter);
for (File file : files) {
if (file.isDirectory()) {
extractFiles(file); // recursive call
} else {
extractedFilesList.add(file); // add to collection
}
}
if (threadCounter.decrementAndGet() == 0) {
onScanEnd();
}
}
});
}
private void onScanEnd() {
ExtractedFilesManager.getInstance().initAll(extractedFilesList);
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(FILE_SCAN_COMPLETE));
stopSelf();
}
}
And here is the singletone container:
import java.io.File;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Created by tochkov.
*/
public class ExtractedFilesManager {
private ArrayList<File> fileList;
private ExtractedFilesManager() {
fileList = new ArrayList<>();
}
private static ExtractedFilesManager instance;
public static ExtractedFilesManager getInstance() {
if (instance == null) {
instance = new ExtractedFilesManager();
}
return instance;
}
public void initAll(CopyOnWriteArrayList<File> newFiles) {
fileList.clear();
fileList.addAll(newFiles);
}
public ArrayList<File> getFiles() {
return fileList;
}
public void sortByCriteria() {
//TODO
}
public void deleteFileFromDevice() {
//TODO
}
public void getInfoForFile() {
//TODO
}
}
- What can go wrong?
- Is there a static optimal threadPool size for Android? How to adjust it on different devices?
- Am I using the executor properly?
- What are the best practices for executing unknown number of threads simultaneously with some kind of a callback when they are all done?
- Is there a way to make it even faster?
EDIT:
I tested this on a low-end phone and found a small problem. My onScanEnd()
method is actually called several times. Everything is working fine, but apparently my threadCounter
logic is wrong. Am I using AtomicInteger
wrong? How can I implement something like a callback to know that all threads have finished?