Qt Android Service problems

634 views Asked by At

I have several problems with Qt Android services.

  1. The service isnt starting immediately. It runs after 1-2 minutes delay.
  2. The service stops after some time and the app crashes.
  3. QTimer in the service doesn't work.
  4. QTcpSocket client doesn't work. ( I added android.permission.INTERNET and still not works )

Im using the same main.cpp file for the Activity and Service. and this is the main function of the service::

QTcpSocket _socket;

void onReadyRead()
{
    QByteArray datas = _socket.readAll();
    QString DataAsString = QString(datas);

    Log::log("DATA: ");
    Log::log(DataAsString);

    if(DataAsString!="HELLO\n")
        NotificationClient().setNotification(DataAsString + " BGN");
}

int main_service(int argc, char *argv[])
{
    QAndroidService app(argc, argv);

    _socket.connectToHost(QHostAddress("xx.xx.xx.xx"), 1234);
    QObject::connect(&_socket, &QTcpSocket::readyRead, onReadyRead);

    _socket.write(QByteArray("get_money()\r\n"));

    QThread::msleep(1000);

    Log::log("creating timer..");
    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, someFunction);
    timer.start(500);

    //MoneyWorker *mw = new MoneyWorker();

    for(;;)
    {
        _socket.write(QByteArray("get_money()\r\n"));

        Log::log("loopin..");
        QThread::msleep(10000);
    }

    //NotificationClient().setNotification("The user is happy!");

    return app.exec();
}

Im starting the service this way::

QJniObject::callStaticMethod<void>(
        "org/qtproject/example/qtandroidservice/QtAndroidService",
        "startQtAndroidService",
        "(Landroid/content/Context;)V",
         QNativeInterface::QAndroidApplication::context());
package org.qtproject.example.qtandroidservice;

import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.qtproject.qt.android.bindings.QtService;

public class QtAndroidService extends QtService
{
    private static final String TAG = "QtAndroidService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "Creating Service");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "Destroying Service");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int ret = super.onStartCommand(intent, flags, startId);

        // Do some work

        return ret;
    }

    public static void startQtAndroidService(Context context) {
            context.startService(new Intent(context, QtAndroidService.class));
    }

    public static void stopQtAndroidService(Context context) {
            context.stopService(new Intent(context, QtAndroidService.class));
    }

    public static void log(String message)
    {
            Log.i(TAG, message);
    }
}
<service android:name="org.qtproject.example.qtandroidservice.QtAndroidService">

The goal is to show my debit card balance on my smart watch via notification so I can know when my money go low.

This is the log from 'adb logcat'

08-07 22:45:15.182  5342  5366 W ActivityManager: Timeout executing service: ServiceRecord{d2392a2 u0 org.qtproject.example.androidnotifier/org.qtproject.example.qtandroidservice.QtAndroidService}
08-07 22:45:15.531  5342 27607 E ActivityManager: Reason: executing service org.qtproject.example.androidnotifier/org.qtproject.example.qtandroidservice.QtAndroidService
08-07 22:47:35.784  5342  5366 W ActivityManager: Timeout executing service: ServiceRecord{33bcd86 u0 org.qtproject.example.androidnotifier/org.qtproject.example.qtandroidservice.QtAndroidService}
08-07 22:47:37.037  5342 29382 E ActivityManager: Reason: executing service org.qtproject.example.androidnotifier/org.qtproject.example.qtandroidservice.QtAndroidService
08-07 22:47:37.121  5342  7675 W ActivityManager: Scheduling restart of crashed service org.qtproject.example.androidnotifier/org.qtproject.example.qtandroidservice.QtAndroidService in 62676ms for start-requested
08-07 22:48:39.837  5342  5367 I ActivityManager: Start proc 29406:org.qtproject.example.androidnotifier/u0a477 for service {org.qtproject.example.androidnotifier/org.qtproject.example.qtandroidservice.QtAndroidService}
08-07 22:48:39.982  5342  7678 W ActivityManager: Stopping service due to app idle: u0a477 -1m34s200ms org.qtproject.example.androidnotifier/org.qtproject.example.qtandroidservice.QtAndroidService
08-07 22:48:40.198 29406 29428 I QtAndroidService: creating timer..
08-07 22:48:40.198 29406 29428 I QtAndroidService: loopin..
08-07 22:48:41.199 29406 29428 I QtAndroidService: loopin..
08-07 22:48:42.200 29406 29428 I QtAndroidService: loopin..
08-07 22:48:43.201 29406 29428 I QtAndroidService: loopin..
08-07 22:48:44.201 29406 29428 I QtAndroidService: loopin..
08-07 22:48:45.202 29406 29428 I QtAndroidService: loopin..
08-07 22:48:46.203 29406 29428 I QtAndroidService: loopin..
08-07 22:48:47.204 29406 29428 I QtAndroidService: loopin..
1

