在后台服务中创建地理围栏

Posted

技术标签:

【中文标题】在后台服务中创建地理围栏【英文标题】:creating geofences in background service 【发布时间】:2016-11-10 20:31:30 【问题描述】:

我正在制作一个 android 应用程序,希望在我的服务器上指定的 LatLong 坐标处创建地理围栏。

坐标会在后台从服务中定期更新。我正在阅读这些坐标,并希望在没有用户干预的情况下在后台为其创建地理围栏。我尝试在服务中这样做,但它不起作用。我可以定期从服务器获取坐标,但我无法为它创建地理围栏,得到一个 nullPointerException。

但是,我可以通过单击按钮(硬编码 LatLong 坐标)在 Activity 中创建地理围栏,但它在服务中不起作用。

我的服务:

public class GPSPollingService extends Service implements ResultCallback<Status>, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener 

private GoogleApiClient mGoogleApiClient;
private PowerManager.WakeLock mWakeLock;
private LocationRequest mLocationRequest;
// Flag that indicates if a request is underway.
private boolean mInProgress;
private final String TAG = "GPSPollingService";

private PendingIntent mGeofencePendingIntent;
String url = "http://shielded.coolpage.biz/status_read.php";
private static final int REQUEST_CODE_LOCATION = 2;
private Boolean servicesAvailable = false;
private Activity mActivity;

ArrayList<Geofence> mCurrentGeofences = new ArrayList<Geofence>();;
RequestQueue requestQueue;
Location mLocation;

public GPSPollingService()

public GPSPollingService(Activity mActivity)
    super();
    this.mActivity = mActivity;



@Nullable
@Override
public IBinder onBind(Intent intent) 
    return null;


@Override
public void onCreate() 
    super.onCreate();
    mInProgress = false;
    requestQueue = Volley.newRequestQueue(this);
    // Instantiate the current List of geofences
    mCurrentGeofences = new ArrayList<Geofence>();

    mGeofencePendingIntent = null;
    setUpLocationClientIfNeeded();


@Override
public int onStartCommand(Intent intent, int flags, int startId) 
    super.onStartCommand(intent, flags, startId);
    mGoogleApiClient.connect();
    Log.d(TAG,"onStartCommand: ");
    PowerManager mgr = (PowerManager)getSystemService(Context.POWER_SERVICE);
    /*
    WakeLock is reference counted so we don't want to create multiple WakeLocks. So do a check before initializing and acquiring.
    This will fix the "java.lang.Exception: WakeLock finalized while still held: MyWakeLock" error that you may find.
    */
    if (this.mWakeLock == null)  //**Added this
        this.mWakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLock");
    

    if (!this.mWakeLock.isHeld())  //**Added this
        this.mWakeLock.acquire();
    

    if(!servicesAvailable || mGoogleApiClient.isConnected() || mInProgress)
        return START_STICKY;

    setUpLocationClientIfNeeded();
    if(!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting() && !mInProgress)
    
        //appendLog(DateFormat.getDateTimeInstance().format(new Date()) + ": Started", Constants.LOG_FILE);
        mInProgress = true;
        mGoogleApiClient.connect();
    

    return START_STICKY;




private void setUpLocationClientIfNeeded()

    if(mGoogleApiClient == null)
        googleApiClientBuild();

protected synchronized void googleApiClientBuild()
    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addApi(LocationServices.API)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .build();



@Override
public void onConnected(@Nullable Bundle bundle) 

    mLocationRequest = LocationRequest.create();
    mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    mLocationRequest.setInterval(120000); // Update location every 60 seconds

    Log.d(TAG,"in onConnected: ");

    if ( ContextCompat.checkSelfPermission( this, android.Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) 

        ActivityCompat.requestPermissions( mActivity, new String[]   android.Manifest.permission.ACCESS_COARSE_LOCATION  ,
                REQUEST_CODE_LOCATION );
    else 
        LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
        //Location mLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
        //txtOutput.setText(mLocation.toString());
        Log.d(TAG, "calling fusedLocationApi");
    

    continueAddGeofences();


@Override
public void onConnectionSuspended(int i) 

    // Turn off the request flag
    mInProgress = false;
    // Destroy the current location client
    mGoogleApiClient = null;
    Log.d(TAG, "onConnectionSuspended ");


@Override
public void onLocationChanged(Location location) 
    mLocation = location;
    Log.d(TAG,"in onLocationChanged: ");
    Log.d(TAG, mLocation.toString());
    // save new location to db


    connectToDb();          // checking for status 1





@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) 

    mInProgress = false;

    Log.d(TAG, "onConnectionFailed");
    /*
     * Google Play services can resolve some errors it detects.
     * If the error has a resolution, try sending an Intent to
     * start a Google Play services activity that can resolve
     * error.
     */
    if (connectionResult.hasResolution()) 
        Log.d(TAG, "google play services has solution");

        // If no resolution is available, display an error dialog
     else 

        Log.d(TAG, "no solution for connection failure");
    


