2017-05-08 8 views
1

Ich sah einige Fragen wie diese, aber keine von ihnen löste mein Problem. Ich starte einen Hintergrunddienst über einen AlarmManager. Jedesmal, der Dienst gestartet wird, überprüft er, ob es in SharedPreferences deaktiviert wurde, und wenn nicht, ist es eine neue Instanz von sich selbst neu planen und geht weiter, diese alternativen Wege folgende:Android - Annullierte Benachrichtigungen erscheinen wieder

  1. , wenn der Benutzer wünscht, GPS, es wartet auf die Position des Benutzers und verwendet , um einen REST-Endpunkt aufzurufen;
  2. Wenn der Benutzer kein GPS verwenden möchte, verwendet er die in den Einstellungen gespeicherte Position.

Das Ergebnis der HTTP-Aufruf (timeout: 30 Sekunden) ist ein JSONObject, die 0- n Benachrichtigungen erzeugt (je nachdem, wie viele "nahe Objekte" Es findet).

Mein Problem ist: Benachrichtigungen, auch wenn durch den Benutzer abgebrochen (auf sie gleiten oder öffnen), erscheint oft wieder, als ob sie nie gezeigt wurden. Es sollte nie passieren, weil der Web-Service eine Liste von ausgeschlossenen Objekt-IDs erhält, die jedes Mal aktualisiert werden.

Hier ist der Code:

ScannerService.java

package com.kiulomb.itascanner.service; 

import android.app.ActivityManager; 
import android.app.AlarmManager; 
import android.app.Notification; 
import android.app.NotificationManager; 
import android.app.PendingIntent; 
import android.app.Service; 
import android.content.ComponentName; 
import android.content.Context; 
import android.content.Intent; 
import android.content.SharedPreferences; 
import android.content.res.Resources; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.location.Address; 
import android.location.Geocoder; 
import android.location.Location; 
import android.location.LocationManager; 
import android.media.RingtoneManager; 
import android.net.ConnectivityManager; 
import android.net.NetworkInfo; 
import android.net.Uri; 
import android.os.Bundle; 
import android.os.IBinder; 
import android.support.v4.app.NotificationCompat; 
import android.support.v4.content.ContextCompat; 
import android.util.Log; 

import com.android.volley.RequestQueue; 
import com.android.volley.toolbox.Volley; 
import com.kiulomb.itascanner.R; 
import com.kiulomb.itascanner.network.HTTPRequestManager; 
import com.kiulomb.itascanner.network.HTTPResponseListener; 
import com.kiulomb.itascanner.network.URLs; 
import com.kiulomb.itascanner.pref.FilterPreferencesManager; 
import com.kiulomb.itascanner.pref.NotificationsHistoryManager; 
import com.kiulomb.itascanner.pref.PrefConstants; 
import com.kiulomb.itascanner.utils.Haversine; 
import com.kiulomb.itascanner.utils.MyConfiguration; 

import org.json.JSONArray; 
import org.json.JSONObject; 

import java.io.IOException; 
import java.io.InputStream; 
import java.net.HttpURLConnection; 
import java.net.URL; 
import java.text.DateFormat; 
import java.text.SimpleDateFormat; 
import java.util.Calendar; 
import java.util.List; 
import java.util.Locale; 
import java.util.Timer; 
import java.util.TimerTask; 

public class ScannerService extends Service { 
    private static final String TAG = ScannerService.class.getSimpleName(); 

    private boolean locationFound = false; 
    private boolean withoutLocation = false; 
    private LocationManager mLocationManager = null; 

    private final Timer myTimer = new Timer(); 
    private final long TIMEOUT = 20000; 
    TimerTask myTask = new TimerTask() { 
     public void run() { 
      try { 
       Log.i(TAG, "Timeout is over, trying to stop service (location found? " + locationFound + ")"); 
       if (!locationFound) { 
        stopSelf(); 
       } 
      } catch (Exception e) { 
       Log.e(TAG, "Could not stop service after time: " + e.getMessage()); 
      } 
     } 
    }; 