There are 1 answers

0
acho On

1.The service isnt starting immediately. It runs after 1-2 minutes delay.

  • see (2.)
  1. The service stops after some time and the app crashes.
  • These problems was because I went this way -> "Service in the Same Process as QtActivity"
  1. QTimer in the service doesn't work.
  • I haven't tried this when I separated the service from the activity.
  1. QTcpSocket client doesn't work.
  • It seems I don't know how to use QTcpSocket properly because I tried it in simple x64 console application and can't do what I want.

I decided to go the Java way and do the task in java and I managed to succeed, now Im watching my money on my smart watch hehe :)

It uses node.js API with tcp server and puppeteer (headless chrome) that logs into my e-banking and gets my balance, listens to port and gives it on request.

    /*

 INCLUDES

    */

const config = require('config');
const puppeteer = require('puppeteer');
const Net = require('net');
const util = require("util");
const cron = require('node-cron');

    /*

 VARIABLES

    */

var kesh = "";
const hl = config.get('puppy.headless');
const server = new Net.Server();
const port = config.get('server.port');

get_kesh();

    /*

   CRON

    */

cron.schedule('0 10 * * *', () =>
{
  get_kesh();
});

cron.schedule('0 13 * * *', () =>
{
  get_kesh();
});

cron.schedule('0 16 * * *', () =>
{
  get_kesh();
});

cron.schedule('0 19 * * *', () =>
{
  get_kesh();
});

    /*

  SERVER

    */

server.listen(port, function()
{
    console.log(`listen 0.0.0.0:${port}`);
});

    /*

 PUPETEER

    */

async function get_kesh()
{
  console.log("updating kesh.. ");

  const browser = await puppeteer.launch({headless: hl});
  const page = await browser.newPage();
  await page.setViewport({width: 1200, height: 720});
  await page.goto('https://xxxxx.com', { waitUntil: 'networkidle0' }); // wait until page load
  await page.type('#username', config.get('bank.user'));
  await page.type('#password', config.get('bank.pass'));
  // click and wait for navigation
  await Promise.all([
    page.click('button.btn.btn-primary.ng-scope'),
    page.waitForNavigation({ waitUntil: 'networkidle0' }),
  ]);

  const data = await page.evaluate(() => document.querySelector('div.acc-balance.row.ng-isolate-scope').querySelector('div#step0').querySelector('h4.blue-txt.ng-binding').textContent);
  kesh = data.split(".")[0];

  console.log(kesh);
  console.log("\n");
  
  browser.close();
}

    /*

  SERVER

    */

server.on('connection', function(socket)
{
    console.log('A new connection has been established.');

    var the_interval = 3 * 1000;
    
    var hb = setInterval(function()
    {
        console.log("sending heartbeat to client..");
        socket.write("HB\r\n");
    }, the_interval);

    socket.write("HELLO\r\n");

    socket.on('data', function(chunk)
    {
      if(chunk.toString() == "get_kesh()\r\n")
      {
        console.log("sending kesh => ");
        console.log(kesh); 
        console.log("BGN\n");

        socket.write(kesh);
        socket.write("\r\n");
      }
    });

    socket.on('end', function()
    {
    clearInterval(hb);
        console.log('Closing connection with the client');
    });

    socket.on('error', function(err)
    {
        console.log(`Error: ${err}`);
    });
});


and this is my java service:

package org.qtproject.example.qtandroidservice;

import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.qtproject.qt.android.bindings.QtService;

import android.os.IBinder;
import android.widget.Toast;

import org.qtproject.example.tcpclient.Kesh;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.NotificationChannel;

//import android.app.Service;

public class QtAndroidService extends QtService
{
    private Kesh k;

    private static final String TAG = "QtAndroidService";

