2fours
2fours

Reputation: 31

Android WorkManager kicking off too many jobs simultaneously

I'm implementing parallel background upload/download functionality in my app using workmanager. Everything works great except WorkManager runs too many jobs simultaneously and the phone becomes unresponsive until things spool down. I'd like to enqueue say 10 tasks and have WM execute say 2 in parallel until all 10 are done. WorkManager seems to execute 5-10 in parallel. I can not figure out how to do this. I've tried my own Configuration with just a single threaded executor and it doesn't make any difference. How can I constrain the number of jobs executed at once? I'm using 1.0.0-rc01.

Upvotes: 3

Views: 2556

Answers (3)

i test this example code and work for me without any bug, 10 parallel execution node(.setExecutor(Executors.newFixedThreadPool(10))) for download file without any lag and freeze UI thread: i use implementation 'android.arch.work:work-runtime:1.0.0-beta02'. on your AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

<application        
    android:name=".App"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:usesCleartextTraffic="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <provider
        android:name="androidx.work.impl.WorkManagerInitializer"
        android:authorities="de.stocard.stocard.workmanager-init"
        android:enabled="false"
        tools:replace="android:authorities"/>

</application>

in App.java:

Configuration configuration = new Configuration.Builder()
            .setExecutor(Executors.newFixedThreadPool(10))
            .build();
WorkManager.initialize(getApplicationContext(), configuration);

in MainActivity.java:

OneTimeWorkRequest MyWorkA = new OneTimeWorkRequest.Builder(MyWorkA.class)
        .build();
OneTimeWorkRequest MyWorkB = new OneTimeWorkRequest.Builder(MyWorkB.class)
        .build();
OneTimeWorkRequest MyWorkC = new OneTimeWorkRequest.Builder(MyWorkC.class)
        .build();
OneTimeWorkRequest MyWorkD = new OneTimeWorkRequest.Builder(MyWorkD.class)
        .build();
OneTimeWorkRequest MyWorkE = new OneTimeWorkRequest.Builder(MyWorkE.class)
        .build();
OneTimeWorkRequest MyWorkF = new OneTimeWorkRequest.Builder(MyWorkF.class)
        .build();
OneTimeWorkRequest MyWorkG = new OneTimeWorkRequest.Builder(MyWorkG.class)
        .build();
OneTimeWorkRequest MyWorkH = new OneTimeWorkRequest.Builder(MyWorkH.class)
        .build();
OneTimeWorkRequest MyWorkI = new OneTimeWorkRequest.Builder(MyWorkI.class)
        .build();
OneTimeWorkRequest MyWorkJ = new OneTimeWorkRequest.Builder(MyWorkJ.class)
        .build();
OneTimeWorkRequest MyWorkK = new OneTimeWorkRequest.Builder(MyWork.class)
        .build();
OneTimeWorkRequest MyWorkL = new OneTimeWorkRequest.Builder(MyWorkL.class)
        .build();
List<OneTimeWorkRequest> allWorker = new ArrayList<>();
allWorker.add(MyWorkA);
allWorker.add(MyWorkB);
allWorker.add(MyWorkC);
allWorker.add(MyWorkD);
allWorker.add(MyWorkE);
allWorker.add(MyWorkF);
allWorker.add(MyWorkG);
allWorker.add(MyWorkH);
allWorker.add(MyWorkI);
allWorker.add(MyWorkJ);
WorkManager.getInstance()
        .enqueue(allWorker);

and for MyWorkA.java to MyWorkJ.java use this code(dummy download file):

public class MyWorkA extends Worker {

    private static final String TAB = MyWorkA.class.getSimpleName();

    public MyWorkA(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {

        Log.e(TAB,"My WorkA");

        InputStream input = null;
        OutputStream output = null;
        HttpURLConnection connection = null;
        try {
            URL url = new URL("http://ipv4.download.thinkbroadband.com/20MB.zip");
            connection = (HttpURLConnection) url.openConnection();
            connection.connect();

            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
            }

            int fileLength = connection.getContentLength();

            input = connection.getInputStream();
            output = new FileOutputStream("/sdcard/file_nameA.zip");

            byte data[] = new byte[4096];
            long total = 0;
            int count;
            while ((count = input.read(data)) != -1) {

                total += count;
                if (fileLength > 0)
                output.write(data, 0, count);
            }
        } catch (Exception e) {
            Log.i("test", e.getMessage());
        } finally {
            try {
                if (output != null)
                    output.close();
                if (input != null)
                    input.close();
            } catch (IOException ignored) {
            }

            if (connection != null)
                connection.disconnect();
        }

        return Result.success();
    }
}

Upvotes: 1

shanx
shanx

Reputation: 425

If you are using ListenableWorker then custom Configuration with setExecutor will not help. Reason being this executor is only meant for running the Worker and not ListenableWorker. So in this particular case, you can have your own fixed thread pool, which you can use while starting the background process. One of the way to achieve this with fixed thread pool in ExecutorService -

public class MyExecutorService {
    private static ExecutorService executorService = null;

    public static ExecutorService getExecutorService() {
        if (executorService == null) {
            executorService = Executors.newFixedThreadPool(2);
        }
        return executorService;
    }
}


public class MyListenableWorker extends ListenableWorker {

    public MyListenableWorker(@NonNull Context appContext, @NonNull WorkerParameters workerParams) {
        super(appContext, workerParams);
    }

    @NonNull
    @Override
    public ListenableFuture<Result> startWork() {
        MyExecutorService.getExecutorService().execute(new Runnable() {
            @Override
            public void run() {
                doBackgroundWork();
            }
        });

        return future; // see note below
    }
}

Note: If you are using your custom ListenableWorker, you need to maintain status of ListenableFuture like mentioned here.

Upvotes: 0

Rahul
Rahul

Reputation: 21134

You can use a custom Configuration to define the number of Workers that you want to execute simultaneously. Something like this in your Application.onCreate(). For more information read about how to initialize WorkManager with a custom configuration.

val config = Configuration.Builder()
  .setExecutor(yourExecutor)
  .build()

WorkManager.initialize(context, config)

Upvotes: 2

Related Questions