    private LocationListener[] mLocationListeners = new LocationListener[] { 
      new LocationListener(LocationManager.GPS_PROVIDER), 
      new LocationListener(LocationManager.NETWORK_PROVIDER) 
    }; 

    private boolean alreadySearching = false; 

    private class LocationListener implements android.location.LocationListener { 
     Location mLastLocation; 

     LocationListener(String provider) { 
      Log.i(TAG, "LocationListener is " + provider); 
      mLastLocation = new Location(provider); 
     } 

     @Override 
     public void onLocationChanged(final Location location) { 
      Log.i(TAG, "onLocationChanged: " + location); 

      if (withoutLocation) { 
       return; 
      } 

      ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 

      NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); 
      boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting(); 

      if (location != null) { 
       if (isConnected) { 
        mLastLocation.set(location); 
        locationFound = true; 

        Log.i(TAG, "already searching? " + alreadySearching); 
        if (!alreadySearching) { 
         findClosest(location.getLatitude(), location.getLongitude()); 
        } 
        alreadySearching = true; 
       } else { 
        Log.e(TAG, "no connectivity, ending service"); 
        stopSelf(); 
       } 
      } else { 
       Log.e(TAG, "no position, ending service"); 
       stopSelf(); 
      } 
     } 

     @Override 
     public void onProviderDisabled(String provider) { 
      Log.i(TAG, "onProviderDisabled: " + provider); 
     } 

     @Override 
     public void onProviderEnabled(String provider) { 
      Log.i(TAG, "onProviderEnabled: " + provider); 
     } 

