Я делаю приложение на 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;
}
}
}
Создаем 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" />
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Буквально пару дней назад появилась проблема с доступом к картам на собственном сайтеЗапросов до 100 в день, а ответ отдает- 429 Too Many Requests
Сразу демка https://codepenio/lubus/pen/rNBXJRB