Как отключить фоновый процесс?

281
25 августа 2021, 12:10

Я делаю приложение на Cordova. Стояла задача: создать плагин, который бы создавал фоновый процесс, который в свою очередь каждые 30 секунд отправлял на сервер информация (идентификатор устройства), тем самым говоря о том, что приложение работает на устройстве. Вот код плагина

public class MyService extends Service {
Handler mHandler = new Handler();
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    mHandler.postDelayed(ToastRunnable(intent), 20000);
    return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
    return null;
}
private Runnable ToastRunnable(Intent intent) {
    return new Runnable() {
        public void run() {
            Context context = getApplicationContext();
            NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            if (intent != null) {
                Bundle arguments = intent.getExtras();
                String uid = arguments.get("uid").toString();
                StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
                StrictMode.setThreadPolicy(policy);
                try {
                    URL url = new URL("http://site.com/site/app-use?token=" + uid);
                    try {
                        HttpURLConnection con = (HttpURLConnection) url.openConnection();
                        con.setRequestMethod("GET");
                        BufferedReader in = new BufferedReader(new InputStreamReader(
                                con.getInputStream()));
                        String inputLine;
                        while ((inputLine = in.readLine()) != null) {
                            System.out.println(inputLine);
                        }
                        in.close();
                    }  catch (IOException e) {
                        System.out.println("Can't read a"); // Or something more intellegent
                    }
                } catch (MalformedURLException ex){
                }
                mHandler.postDelayed(ToastRunnable(intent), 20000);
            } else {
                mHandler.postDelayed(ToastRunnable(intent), 20000);
            }
        }
    };
};

}

На 7 и 8 Android если приложение закрыть - то плагин не отправляет данные (так и надо). Но на 9 Android плагин дальше шлет.

Вопрос: как можно убить этот процесс при закрытии или может дописать какое то условие?

P.S. Код, который запускается как плагин и который запускает сервис

package com.example.plugin;
import org.apache.cordova.*;
import org.json.JSONArray;
import org.json.JSONException;
import android.widget.Toast;
import android.content.Context;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
public class MyPlugin extends CordovaPlugin {
@Override
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
    if (action.equals("runBackground")) {
        Context context = cordova.getActivity().getApplicationContext();
        Intent service = new Intent(context, MyService.class);
        if (data.getString(0) != null) {
            service.putExtra("uid", data.getString(0));
        } else {
            service.putExtra("uid", "Відсутній ідентифікатор");
        }
        context.startService(service);
        return true;
    } else {
        return false;
    }
}
}
Answer 1

Создаем Foreground Service. Код ниже взят из нескольких менеджеров и засунут в один класс, для понимания и в принципе далек от идеала, но он рабочий.