     @Override 
     public void onStatusChanged(String provider, int status, Bundle extras) { 
      Log.i(TAG, "onStatusChanged: " + provider); 
     } 
    } 

    private void initializeLocationManager() { 
     Log.d(TAG, "initializeLocationManager"); 
     if (mLocationManager == null) { 
      mLocationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE); 
     } 
    } 

    @Override 
    public IBinder onBind(Intent arg0) { 
     return null; 
    } 

    @Override 
    public int onStartCommand(Intent intent, int flags, int startId) { 
     Log.d(TAG, "onStartCommand"); 
     // super.onStartCommand(intent, flags, startId); 
     return START_STICKY; 
    } 

    @Override 
    public void onCreate() { 
     Log.d(TAG, "onCreate"); 

     SharedPreferences pref = getSharedPreferences(PrefConstants.PREF_APP_FILE, MODE_PRIVATE); 
     if (pref.getBoolean(PrefConstants.PREF_APP_SERVICE_ENABLED, PrefConstants.PREF_APP_SERVICE_ENABLED_DEFAULT)) { 
      Intent intent = new Intent(ScannerService.this, ScannerService.class); 
      PendingIntent pintent = PendingIntent.getService(ScannerService.this, 0, intent, 0); 
      AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 
      Calendar cal = Calendar.getInstance(); 
      alarm.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis() + 60000, pintent); // or setExact() // TODO custom time 
      // alarm.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), 60000, pintent); 

      if (!pref.getBoolean(PrefConstants.PREF_APP_SERVICE_CUSTOMCENTER, PrefConstants.PREF_APP_SERVICE_CUSTOMCENTER_DEFAULT)) { 
       // use GPS 
       initializeLocationManager(); 
       try { 
        mLocationManager.requestLocationUpdates(
          LocationManager.NETWORK_PROVIDER, 
          MyConfiguration.LOCATION_INTERVAL, 
          MyConfiguration.LOCATION_DISTANCE, 
          mLocationListeners[1]); 
       } catch (SecurityException ex) { 
        Log.e(TAG, "fail to request location update, ignore", ex); 
       } catch (IllegalArgumentException ex) { 
        Log.e(TAG, "network provider does not exist, " + ex.getMessage()); 
       } 

       try { 
        mLocationManager.requestLocationUpdates(
          LocationManager.GPS_PROVIDER, 
          MyConfiguration.LOCATION_INTERVAL, 
          MyConfiguration.LOCATION_DISTANCE, 
          mLocationListeners[0]); 
       } catch (SecurityException ex) { 
        Log.e(TAG, "fail to request location update, ignore", ex); 
       } catch (IllegalArgumentException ex) { 
        Log.e(TAG, "gps provider does not exist " + ex.getMessage()); 
       } 
      } else { 
       withoutLocation = true; 

       // do not use GPS 
       String[] savedNotifCenter = pref.getString(PrefConstants.PREF_APP_SERVICE_CENTER, PrefConstants.PREF_APP_SERVICE_CENTER_DEFAULT).split(","); 
       double savedLat = Double.parseDouble(savedNotifCenter[0]); 
       double savedLng = Double.parseDouble(savedNotifCenter[1]); 

       locationFound = true; // prevent the service from stopping 
       findClosest(savedLat, savedLng); 
      } 
     } else { 
      stopSelf(); 
      return; 
     } 

     /*if (isForeground(getPackageName())) { 
      Log.i(getClass().getSimpleName(), "application is in foreground, stopping service"); 
      stopSelf(); 
      return; 
     }*/ 

     myTimer.schedule(myTask, TIMEOUT); 
    } 

    public boolean isForeground(String myPackage) { 
     ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); 
     List<ActivityManager.RunningTaskInfo> runningTaskInfo = manager.getRunningTasks(1); 
     ComponentName componentInfo = runningTaskInfo.get(0).topActivity; 
     return componentInfo.getPackageName().equals(myPackage); 
    } 

    @Override 
    public void onDestroy() { 
     Log.d(TAG, "onDestroy"); 
     super.onDestroy(); 
     if (mLocationManager != null) { 
      for (LocationListener mLocationListener : mLocationListeners) { 
       try { 
        mLocationManager.removeUpdates(mLocationListener); 
       } catch (SecurityException se) { 
        Log.e(TAG, "security exception", se); 
       } catch (Exception ex) { 
        Log.e(TAG, "fail to remove location listeners, ignore", ex); 
       } 
      } 
     } 
    } 

    private void findClosest(final double lat, final double lng) { 
     new Thread(new Runnable() { 
      @Override 
      public void run() { 
       String url = URLs.buildURL(URLs.NOTIFICATIONS); 
       url += "?lat=" + lat; 
       url += "&lng=" + lng; 

       final SharedPreferences pref = getSharedPreferences(PrefConstants.PREF_APP_FILE, MODE_PRIVATE); 
       if (pref.contains(PrefConstants.PREF_APP_SERVICE_RADIUS)) { 
        url += "&radius=" + pref.getInt(PrefConstants.PREF_APP_SERVICE_RADIUS, PrefConstants.PREF_APP_SERVICE_RADIUS_DEFAULT); 
       } 

       url += "&limit=" + PrefConstants.PREF_APP_MAP_LIMIT_DEFAULT; 

       if (pref.contains(PrefConstants.PREF_APP_SERVICE_IV)) { 
        url += "&iv=" + pref.getInt(PrefConstants.PREF_APP_SERVICE_IV, PrefConstants.PREF_APP_SERVICE_IV_DEFAULT); 
       } 

       String exclusionsNumbers = getExcludedNumbersParam(); 
       if (exclusionsNumbers.length() > 0) { 
        url += "&exNum=" + exclusionsNumbers; 
       } 

       final NotificationsHistoryManager notificationsHistoryManager = new NotificationsHistoryManager(ScannerService.this); 
       final List<Long> excludedIds = notificationsHistoryManager.getAlreadyFoundObjects(); 
       String exclusionsIds = getExcludedIdsParam(excludedIds); 
       if (exclusionsIds.length() > 0) { 
        url += "&exId=" + exclusionsIds; 
       } 

       /*final long lastId = pref.getLong(PrefConstants.PREF_SERVICE_LAST_ID, 0L); 
       url += "&li=" + lastId;*/ 

       final Context context = ScannerService.this; 
       HTTPRequestManager requestManager = new HTTPRequestManager(context, url, true, null, new HTTPResponseListener() { 
        @Override 
        public void onSuccess(JSONObject response) { 
         try { 
          JSONArray responseArray = response.getJSONArray("objects"); 
          final String foundString = getString(R.string.found); 
          final String inCityString = getString(R.string.in_city); 
          final String expiringString = getString(R.string.expiring); 
          final DateFormat sdf = SimpleDateFormat.getTimeInstance(); 
          final Resources res = getResources(); 
          final String packageName = getPackageName(); 
          final String mapsApiKey = getString(R.string.google_maps_key); 
          final boolean notifClickAutoCancel = pref.getBoolean(PrefConstants.PREF_APP_SERVICE_NOTIFCANCEL, PrefConstants.PREF_APP_SERVICE_NOTIFCANCEL_DEFAULT); 
          final boolean notifExpiredAutoCancel = pref.getBoolean(PrefConstants.PREF_APP_SERVICE_NOTIFCANCELEXPIRED, PrefConstants.PREF_APP_SERVICE_NOTIFCANCELEXPIRED_DEFAULT); 
          final boolean mapPicture = pref.getBoolean(PrefConstants.PREF_APP_SERVICE_MAPPICTURE, PrefConstants.PREF_APP_SERVICE_MAPPICTURE_DEFAULT); 
          final Locale defaultLocale = Locale.getDefault(); 

          Calendar calendar = Calendar.getInstance(); 
          // long maxId = lastId; 
          for (int i = 0; i < responseArray.length(); i++) { 
           try { 
            final MyEntity p = MyEntity.fromJSONLight(responseArray.getJSONObject(i)); 
            // it should never happen, but notifications are shown many times :/ 
            if (!excludedIds.contains(p.getId())) { 
             excludedIds.add(p.getId()); 
             // maxId = Math.max(p.getId(), maxId); 
             final double iv = p.getIV(); 
             final long expirationFixed = (p.getDisappearTime() - System.currentTimeMillis() - 2000); 

             final Calendar expirationTime = (Calendar) calendar.clone(); 
             // now.add(Calendar.SECOND, (int) ((p.getDisappearTime() - System.currentTimeMillis()/1000) - 2)); 
             expirationTime.setTimeInMillis(expirationTime.getTimeInMillis() + expirationFixed); 

             final int distance = (int) Math.round(1000 * Haversine.distance(lat, lng, p.getLatitude(), p.getLongitude())); 

             String cityName = null; 
             Geocoder gcd = new Geocoder(context, defaultLocale); 
             List<Address> addresses = gcd.getFromLocation(p.getLatitude(), p.getLongitude(), 1); 
             if (addresses.size() > 0) { 
              cityName = addresses.get(0).getLocality(); 
             } 
             final String cityNameParam = cityName; 
             new Thread(new Runnable() { 
              @Override 
              public void run() { 
               sendNotification((int) (p.getId()), 
                   foundString + " " + p.getName() + (iv > 0 ? " " + iv + "%" : "") + (cityNameParam != null ? " " + inCityString + " " + cityNameParam : ""), 
                   expiringString + " " + sdf.format(expirationTime.getTime()) + " - " + distance + "m" + (movesStringParam != null ? " (" + movesStringParam + ")" : ""), 
                   p, 
                   res, 
                   packageName, 
                   notifClickAutoCancel, 
                   notifExpiredAutoCancel, 
                   expirationFixed, 
                   mapsApiKey, 
                   mapPicture); 
              } 
             }).start(); 
            } 
           } catch (Exception e) { 
            Log.e(TAG, "error", e); 
           } 
          } 

          notificationsHistoryManager.saveAlreadyFoundObjects(excludedIds); 

          stopSelf(); 
         } catch (Exception e) { 
          Log.e(TAG, "error in reading JSONArray", e); 
          stopSelf(); 
         } 
        } 

        @Override 
        public void onError(int errorCode) { 
         stopSelf(); 
        } 
       }); 

       RequestQueue requestQueue = Volley.newRequestQueue(context); 
       requestQueue.add(requestManager); 
      } 
     }).start(); 
    } 

    private String getExcludedNumbersParam() { 
     String exclusionsNumbers = ""; 
     List<Integer> excludedNumbers = new FilterPreferencesManager(ScannerService.this).getNotificationsExcludedNumbers(); 
     int sizeNumbers = excludedNumbers.size(); 
     for (int i = 0; i < sizeNumbers; i++) { 
      exclusionsNumbers += excludedNumbers.get(i); 

      if (i < sizeNumbers - 1) { 
       exclusionsNumbers += ","; 
      } 
     } 

     return exclusionsNumbers; 
    } 

    private String getExcludedIdsParam(List<Long> excludedIds) { 
     String exclusionsIds = ""; 

     int sizeIds = excludedIds.size(); 
     for (int i = 0; i < sizeIds; i++) { 
      exclusionsIds += excludedIds.get(i); 

      if (i < sizeIds - 1) { 
       exclusionsIds += ","; 
      } 
     } 
     return exclusionsIds; 
    } 

    private Locale locale = Locale.getDefault(); 

    private void sendNotification(final int notificationId, 
      final String title, 
      final String message, 
      final MyEntity entity, 
      final Resources res, 
      final String packageName, 
      final boolean autoClickCancel, 
      final boolean autoExpiredCancel, 
      final long expirationFromNow, 
      final String mapsApiKey, 
      final boolean mapPicture) { 

     final double entityLat = entity.getLatitude(); 
     final double entityLng = entity.getLongitude(); 

     Intent mapIntent = null; 
     try { 
      String urlAddress = "http://maps.google.com/maps?q=" + entityLat + "," + entityLng; 
      mapIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlAddress)); 
     } catch (Exception e) { 
      Log.e(TAG, "error in notification intent preparation", e); 
     } 
     PendingIntent pendingIntent = PendingIntent.getActivity(ScannerService.this, 0, mapIntent, PendingIntent.FLAG_CANCEL_CURRENT); 

     Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); 

     int drawable = res.getIdentifier("entity" + String.format(locale, "%04d", entity.getNumber()) + "big", "drawable", packageName); 
     final NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(ScannerService.this) 
       .setSmallIcon(drawable) 
       .setContentTitle(title) 
       .setContentText(message) 
       .setAutoCancel(autoClickCancel) 
       .setSound(defaultSoundUri) 
       .setPriority(Notification.PRIORITY_HIGH) 
       .setLights(ContextCompat.getColor(ScannerService.this, R.color.colorPrimary), 500, 2000); 

     if (mapPicture) { 
      String imageUrl = "https://maps.googleapis.com/maps/api/staticmap" 
        + "?center=" + entityLat + "," + entityLng 
        + "&zoom=14" 
        + "&scale=false" 
        + "&size=450x275" 
        + "&maptype=roadmap" 
        + "&key=" + mapsApiKey 
        + "&format=jpg" 
        + "&visual_refresh=true"; 

      Log.i(getClass().getSimpleName(), "generated url for notification image: " + imageUrl); 
      Bitmap bmURL = getBitmapFromURL(imageUrl); 
      if (bmURL != null) { 
       notificationBuilder.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(bmURL)); 
      } 
     } 

     if (mapIntent != null) { 
      notificationBuilder.setContentIntent(pendingIntent); 
     } 

     if (autoExpiredCancel) { 
      Log.i(getClass().getSimpleName(), "setting notification timer for expiration, id: " + notificationId + ", expiring in " + expirationFromNow + "ms"); 

      Timer timer = new Timer(); 
      timer.schedule(new TimerTask() { 
       @Override 
       public void run() { 
        Log.i(getClass().getSimpleName(), "canceling notification expired, id: " + notificationId); 
        notificationManager.cancel(notificationId); 
       } 
      }, expirationFromNow); 
     } 
    } 
    // } 

    private Bitmap getBitmapFromURL(String strURL) { 
     try { 
      URL url = new URL(strURL); 
      HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 
      connection.setDoInput(true); 
      connection.connect(); 
      InputStream input = connection.getInputStream(); 
      return BitmapFactory.decodeStream(input); 
     } catch (IOException e) { 
      e.printStackTrace(); 
      return null; 
     } 
    } 
} 

