Description
This tutorial describes how to schedule tasks in a Qt app for Android using Android's JobScheduler API. If your Android app needs to perform a repetitive task whether the app is active or not, you should consider creating an Android app with a JobService to perform the work. You can add one or more job services, where each one defines a different task that is performed in the future based upon specified conditions. The tasks can even survive application restarts and device reboots.
Source code
Getting Started
Create a new Qt Quick empty project naming it JobServiceExample. The app will link against the Qt Android Extras module, so add this line to the project file:
android:QT += androidextras
This sample app displays a message box, so you need to add entry to support the QMessageBox class.
QT += widgets
Create Android Package Templates files. This can be easily done with QtCreator. If you are creating the Templates manually and you need assistance, refer to the Qt for Android documentation.
Verify the app was generated correctly by building all Android ABI's and deploying your new app onto an Android device or emulator. Upon successfully running your new Android app, it's time to make the changes needed to create a job scheduling service.
Update AndroidManifest.xml
In the AndroidManifest.xml, name the package following the normal java package naming conventions. The JobServiceExample package name is com.example.jobserviceexample. The AndroidManifest.xml file should contain an entry similar to this.
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionName="100" android:versionCode="100" android:installLocation="auto">
There are several other changes to make to the AndroidManifest.xml and now is as good a time as any. The job scheduling api requires a minimum android version. Specify the supported Android sdk, with an entry such as this.
In the AndroidManifest.xml file , the JobServiceExample must be declared as a service with the BIND_JOB_SERVICE permission. The name JobServiceExample is the name of the Java class you will be creating.
android:permission="android.permission.BIND_JOB_SERVICE"/>
Because Android will run the job, per the job's defined schedule, after the system has finished booting, the RECEIVE_BOOT_COMPLETED permission is needed.
The sample app scheduled task is to append a line of text composed of a time stamp to a file periodically. Thus, the permission WRITE_EXTERNAL_STORAGE is required.
Please refer to the sample app, if you are unsure as to what the AndroidManifest.xml entries are.
JobScheduler Java API
An Android service is a component that runs in background and has no user interface. The service will continue to run even if the application exits. The work or task you are scheduling is known as a 'job' and is defined in a Java class which enables Android to perform the work, even when the app is not active. Refer to Android documentation for details.
Java files belong to the package specified in the AndroidManifest.xml file. Java files called by Qt application must be placed in a directory which conforms to the path hierarchy defined by the package name. Create a java class called JobServiceExample.class which extends android.app.job.JobService in the directory ...JobServiceExample/android/src/com/example/jobserviceexample. This Java class is an Android Service that extends the Android JobService class. Since JobServiceExample class extends the JobService class, a couple of methods must be implemented: onStartJob(), which is called by the system when the job has begun executing, and onStopJob(), which is called by the system if the job is cancelled before finishing. Note, JobServiceExample class runs on the main thread so the task should be run on a worker thread.
@Override
public boolean onStartJob( JobParameters jobParameters )
{
Log.i( TAG, "JobServiceExample.onStartJob : jobParameters.getJobId() = " +
jobParameters.getJobId() );
try {
Thread thread = new Thread( new ExampleWork( this ) );
thread.start();
thread.join();
} catch ( Exception e ) {
e.printStackTrace();
}
return false; // Returns false from when job has finished. onStopJob will not be invoked
}
@Override
public boolean onStopJob( JobParameters jobParameters )
{
// This method is typically not invoked
Log.i( TAG, "JobServiceExample.onStopJob : jobParameters.getJobId() = " +
jobParameters.getJobId() );
return false; // Returns false to end the job entirely
}
The class ExampleWork specified above implements Runnable. For this example, the task is to append a line of text composed of a timestamp to a file.
@Override
public void run()
{
...
doWork( mContext );
...
}
public static void doWork( Context context )
{
try {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String textToAppend = dateFormat.format(new Date());
File path = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS );
String filename = path.getAbsolutePath() + File.separatorChar +
"JobServiceExampleLog.txt";
Log.i( TAG, "ExampleWork.doWork path =" + filename + " appending text " + textToAppend);
BufferedWriter writer = new BufferedWriter(new FileWriter(filename, true));
writer.newLine();
writer.write(textToAppend);
writer.close();
} catch ( IOException e ) {
e.printStackTrace();
}
}
Schedule a job by using Android's JobScheduler java class. Using this class, Android can efficiently batch your job with other jobs that need network access. JobScheduler can request retries and when needed Android will rescheduled the work; the work is guaranteed. In the sample project JobServiceExample, the class ExampleJobScheduler illustrates scheduling. A unit of work is encapsulated by a java JobInfo class specifying the scheduling criteria. The JobInfo.Builder class is used to configure how the scheduled task should run.
public static void scheduleJob (Context context, int intervalinMS )
{
handleJob(context, intervalinMS );
}
private static void handleJob (Context context, long intervalinMS)
{
...
ComponentName serviceComponent =
new ComponentName( context, JobServiceExample.class );
...
JobScheduler jobScheduler = context.getSystemService( JobScheduler.class );
...
JobInfo.Builder builder = new JobInfo.Builder( JOB_ID, serviceComponent );
...
builder.setPeriodic( intervalinMS ); // job runs within the intervalinMS; API 21
builder.setPersisted( true ); // persist this job across device reboots; API 21
builder.setRequiredNetworkType( JobInfo.NETWORK_TYPE_ANY ); // API 21
builder.setRequiresDeviceIdle(false); // API 21
int result = jobScheduler.schedule( builder.build() );
String resultText = ( JobScheduler.RESULT_SUCCESS == result ) ?
"successfully" : "failed";
Log.i ( TAG, "ExampleJobScheduler.handleJob scheduled for intervalinMS of " +
intervalinMS + " is " + resultText );
...
}
QML
In this example app, the QML UI, main.qml, allows the user to schedule how frequently the task is executed.
...
Button {
text: qsTr("Apply")
anchors.horizontalCenter: parent.horizontalCenter
onClicked: Controller.scheduleJobService(scheduleModelId.
get(scheduleSelectorId.currentIndex).intervalMS)
}
...
Qt & C++
The FrontController C++ class exposes the job scheduling function to the QML interface.
Q_INVOKABLE void scheduleJobService(int intervalinMS);
...
void FrontController::scheduleJobService( int intervalinMS )
{
QAndroidJniObject::callStaticMethod
( "com/example/jobserviceexample/JobServiceExample","scheduleJobService",
"(Landroid/content/Context;I)V",
QtAndroid::androidActivity().object(), intervalinMS);
}
The Permissions C++ class is called when the application starts for check for and request of needed permissions.
void Permissions::requestExternalStoragePermission()
{
...
QtAndroid::PermissionResult request = QtAndroid::checkPermission(
"android.permission.WRITE_EXTERNAL_STORAGE" );
if ( request == QtAndroid::PermissionResult::Denied ) {
QtAndroid::requestPermissionsSync( QStringList() <<
"android.permission.WRITE_EXTERNAL_STORAGE" );
request = QtAndroid::checkPermission(
"android.permission.WRITE_EXTERNAL_STORAGE" );
if ( request == QtAndroid::PermissionResult::Denied ) {
mPermissionGranted = false;
if ( QtAndroid::shouldShowRequestPermissionRationale(
"android.permission.READ_EXTERNAL_STORAGE" ) ) {
QAndroidJniObject
( "com/example/jobserviceexample/ShowPermissionRationale",
"(Landroid/app/Activity;)V",
QtAndroid::androidActivity().object());
QAndroidJniEnvironment env;
if ( env->ExceptionCheck() ) {
env->ExceptionClear();
}
}
} else {
mPermissionGranted = true;
}
} else {
mPermissionGranted = true;
}
...
}
The communication between the C++ Qt/QML and Java needs to be specified in main.cpp.
#include
#include "frontcontroller.h"
#include "permissions.hpp"
int main(int argc, char *argv[])
{
...
QQmlApplicationEngine engine;
FrontController frontController{app.parent()};
engine.rootContext()->setContextProperty( "Controller", &frontController );
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection); engine.load(url);
Permissions permissions;
permissions.requestExternalStoragePermission();
if ( permissions.getPermissionResult() ) {
qInfo( "Successfully obtained required permissions, app starting" );
return app.exec();
} else {
qWarning( "Failed to obtain required permissions, app terminating" );
}
}
Test it Out
Upon successfully building the app, run the JobServiceExample app and schedule a job by selecting the "Recording interval" and pressing "Apply". Quit the app. On Android, the file /storage/emulated/0/Download/JobServiceExampleLog.txt will be updated at the specified time interval. Upon rebooting the Android, you can observe, the file /storage/emulated/0/Download/JobServiceExampleLog.txt will continue to be updated at the specified time interval. The sample app, JobServiceExample, logs often to help you follow along.
![]()
Conclusion
To code a job scheduler in your QT Android application, there are many small steps, but this is true with all Android app development. Android job scheduling is a powerful, performant, robust feature that enables the Android OS to shoulder the burden of executing tasks based upon specific conditions. It's a nice tool to have in your toolbox.