通过后台服务获取位置更新

时间:2015-05-16 18:48:17

标签: android android-service android-location android-alarms android-googleapiclient

我刚刚开始学习如何使用服务,我正在尝试构建一个应用程序,当设备与用户定义的位置相距一定距离时会触发警报。 我读了几个教程并写了这个:

 package com.example.fgps;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.IntentSender;
import android.database.Cursor;
import android.location.Location;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.model.LatLng;

public class BackgroundLocationService extends Service implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
LocationListener {
    public static final String TAG = BackgroundLocationService.class.getSimpleName();

    private List<LocationAlarm> alarms;
    private final static int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;

    private GoogleApiClient mGoogleApiClient;
    private boolean mInProgress;

    private LocationRequest mLocationRequest;



    public void onCreate(){
        super.onCreate();

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

        // Create the LocationRequest object
        mLocationRequest = LocationRequest.create()
                .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
                .setInterval(3 * 1000)        // 3 seconds, in milliseconds
                .setFastestInterval(1 * 500); // 1/2 second, in milliseconds
        openDB();
        Cursor c =Mdb.getAllRows(); 
        LatLng latLng;
        for(alarms = new ArrayList<LocationAlarm>(); !c.isAfterLast(); c.moveToNext()) {
            latLng=new LatLng(c.getDouble(c.getColumnIndex(c.getColumnName(2))),c.getDouble(c.getColumnIndex(c.getColumnName(3))));
            alarms.add(new LocationAlarm(c.getInt(c.getColumnIndex(c.getColumnName(4))) != 0, latLng, c.getDouble(c.getColumnIndex(c.getColumnName(5))), c.getString(c.getColumnIndex(c.getColumnName(1)))));
        }

    }
    @Override
    public int onStartCommand (Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);

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

        setUpLocationClientIfNeeded();

        if(!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting() && !mInProgress) {
            mInProgress = true;
            mGoogleApiClient.connect();
        }

        return START_STICKY;
    }
    private void setUpLocationClientIfNeeded() {
        Location location = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
        if (location == null) {
            LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void onConnected(Bundle bundle) {
        Location location = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
        if (location == null) {
            LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
        }
        else {
            handleNewLocation(location);
        }
    }

    @Override
    public void onConnectionSuspended(int i) {

    }


    public void onConnectionFailed(ConnectionResult connectionResult) {
        mInProgress = false;

        // * 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()) {
            try {
                // Start an Activity that tries to resolve the error
                connectionResult.startResolutionForResult(null, CONNECTION_FAILURE_RESOLUTION_REQUEST);

                // * Thrown if Google Play services canceled the original
                // * PendingIntent

            } catch (IntentSender.SendIntentException e) {
                // Log the error
                e.printStackTrace();
            }
        } else {

             //* If no resolution is available, display a dialog to the
            // * user with the error.

            Log.i(TAG, "Location services connection failed with code " + connectionResult.getErrorCode());
        }
    }
    @Override
    public void onDestroy(){
        mInProgress = false;
        if (mGoogleApiClient.isConnected()) {
            LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
            mGoogleApiClient.disconnect();
        }
        super.onDestroy();
    }

    @Override
    public void onLocationChanged(Location location) {
        handleNewLocation(location);
    }

    private DBAdapter Mdb;
    private void handleNewLocation(Location location) {
        Log.d(TAG, location.toString());
        int c=0;
        for(int i=0;i<alarms.size();i++)
        {
            if(alarms.get(i).getActive())
            {
                break;
            }
            else{
                c++;
            }
        }
        if(c==alarms.size())
            stopSelf();

        double currentLatitude = location.getLatitude();
        double currentLongitude = location.getLongitude();
        LatLng latLng = new LatLng(currentLatitude, currentLongitude);
        for(int i=0;i<alarms.size();i++)
        {
            if(alarms.get(i).getActive() && (distanceCal(latLng, alarms.get(i).getLatlng())<=alarms.get(i).getDistance()))
            {
                setOffAlarm();
            }
        }

    }
    private void setOffAlarm() {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.SECOND, 1);

        //Create a new PendingIntent and add it to the AlarmManager
        Intent intent = new Intent(this, AlarmReceiverActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,
            12345, intent, PendingIntent.FLAG_CANCEL_CURRENT);
        AlarmManager am = 
            (AlarmManager)getSystemService(Activity.ALARM_SERVICE);
        am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(),
                pendingIntent);
    }
    private double distanceCal(LatLng Mloc,LatLng destination)
    {
        final int R = 6371; // Radius of the earth
        Double lat1 = Mloc.latitude;
        Double lon1 = Mloc.longitude;
        Double lat2 = destination.latitude; 
        Double lon2 = destination.longitude;
        Double latDistance = toRad(lat2-lat1);
        Double lonDistance = toRad(lon2-lon1);
        Double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2) + 
                   Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * 
                   Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
        Double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
        Double distance = R * c;
        return distance;
    }
    private Double toRad(Double value) {
        return value * Math.PI / 180;
    }

    private void openDB(){
        Mdb = new DBAdapter(this);
        Mdb.open();
    }
}

当我在模拟器中运行应用程序时,应用程序崩溃,并在日志中写有“谷歌客户端尚未连接”。 我该如何解决?此外,这项服务甚至可以正确构建吗?

编辑:--------------------------------------------- -------