@Override
public void onDestroy() 
    // Turn off the request flag
    this.mInProgress = false;

    if (this.servicesAvailable && this.mGoogleApiClient != null) 
        this.mGoogleApiClient.unregisterConnectionCallbacks(this);
        this.mGoogleApiClient.unregisterConnectionFailedListener(this);
        this.mGoogleApiClient.disconnect();
        // Destroy the current location client
        this.mGoogleApiClient = null;
    
    // Display the connection status
    // Toast.makeText(this, DateFormat.getDateTimeInstance().format(new Date()) + ":
    // Disconnected. Please re-connect.", Toast.LENGTH_SHORT).show();

    if (this.mWakeLock != null) 
        this.mWakeLock.release();
        this.mWakeLock = null;
    

    super.onDestroy();

public void connectToDb()
    StringRequest stringRequest = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() 
        @Override
        public void onResponse(String s) 
            Toast.makeText(getApplicationContext() , ""+s,Toast.LENGTH_LONG).show();
            Log.d(TAG, ""+s);
            try 
                JSONObject jsonObject = new JSONObject(s);
                JSONArray jsonArray = jsonObject.getJSONArray("result");
                if(jsonArray.length()!=0)
                    Log.d(TAG,"array length not 0");
                    JSONObject personInDanger = jsonArray.getJSONObject(0);
                    setGeofence(Double.valueOf(personInDanger.getString("latitude")), Double.valueOf(personInDanger.getString("longitude")));
                
        catch (JSONException je) 
            // do something with it
            
        
    , new Response.ErrorListener() 
        @Override
        public void onErrorResponse(VolleyError volleyError) 
            Toast.makeText(getApplicationContext(), ""+volleyError, Toast.LENGTH_LONG).show();

        
    );

    requestQueue.add(stringRequest);


/**
 * Verify that Google Play services is available before making a request.
 *
 * @return true if Google Play services is available, otherwise false
 */
private boolean servicesConnected() 

    // Check that Google Play services is available
    int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);

    // If Google Play services is available
    if (ConnectionResult.SUCCESS == resultCode) 

        // In debug mode, log the status
        Log.d(GeofenceUtils.APPTAG, getString(R.string.play_services_available));

        // Continue
        return true;

        // Google Play services was not available for some reason
     else 

        // Display an error dialog
        Toast.makeText(getApplicationContext(), "Google play services not available", Toast.LENGTH_SHORT).show();
        //Dialog dialog = GooglePlayServicesUtil.getErrorDialog(resultCode, (Activity) getApplicationContext(), 0);
        //if (dialog != null) 
        //    ErrorDialogFragment errorFragment = new ErrorDialogFragment();
        //    errorFragment.setDialog(dialog);
        //    errorFragment.show(getSupportFragmentManager(), GeofenceUtils.APPTAG);
        
        return false;
    

public void setGeofence(Double latitude , Double longitude)

    Log.d(TAG, "in setGeofence");
    if (!servicesConnected()) 

        return;
    

    SimpleGeofence newGeofence = new SimpleGeofence(
            "1",
            // Get latitude, longitude, and radius from the db
            latitude,
            longitude,
            Float.valueOf("50.0"),
            // Set the expiration time
            Geofence.NEVER_EXPIRE,
            // Detect both entry and exit transitions
            Geofence.GEOFENCE_TRANSITION_ENTER
    );



    mCurrentGeofences.add(newGeofence.toGeofence());
    // Start the request. Fail if there's already a request in progress
    try 
        // Try to add geofences
        addGeofences(mCurrentGeofences);
     catch (UnsupportedOperationException e) 
        // Notify user that previous request hasn't finished.
        Toast.makeText(this, R.string.add_geofences_already_requested_error,
                Toast.LENGTH_LONG).show();
    



public void addGeofences(ArrayList<Geofence> geofences) throws UnsupportedOperationException 
    if (!mInProgress) 

        // Toggle the flag and continue
        mInProgress = true;

        // Request a connection to Location Services
        requestConnection();

        // If a request is in progress
     else 

        // Throw an exception and stop the request
        throw new UnsupportedOperationException();
    


private void requestConnection() 
    getLocationClient().connect();


private GoogleApiClient getLocationClient() 
    if (mGoogleApiClient == null) 

        mGoogleApiClient = new GoogleApiClient.Builder(mActivity)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
    
    return mGoogleApiClient;



private void continueAddGeofences() 

    // Get a PendingIntent that Location Services issues when a geofence transition occurs
    mGeofencePendingIntent = createRequestPendingIntent();
    if (!mGoogleApiClient.isConnected()) 
        Toast.makeText(mActivity, "not connected", Toast.LENGTH_SHORT).show();
        return;
    

    try 
        LocationServices.GeofencingApi.addGeofences(
                mGoogleApiClient,
                // The GeofenceRequest object.
                mCurrentGeofences,
                // A pending intent that that is reused when calling removeGeofences(). This
                // pending intent is used to generate an intent when a matched geofence
                // transition is observed.
                mGeofencePendingIntent
        ).setResultCallback(this); // Result processed in onResult().
     catch (SecurityException securityException) 
        // Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
        logSecurityException(securityException);
    