public class MyService extends Service {
private static final String DEFAULT_CHANNEL_ID = "package.channel.id";
private static final String DEFAULT_GROUP_ID = "package.group.id";
private static final String DEFAULT_GROUP_NAME = "Info";
private int messageId = 1;
private Handler mHandler = new Handler();
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Notification notification = getNotificationForService();
    startForeground(messageId, notification);
    mHandler.postDelayed(ToastRunnable(intent), 20000);
    return START_REDELIVER_INTENT;
}
@Override
public void onDestroy() {
    stopService();
    super.onDestroy();
}
private Notification getNotificationForService() {
    messageId++;
    Intent resultIntent = new Intent(getApplicationContext(), MockNavigationActivity.class); // Тут указываю название активити которое должно открыться при клике на уведомление в шторке - как правило это стартовый класс
    resultIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    PendingIntent resultPendingIntent = PendingIntent.getActivity(
            this,
            0,
            resultIntent,
            PendingIntent.FLAG_UPDATE_CURRENT);
    Bitmap largeIcon = BitmapFactory.decodeResource(getApplicationContext().getResources(), R.mipmap.ic_launcher_foreground); // Больша иконка приложения
    NotificationCompat.Builder builder =
            new NotificationCompat.Builder(this, DEFAULT_CHANNEL_ID)
                    //Icons
                    .setSmallIcon(R.drawable.ic_about) // Маленькая, черно-белая иконка, можно сгинерить в студии
                    .setLargeIcon(largeIcon)
                    //Content
                    .setContentTitle("MY TITILE") // Заголовок, как правило название приложения
                    .setContentText("sfdjhsjhfjkhsdjkfhkjsd") // Текст который будет в пуше
                    //Settings
                    .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) // Звук - если надо!
                    .setContentIntent(resultPendingIntent);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        String channelId = getChannelId();
        if (!TextUtils.isEmpty(channelId)) {
            builder.setChannelId(channelId);
        }
    }
    return builder.build();
}
@TargetApi(Build.VERSION_CODES.O)
private String getChannelId() {
    NotificationManager nm = (NotificationManager) getApplicationContext().getSystemService(NOTIFICATION_SERVICE);
    if (nm != null) {
        List<NotificationChannel> channels = nm.getNotificationChannels();
        for (NotificationChannel channel : channels) {
            if (channel.getId().equalsIgnoreCase(DEFAULT_CHANNEL_ID)) {
                return channel.getId();
            }
        }
        String group = getNotificationChannelGroup();
        NotificationChannel notificationChannel = new NotificationChannel(DEFAULT_CHANNEL_ID, "My chanel", NotificationManager.IMPORTANCE_HIGH);
        notificationChannel.setGroup(group);
        nm.createNotificationChannel(notificationChannel);
        return DEFAULT_CHANNEL_ID;
    }
    return null;
}
@TargetApi(Build.VERSION_CODES.O)
private String getNotificationChannelGroup() {
    NotificationManager nm = (NotificationManager) getApplicationContext().getSystemService(NOTIFICATION_SERVICE);
    if (nm != null) {
        List<NotificationChannelGroup> channelGroups = nm.getNotificationChannelGroups();
        for (NotificationChannelGroup group : channelGroups) {
            if (group.getId().equalsIgnoreCase(DEFAULT_GROUP_ID)) {
                return group.getId();
            }
        }
        nm.createNotificationChannelGroup(new NotificationChannelGroup(DEFAULT_GROUP_ID, DEFAULT_GROUP_NAME));
        return DEFAULT_GROUP_ID;
    }
    return null;
}
private void stopService() {
    stopSelf();
    stopForeground(true);
}
private Runnable ToastRunnable(Intent intent) {
    return () -> {
        if (intent == null || intent.getExtras() == null || intent.getExtras().get("uid") == null) return;
        Bundle arguments = intent.getExtras();
        String uid = arguments.get("uid").toString();
        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);
        try {
            URL url = new URL("http://site.com/site/app-use?token=" + uid);
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            con.setRequestMethod("GET");
            BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                System.out.println(inputLine);
            }
            in.close();
        } catch (MalformedURLException ex) {
        } catch (IOException e) {
            System.out.println("Can't read a"); // Or something more intellegent
        }
        mHandler.postDelayed(ToastRunnable(intent), 20000);
    };
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
    return new Binder();
}
}

Дальше остановка и запуск сервиса, все зависит от того как Вам надо, если сервис должен умереть при сворачивании приложения (когда прилож в фоне но не мертв), а при разворачивании снова стартануть тогда в главной активити пишите так

override fun onResume() {
    super.onResume()
    Intent intent = new Intent(this, MyService::class.java);
    intent.putExtra("uid", "VALUE"); // Тут передаете ИД устройства
    startService(intent);
}
override fun onStop() {
    super.onStop()
    stopService(new Intent(this, MyService::class.java));
}

А если надо что запускался 1 раз и умирал когда основная активность умирает делайте тоже самое что в коде выше но в методах onCreate() and onDestroy()

В манифест надо добавить пермишены

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

и указать что у вас есть сервис

<service
        android:name="ПУТЬ.К.СЕРВИСУ.MyService"
        android:enabled="true"
        android:exported="false" />
READ ALSO
Метод equals в классе Object и в моем

Метод equals в классе Object и в моем

Вот метод equals() в классе Object:

114
Яндекс Карты API 429 Too Many Requests

Яндекс Карты API 429 Too Many Requests

Буквально пару дней назад появилась проблема с доступом к картам на собственном сайтеЗапросов до 100 в день, а ответ отдает- 429 Too Many Requests

158