2017-05-08 19 views
1

我看到了類似這樣的問題,但沒有一個解決了我的問題。 我通過AlarmManager開始後臺服務。每次在服務啓動,如果它已在SharedPreferences被禁用,如果沒有,就重新安排自身的新實例,它會檢查和推移,以下這些替代路徑:Android - 已取消的通知不斷重現

    如果用戶想要使用GPS
  1. ,它等待用戶的位置並使用 調用REST端點;
  2. 如果用戶不想使用GPS,它將使用存儲在首選項中的位置。

HTTP調用(超時:30秒)的結果是一個JSONObject,其產生0- Ñ通知(取決於它找到多少個「近處的物體」)。

我的問題是:通知,即使被用戶取消(滑動或打開它們),通常會再次出現,就好像它們從未顯示過一樣。它永遠不會發生,因爲Web服務會收到每次更新的排除對象id列表。

下面的代碼:

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; 
     } 
    } 
} 

通知歷史經理

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(); 
    } 
} 

注:當MainActivity啓動時,它會檢查服務的一個實例正在運行,如果沒有,則使用AlarmManager安排一個新的。我認爲這是問題的原因,但正如你所看到的那樣,服務每次檢查已經通知的內容並跳過它。 我試着改變START_STICKY爲NOT_STICKY,使用偏好來處理重複的ID,同步操作......我不知道還有什麼要嘗試的。請幫助我:)如果你需要更多的細節,請問。

謝謝!

回答

0

分享我發現的...我明白了問題所在。看看NotificationsHistoryManager:它使用保存找到的對象的列表,一個Set(SharedPreferences中唯一的「列表」對象類型),只保存最後找到的200個對象(舊的對象到期,所以它沒有意義)保留它們)。 問題是:設置沒有排序列表。我保存的200個對象不是最後添加的,因爲當我從pref(getAlreadyFoundObjects())讀取它們時,它們被寫入一個集合中,「隨機」排序。 我不得不改變我存儲它們的方式,創建一個自定義字符串(逗號分隔值),以確保它們以我想要的順序保存。

希望它可以幫助別人。