Benachrichtigungen Geschichte Manager

import android.content.Context; 
import android.content.SharedPreferences; 
import android.util.Log; 

import java.util.ArrayList; 
import java.util.HashSet; 
import java.util.List; 
import java.util.Set; 

public class NotificationsHistoryManager { 

    private final static String PREF_FILE = "nh"; 
    private final static String PREF_FOUND_KEY = "f"; 

    private SharedPreferences pref; 

    public NotificationsHistoryManager(Context context) { 
     pref = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); 
    } 

    public void saveAlreadyFoundObjects(List<Long> found) { 
     Set<String> idsString = new HashSet<>(); 
     int size = found.size(); 
     for (int i = Math.max(0, size - 200); i < size; i++) { 
      long f = found.get(i); 
      idsString.add(f + ""); 
     } 

     pref.edit().putStringSet(PREF_FOUND_KEY, idsString).apply(); 
    } 

    public List<Long> getAlreadyFoundObjects() { 
     List<Long> excluded = new ArrayList<>(); 

     for (String id : pref.getStringSet(PREF_FOUND_KEY, new HashSet<String>())) { 
      try { 
       excluded.add(Long.parseLong(id)); 
      } catch (Exception e) { 
       Log.e(getClass().getSimpleName(), "error in parsing string '" + id + "' to long id: " + e.getMessage()); 
      } 
     } 

     return excluded; 
    } 

    public void clean() { 
     pref.edit().clear().apply(); 
    } 
} 