我更改了setoffalarm():

private void setOffAlarm(int index) {
    Log.d("setoff", "got here");
    Calendar cal = Calendar.getInstance();
    cal.add(Calendar.SECOND, 1);

    //Create a new PendingIntent and add it to the AlarmManager
    Intent intent = new Intent(this, AlarmReceiverActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this,
        index, intent, PendingIntent.FLAG_CANCEL_CURRENT);
    AlarmManager am = 
        (AlarmManager)getSystemService(Activity.ALARM_SERVICE);
    am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(),
            pendingIntent);
}

我还补充说:

for(int i=0;i<alarms.size();i++)
    {
        if(alarms.get(i).getActive() && (distanceCal(latLng, alarms.get(i).getLatlng())<=alarms.get(i).getDistance()))
        {
            setOffAlarm(i);
        }
    }

但它仍然不会激活闹钟,有什么建议吗? 编辑2 -------------------------------------------

alarmreceiver类:

    package com.example.fgps;

import java.io.IOException;

import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;

/**
 * This activity will be called when the alarm is triggered.
 * 
 */
public class AlarmReceiverActivity extends Activity {
    private MediaPlayer mMediaPlayer; 
    private PowerManager.WakeLock wl;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "My Tag");
        wl.acquire();
        Log.d("alarm", "got here");
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN | 
                WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | 
                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | 
                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON,
                WindowManager.LayoutParams.FLAG_FULLSCREEN | 
                WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | 
                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | 
                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
        setContentView(R.layout.alarm);

        Button stopAlarm = (Button) findViewById(R.id.stopAlarm);
        stopAlarm.setOnTouchListener(new OnTouchListener() {
            public boolean onTouch(View arg0, MotionEvent arg1) {
                mMediaPlayer.stop();
                finish();
                return false;
            }
        });

        playSound(this, getAlarmUri());
    }

    @Override
    protected void onStop() {
        super.onStop();
        wl.release();
    }

    private void playSound(Context context, Uri alert) {
        mMediaPlayer = new MediaPlayer();
        try {
            mMediaPlayer.setDataSource(context, alert);
            final AudioManager audioManager = (AudioManager) context
                    .getSystemService(Context.AUDIO_SERVICE);
            if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
                mMediaPlayer.prepare();
                mMediaPlayer.start();
            }
        } catch (IOException e) {
            System.out.println("OOPS");
        }
    }

    //Get an alarm sound. Try for an alarm. If none set, try notification, 
    //Otherwise, ringtone.
    private Uri getAlarmUri() {
        Uri alert = RingtoneManager
                .getDefaultUri(RingtoneManager.TYPE_ALARM);
        if (alert == null) {
            alert = RingtoneManager
                    .getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
            if (alert == null) {
                alert = RingtoneManager
                        .getDefaultUri(RingtoneManager.TYPE_RINGTONE);
            }
        }
        return alert;
    }
}

“闹钟,到达这里”并没有出现在日志中

1 个答案:

答案 0 :(得分:1)

问题是,当API尚未连接时,您正在调用setUpLocationClientIfNeeded()

要解决此错误,请在调用onConnected()之前等待setUpLocationClientIfNeeded()回调。

因此,请将其从onStartCommand()

中删除
@Override
public int onStartCommand (Intent intent, int flags, int startId) {
    super.onStartCommand(intent, flags, startId);

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

    //remove this:
    //setUpLocationClientIfNeeded();

    if(!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting() && !mInProgress) {
        mInProgress = true;
        mGoogleApiClient.connect();
    }

    return START_STICKY;
}

然后将其添加到onConnected()

@Override
public void onConnected(Bundle bundle) {
    Location location = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
    if (location == null) {
        LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
    }
    else {
        handleNewLocation(location);
    }

    //add this here!
    setUpLocationClientIfNeeded();
}

编辑:关于未触发的警报,看起来您只创建了一个警报,列表中的每个警报都会覆盖前一个警报。 您需要为每个PendingIntent提供不同的requestCode以便解决此问题。

请参阅documentation for PendingIntent.getActivity() here

因此,您应该向setAlarm()添加一个参数,您只需使用alarms列表中的索引:

    for(int i=0;i<alarms.size();i++)
    {
        if(alarms.get(i).getActive() && (distanceCal(latLng, alarms.get(i).getLatlng())<=alarms.get(i).getDistance()))
        {
            setOffAlarm(i); //give the index here
        }
    }

然后,在setOffAlarm()中,使用传入参数的索引作为requestCode,而不是将其硬编码为12345

private void setOffAlarm(int index) {
    Calendar cal = Calendar.getInstance();
    cal.add(Calendar.SECOND, 1);

    //Create a new PendingIntent and add it to the AlarmManager
    Intent intent = new Intent(this, AlarmReceiverActivity.class);

    //don't use 12345 for requestCode, this will overwrite previous alarm
    //PendingIntent pendingIntent = PendingIntent.getActivity(this,
    //    12345, intent, PendingIntent.FLAG_CANCEL_CURRENT);

    //use index instead:
    PendingIntent pendingIntent = PendingIntent.getActivity(this,
        index, intent, PendingIntent.FLAG_CANCEL_CURRENT);
    AlarmManager am = 
        (AlarmManager)getSystemService(Activity.ALARM_SERVICE);
    am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(),
            pendingIntent);
}