    @Override
    public void onCreate()
    {
        //super.onCreate();

        Log.i(TAG, "Creating Service");

        if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O)
        {
            String CHANNEL_ID = "my_channel_id";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "my_channel_title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new Notification.Builder(this, channel.getId())
                    .setContentTitle("service")
                    .setContentText("")
                    .build();

            startForeground(1, notification);
        }
    }

    @Override
    public void onDestroy()
    {
        //super.onDestroy();

        Log.i(TAG, "Destroying Service");

        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId)
    {
        //int ret = super.onStartCommand(intent, flags, startId);

        Log.i(TAG, "Starting Service");

        k = new Kesh(this);

        if(k.isConnected())
            Log.i(TAG, "connected");
        else
            Log.i(TAG, "not connected");

        Toast.makeText(this, "service started", Toast.LENGTH_SHORT).show();
        //Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();

            new Thread()
            {
                public void run()
                {
                    task();
                }
            }.start();

        // If we get killed, after returning from here, restart
        return START_STICKY;
        //return ret;
    }

    @Override
    public IBinder onBind(Intent intent)
    {
        // We don't provide binding, so return null
        return null;
    }

    public static void startQtAndroidService(Context context)
    {
        if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O)
        {
            context.startForegroundService(new Intent(context, QtAndroidService.class));
        }
        else
        {
            context.startService(new Intent(context, QtAndroidService.class));
        }
    }

    public static void stopQtAndroidService(Context context)
    {
            context.stopService(new Intent(context, QtAndroidService.class));
    }

    public static void log(String message)
    {
            Log.i(TAG, message);
    }

    private void task()
    {
        try
        {
            for(;;)
            {
                while(!k.hello);

                Log.i(TAG, "polling..");

                k.get_kesh();
                Thread.sleep(10000);

                //Thread.sleep(1800000);
                //Thread.sleep(1800000);
            }
        }
        catch (InterruptedException e)
        {
            // Restore interrupt status.
            Thread.currentThread().interrupt();
        }
    }
}

this is my Kesh Class

package org.qtproject.example.tcpclient;

import android.os.AsyncTask;
import android.util.Log;

import android.content.Context;
import org.qtproject.example.androidnotifier.NotificationClient;

//import java.io.IOException;

import java.util.Timer;
import java.util.TimerTask;
import android.os.CountDownTimer;

public class Kesh
{
    private TcpClient mTcpClient;
    private static final String TAG = "QtAndroidService";
    public String kesh;
    public boolean hello = false;
    private Context context;

    private boolean tryToReconnect = true;
    //private final Thread heartbeatThread;
    private long heartbeatDelayMillis = 5000;
    private boolean error;

    private static final long TIMER_INTERVAL = 10000L;
    private Timer mTimer;

    private CountDownTimer waitTimer;


    /**
     * Constructor of the class.
     */
    public Kesh(Context context)
    {
        this.context = context;

        // connect
        new ConnectTask().execute("");

        waitTimer = new CountDownTimer(10000, 1000)
        {

          public void onTick(long millisUntilFinished)
          {
             Log.i(TAG, "Timer val: " +Long.toString(millisUntilFinished));
             //called every 300 milliseconds, which could be used to
             //send messages or some other action
          }

          public void onFinish()
          {
              Log.i(TAG, "Connection lost, reconnecting..");
              Log.i(TAG, "Connection lost, reconnecting..");
              Log.i(TAG, "Connection lost, reconnecting..");
              Log.i(TAG, "Connection lost, reconnecting..");
              Log.i(TAG, "Connection lost, reconnecting..");
              reconnect();
              waitTimer.start();
          }
        }.start();
    }

    public void restart_my_timer()
    {
        if(waitTimer != null)
        {
            waitTimer.cancel();
            waitTimer.start();
        }
    }

    public void notify_kesh()
    {
        NotificationClient.notify(this.context, this.kesh);
    }

    public void get_kesh()
    {
        //sends the message to the server
        if (mTcpClient != null)
        {
            mTcpClient.sendMessage("get_kesh()\r\n");
        }
    }

    protected void reconnect()
    {
        // disconnect
        mTcpClient.stopClient();
        //mTcpClient = null;
        mTcpClient.run();
        hello=false;
        new ConnectTask().execute("");
    }