Hinweis: Wenn MainActivity gestartet wird, überprüft es, ob eine Instanz des Dienstes ausgeführt wird und Falls nicht, wird ein neuer mit einem AlarmManager geplant. Ich dachte, es sei die Ursache des Problems, aber der Dienst überprüft, wie Sie sehen, jedes Mal, was bereits gemeldet wurde, und überspringt es. Ich habe versucht, START_STICKY zu NOT_STICKY zu ändern, Einstellungen zu verwenden, um doppelte IDs zu verarbeiten, Vorgänge zu synchronisieren ... Ich weiß nicht, was ich sonst noch versuchen sollte. Bitte helfen Sie mir :) Wenn Sie weitere Informationen benötigen, fragen Sie einfach.

Vielen Dank!

Antwort

0

Um zu teilen, was ich gefunden habe ... Ich habe verstanden, was das Problem ist. Werfen Sie einen Blick auf NotificationsHistoryManager: Es verwendet, um die Liste der gefundenen Objekte zu speichern, ein Set (der einzige "Liste" Objekttyp in SharedPreferences), nur die letzten 200 Objekte gespeichert (alte abläuft, so dass es bedeutungslos ist) behalte sie). DAS PROBLEM IST: SET SIND NICHT BESTELLTE LISTE. Die 200 Objekte, die ich speichere, sind nicht die LETZTEN, die hinzugefügt werden, denn wenn ich sie aus pref (getAlreadyFoundObjects()) lese, werden sie in einem Satz geschrieben, "zufällig" geordnet. Ich musste die Art, wie ich sie gespeichert habe, ändern, indem ich eine benutzerdefinierte Zeichenfolge (durch Komma getrennte Werte) erstellte, um sicher zu sein, dass sie in der von mir gewünschten Reihenfolge gespeichert wurden.

Ich hoffe, es hilft jemandem.

Verwandte Themen