private void logSecurityException(SecurityException securityException) 
    Log.e(GeofenceUtils.APPTAG , "Invalid location permission. " +
            "You need to use ACCESS_FINE_LOCATION with geofences", securityException);


public void onResult(Status status) 
    Intent broadcastIntent = new Intent();

    // Temp storage for messages
    String msg;


    if (status.isSuccess()) 

        Toast.makeText(
                mActivity, "Geofences added",
                Toast.LENGTH_SHORT
        ).show();

        msg = mActivity.getString(R.string.add_geofences_result_success, status);

        // In debug mode, log the result
        Log.d(GeofenceUtils.APPTAG, msg);

        // Create an Intent to broadcast to the app
        broadcastIntent.setAction(GeofenceUtils.ACTION_GEOFENCES_ADDED)
                .addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES)
                .putExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS, msg);
        // If adding the geofences failed
     else 

        /*
         * Create a message containing the error code and the list
         * of geofence IDs you tried to add
         */
        msg = mActivity.getString(
                R.string.add_geofences_result_failure,
                status.getStatusCode(), status.getStatusMessage());

        // Log an error
        Log.e(GeofenceUtils.APPTAG, msg);

    



private PendingIntent createRequestPendingIntent() 

    // If the PendingIntent already exists
    if (null != mGeofencePendingIntent) 

        // Return the existing intent
        return mGeofencePendingIntent;

        // If no PendingIntent exists
     else 
        Intent intent = new Intent("com.example.android.geofence.ACTION_RECEIVE_GEOFENCE");
        return PendingIntent.getBroadcast(
                mActivity,
                0,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
    

我的日志:

java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
                                                                                 at android.app.PendingIntent.getBroadcastAsUser(PendingIntent.java:506)
                                                                                 at android.app.PendingIntent.getBroadcast(PendingIntent.java:495)
                                                                                 at com.example.android.safetyalert.GPSPollingService.createRequestPendingIntent(GPSPollingService.java:449)
                                                                                 at com.example.android.safetyalert.GPSPollingService.continueAddGeofences(GPSPollingService.java:369)
                                                                                 at com.example.android.safetyalert.GPSPollingService.onConnected(GPSPollingService.java:163)
                                                                                 at com.google.android.gms.common.internal.zzl.zzm(Unknown Source)
                                                                                 at com.google.android.gms.internal.zzof.zzk(Unknown Source)
                                                                                 at com.google.android.gms.internal.zzod.zzsb(Unknown Source)
                                                                                 at com.google.android.gms.internal.zzod.onConnected(Unknown Source)
                                                                                 at com.google.android.gms.internal.zzoh.onConnected(Unknown Source)
                                                                                 at com.google.android.gms.internal.zznw.onConnected(Unknown Source)
                                                                                 at com.google.android.gms.common.internal.zzk$1.onConnected(Unknown Source)
                                                                                 at com.google.android.gms.common.internal.zzd$zzj.zztp(Unknown Source)
                                                                                 at com.google.android.gms.common.internal.zzd$zza.zzc(Unknown Source)
                                                                                 at com.google.android.gms.common.internal.zzd$zza.zzw(Unknown Source)
                                                                                 at com.google.android.gms.common.internal.zzd$zze.zztr(Unknown Source)
                                                                                 at com.google.android.gms.common.internal.zzd$zzd.handleMessage(Unknown Source)
                                                                                 at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                                 at android.os.Looper.loop(Looper.java:135)
                                                                                 at android.app.ActivityThread.main(ActivityThread.java:5312)
                                                                                 at java.lang.reflect.Method.invoke(Native Method)
                                                                                 at java.lang.reflect.Method.invoke(Method.java:372)
                                                                                 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:901)
                                                                                 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:696)

我已经尝试了我能想到的一切。 我正在轮询 GPS,以便为操作系统提供准确的位置,以提高地理围栏的准确性。我知道它会耗尽我的电池,但现在这不是问题。

【问题讨论】:

您的代码工作正常吗?如果是,请上传代码。我在地理围栏中使用后台服务。 【参考方案1】:

Service 已经有Context。您不应该使用来自ActivityContext,因为如果您的Activity 被杀死,mActivity 将为空,从而导致各种问题。用这个替换 createRequestPendingIntent 中的 else 块

 else 
    Intent intent = new Intent("com.example.android.geofence.ACTION_RECEIVE_GEOFENCE");
    return PendingIntent.getBroadcast(
            this,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT);

【讨论】:

以上是关于在后台服务中创建地理围栏的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Geofence android 中添加后台服务

应用程序在后台时不会触发地理围栏转换

iOS 7.1 地理围栏和 iBeacons 停止工作

Android 8 或 9 上的后台地理围栏不起作用

如何在 Android 中创建地理围栏?

Android O,后台服务运行超过 30 分钟。为啥?