    public boolean isConnected()
    {
        if (mTcpClient != null)
        {return true;
        }
        else
        {return false;
        }}

    public class ConnectTask extends AsyncTask<String, String, TcpClient>
    {

        @Override
        protected TcpClient doInBackground(String... message)
        {

            //we create a TCPClient object and
            mTcpClient = new TcpClient(new TcpClient.OnMessageReceived()
            {
                @Override
                //here the messageReceived method is implemented
                public void messageReceived(String message)
                {
                    Log.i(TAG, message);

                    if(message.equals("HB"))
                    {
                        //mTimer.cancel(); // Terminates this timer, discarding any currently scheduled tasks.
                        //mTimer.purge(); // Removes all cancelled tasks from this timer's task queue.
                        //start_my_timer();

                        restart_my_timer();
                    }
                    else if(!message.equals("HELLO"))
                    {
                        Kesh.this.kesh = message;
                        Kesh.this.notify_kesh();
                    }
                    else
                    {
                        Kesh.this.hello = true;
                    }

                }
            });

            mTcpClient.run();

            return null;
        }
    }
}

and my TcpClient class

package org.qtproject.example.tcpclient;

import android.util.Log;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

/**
 * Description
 *
 * @author Catalin Prata
 *         Date: 2/12/13
 */
public class TcpClient
{
    public static final String SERVER_IP = "192.168.100.11"; //your computer IP address
    public static final int SERVER_PORT = 9009;
    
    // message to send to the server
    private String mServerMessage;
    // sends message received notifications
    private OnMessageReceived mMessageListener = null;
    // while this is true, the server will continue running
    private boolean mRun = false;
    // used to send messages
    private PrintWriter mBufferOut;
    // used to read messages from the server
    private BufferedReader mBufferIn;

    public Socket socket;

    /**
     * Constructor of the class. OnMessagedReceived listens for the messages received from server
     */
    public TcpClient(OnMessageReceived listener)
    {
        mMessageListener = listener;
    }

    /**
     * Sends the message entered by client to the server
     *
     * @param message text entered by client
     */
     public void sendMessage(String message)
     {
         if (mBufferOut != null && !mBufferOut.checkError())
         {
             mBufferOut.print(message);
             mBufferOut.flush();
         }
     }
//    public boolean sendMessage(String message)
//    {
//        if(mBufferOut.checkError())
//        {
//            return true;
//        }

//        if (mBufferOut != null)
//        {
//            mBufferOut.print(message);
//            mBufferOut.flush();
//        }

//        return false;
//    }

    /**
     * Close the connection and release the members
     */
    public void stopClient()
    {
        mRun = false;
    }

    public void run()
    {

        mRun = true;

        try {
            //here you must put your computer's IP address.
            InetAddress serverAddr = InetAddress.getByName(SERVER_IP);

            Log.e("QtAndroidService - TCP Client", "C: Connecting.../creating socket");

            //create a socket to make the connection with the server
            socket = new Socket(serverAddr, SERVER_PORT);

            try {

                //sends the message to the server
                mBufferOut = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);

                //receives the message which the server sends back
                mBufferIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                //in this while the client listens for the messages sent by the server
                while (mRun)
                {
                    mServerMessage = mBufferIn.readLine();

                    if (mServerMessage != null && mMessageListener != null)
                    {
                        //call the method messageReceived from MyActivity class
                        mMessageListener.messageReceived(mServerMessage);
                    }

                }

                Log.e("RESPONSE FROM SERVER", "S: Received Message: '" + mServerMessage + "'");

            } catch (Exception e) {

                Log.e("TCP", "S: Error", e);

            } finally {
                //the socket must be closed. It is not possible to reconnect to this socket
                // after it is closed, which means a new socket instance has to be created.
                socket.close();
            }

            socket = null;

            if (mBufferOut != null)
            {
    //            mBufferOut.flush();
                mBufferOut.close();
                mBufferOut = null;
            }

            mBufferIn = null;
            mMessageListener = null;
            mServerMessage = null;

            //socket = null;

        } catch (Exception e) {

            Log.e("TCP", "C: Error", e);
        }

    }

    //Declare the interface. The method messageReceived(String message) will must be implemented in the MyActivity
    //class at on asynckTask doInBackground
    public interface OnMessageReceived {
        public void messageReceived(String message);
    }
}