Android - 我只能在省电 GPS 模式下获取位置信息(为什么我会获取两次?)

Adroid - I get location only on battery saving GPS mode (and why I am getting it twice?)

我希望我的应用程序在从蓝牙 LE 设备接收数据时向传入数据添加日期和时间以及位置前缀。真正奇怪的是,只有当我使用省电 GPS 选项(仅根据 WiFi 和网络信号计算)时,才会计算位置。如果我使用仅 GPS 或高精度选项(GPS 和网络),它不会显示位置。

在清单文件上我使用了权限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
<uses-feature android:name="android.hardware.location.network" />

此外,在那种省电模式下,出于某种未知原因,纬度和经度也会在蓝牙消息后打印出来(我没有暗示)!如果有人也可以帮助我了解为什么会发生这种情况,我将不胜感激。

对应代码如下:

public class Chat extends Activity {
private final static String TAG = Chat.class.getSimpleName();

public static final String EXTRAS_DEVICE = "EXTRAS_DEVICE";
private TextView tv = null;
private EditText et = null;
private Button btn = null;
private String mDeviceName;
private String mDeviceAddress;
private RBLService mBluetoothLeService;
private Map<UUID, BluetoothGattCharacteristic> map = new HashMap<UUID, BluetoothGattCharacteristic>();
private FusedLocationProviderClient mFusedLocationClient;

private final ServiceConnection mServiceConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName componentName,
            IBinder service) {
        mBluetoothLeService = ((RBLService.LocalBinder) service)
                .getService();
        if (!mBluetoothLeService.initialize()) {
            Log.e(TAG, "Unable to initialize Bluetooth");
            finish();
        }
        // Automatically connects to the device upon successful start-up
        // initialization.
        mBluetoothLeService.connect(mDeviceAddress);
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        mBluetoothLeService = null;
    }
};

private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();

        if (RBLService.ACTION_GATT_DISCONNECTED.equals(action)) {
        } else if (RBLService.ACTION_GATT_SERVICES_DISCOVERED
                .equals(action)) {
            getGattService(mBluetoothLeService.getSupportedGattService());
        } else if (RBLService.ACTION_DATA_AVAILABLE.equals(action)) {
            displayData(intent.getByteArrayExtra(RBLService.EXTRA_DATA));
        }
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.second);
    mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);

    tv = (TextView) findViewById(R.id.textView);
    tv.setMovementMethod(ScrollingMovementMethod.getInstance());
    et = (EditText) findViewById(R.id.editText);
    btn = (Button) findViewById(R.id.send);
    btn.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            BluetoothGattCharacteristic characteristic = map
                    .get(RBLService.UUID_BLE_SHIELD_TX);

            String str = et.getText().toString();
            byte b = 0x00;
            byte[] tmp = str.getBytes();
            byte[] tx = new byte[tmp.length + 1];
            tx[0] = b;
            for (int i = 1; i < tmp.length + 1; i++) {
                tx[i] = tmp[i - 1];
            }

            characteristic.setValue(tx);
            mBluetoothLeService.writeCharacteristic(characteristic);

            et.setText("");
        }
    });

    Intent intent = getIntent();

    mDeviceAddress = intent.getStringExtra(Device.EXTRA_DEVICE_ADDRESS);
    mDeviceName = intent.getStringExtra(Device.EXTRA_DEVICE_NAME);

    getActionBar().setTitle(mDeviceName);
    getActionBar().setDisplayHomeAsUpEnabled(true);

    Intent gattServiceIntent = new Intent(this, RBLService.class);
    bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);

    AlertDialog.Builder builder1 = new AlertDialog.Builder(this);
    builder1.setMessage("Παρακαλώ ενεργοποιήστε το gps σε υψηλή ακρίβεια για καταγραφή της τοποθεσίας των μετρήσεων.");
    builder1.setCancelable(true);

    builder1.setPositiveButton(
            "Οκ, έγινε",
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                    dialog.cancel();
                }
            });

    AlertDialog alert11 = builder1.create();
    alert11.show();

}


@Override
protected void onResume() {
    super.onResume();

    registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId() == android.R.id.home) {
        mBluetoothLeService.disconnect();
        mBluetoothLeService.close();

        System.exit(0);
    }

    return super.onOptionsItemSelected(item);
}

@Override
protected void onStop() {
    super.onStop();

    unregisterReceiver(mGattUpdateReceiver);
}

@Override
protected void onDestroy() {
    super.onDestroy();

    mBluetoothLeService.disconnect();
    mBluetoothLeService.close();

    System.exit(0);
}

private void displayData(byte[] byteArray) {
    if (byteArray != null) {
        getLocation();
        getDate();
        tv.append("\n");
        String data = new String(byteArray);
        tv.append(data);
        tv.append("\n");
        // find the amount we need to scroll. This works by
        // asking the TextView's internal layout for the position
        // of the final line and then subtracting the TextView's height
        final int scrollAmount = tv.getLayout().getLineTop(
                tv.getLineCount())
                - tv.getHeight();
        // if there is no need to scroll, scrollAmount will be <=0
        if (scrollAmount > 0)
            tv.scrollTo(0, scrollAmount);
        else
            tv.scrollTo(0, 0);
    }
}

private void getDate() {
    Calendar c = Calendar.getInstance();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd-HH:mm:ss");
    String strDate = sdf.format(c.getTime());
    tv.append(strDate);
}

private void getLocation() {
    try {

        mFusedLocationClient.getLastLocation()
                .addOnSuccessListener(this, new OnSuccessListener<Location>() {
                    @Override
                    public void onSuccess(Location location) {
                        // Got last known location. In some rare situations this can be null.
                        if (location != null) {
                            double lat = location.getLatitude();
                            double lng = location.getLongitude();
                            String latitude = String.valueOf(lat);
                            String longitude = String.valueOf(lng);
                            tv.append(latitude);
                            tv.append(",");
                            tv.append(longitude);
                            tv.append("/");
                        }
                    }
                });

    } catch (SecurityException e) {
        // lets the user know there is a problem with the gps
    }
}

private void getGattService(BluetoothGattService gattService) {
    if (gattService == null)
        return;

    BluetoothGattCharacteristic characteristic = gattService
            .getCharacteristic(RBLService.UUID_BLE_SHIELD_TX);
    map.put(characteristic.getUuid(), characteristic);

    BluetoothGattCharacteristic characteristicRx = gattService
            .getCharacteristic(RBLService.UUID_BLE_SHIELD_RX);
    mBluetoothLeService.setCharacteristicNotification(characteristicRx,
            true);
    mBluetoothLeService.readCharacteristic(characteristicRx);
    }

private static IntentFilter makeGattUpdateIntentFilter() {
    final IntentFilter intentFilter = new IntentFilter();

    intentFilter.addAction(RBLService.ACTION_GATT_CONNECTED);
    intentFilter.addAction(RBLService.ACTION_GATT_DISCONNECTED);
    intentFilter.addAction(RBLService.ACTION_GATT_SERVICES_DISCOVERED);
    intentFilter.addAction(RBLService.ACTION_DATA_AVAILABLE);

    return intentFilter;
    }
}

而 RBLservice.java 是:

/**
* Service for managing connection and data communication with a GATT server
* hosted on a given Bluetooth LE device.
*/
public class RBLService extends Service {
private final static String TAG = RBLService.class.getSimpleName();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
private BluetoothGatt mBluetoothGatt;

public final static String ACTION_GATT_CONNECTED = "ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED = "ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED = "ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_GATT_RSSI = "ACTION_GATT_RSSI";
public final static String ACTION_DATA_AVAILABLE = "ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA = "EXTRA_DATA";

public final static UUID UUID_BLE_SHIELD_TX = UUID
        .fromString(RBLGattAttributes.BLE_SHIELD_TX);
public final static UUID UUID_BLE_SHIELD_RX = UUID
        .fromString(RBLGattAttributes.BLE_SHIELD_RX);
public final static UUID UUID_BLE_SHIELD_SERVICE = UUID
        .fromString(RBLGattAttributes.BLE_SHIELD_SERVICE);

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                        int newState) {
        String intentAction;

        if (newState == BluetoothProfile.STATE_CONNECTED) {
            intentAction = ACTION_GATT_CONNECTED;
            broadcastUpdate(intentAction);
            Log.i(TAG, "Connected to GATT server.");
            // Attempts to discover services after successful connection.
            Log.i(TAG, "Attempting to start service discovery:"
                    + mBluetoothGatt.discoverServices());
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            intentAction = ACTION_GATT_DISCONNECTED;
            Log.i(TAG, "Disconnected from GATT server.");
            broadcastUpdate(intentAction);
        }
    }

    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
        if (status == BluetoothGatt.GATT_SUCCESS) {
            broadcastUpdate(ACTION_GATT_RSSI, rssi);
        } else {
            Log.w(TAG, "onReadRemoteRssi received: " + status);
        }
    }

    ;

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        if (status == BluetoothGatt.GATT_SUCCESS) {
            broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
        } else {
            Log.w(TAG, "onServicesDiscovered received: " + status);
        }
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt,
                                     BluetoothGattCharacteristic characteristic, int status) {
        if (status == BluetoothGatt.GATT_SUCCESS) {
            broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
        }
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt,
                                        BluetoothGattCharacteristic characteristic) {
        broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
    }
};

private void broadcastUpdate(final String action) {
    final Intent intent = new Intent(action);
    sendBroadcast(intent);
}

private void broadcastUpdate(final String action, int rssi) {
    final Intent intent = new Intent(action);
    intent.putExtra(EXTRA_DATA, String.valueOf(rssi));
    sendBroadcast(intent);
}

private void broadcastUpdate(final String action,
                             final BluetoothGattCharacteristic characteristic) {
    final Intent intent = new Intent(action);

    // This is special handling for the Heart Rate Measurement profile. Data
    // parsing is
    // carried out as per profile specifications:
    // http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
    if (UUID_BLE_SHIELD_RX.equals(characteristic.getUuid())) {
        final byte[] rx = characteristic.getValue();
        intent.putExtra(EXTRA_DATA, rx);
    }

    sendBroadcast(intent);
}

public class LocalBinder extends Binder {
    RBLService getService() {
        return RBLService.this;
    }
}

@Override
public IBinder onBind(Intent intent) {
    return mBinder;
}

@Override
public boolean onUnbind(Intent intent) {
    // After using a given device, you should make sure that
    // BluetoothGatt.close() is called
    // such that resources are cleaned up properly. In this particular
    // example, close() is
    // invoked when the UI is disconnected from the Service.
    close();
    return super.onUnbind(intent);
}

private final IBinder mBinder = new LocalBinder();

/**
 * Initializes a reference to the local Bluetooth adapter.
 *
 * @return Return true if the initialization is successful.
 */
public boolean initialize() {
    // For API level 18 and above, get a reference to BluetoothAdapter
    // through
    // BluetoothManager.
    if (mBluetoothManager == null) {
        mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        if (mBluetoothManager == null) {
            Log.e(TAG, "Unable to initialize BluetoothManager.");
            return false;
        }
    }

    mBluetoothAdapter = mBluetoothManager.getAdapter();
    if (mBluetoothAdapter == null) {
        Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
        return false;
    }

    return true;
}

/**
 * Connects to the GATT server hosted on the Bluetooth LE device.
 *
 * @param address The device address of the destination device.
 * @return Return true if the connection is initiated successfully. The
 * connection result is reported asynchronously through the
 * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
 * callback.
 */
public boolean connect(final String address) {
    if (mBluetoothAdapter == null || address == null) {
        Log.w(TAG,
                "BluetoothAdapter not initialized or unspecified address.");
        return false;
    }

    // Previously connected device. Try to reconnect.
    if (mBluetoothDeviceAddress != null
            && address.equals(mBluetoothDeviceAddress)
            && mBluetoothGatt != null) {
        Log.d(TAG,
                "Trying to use an existing mBluetoothGatt for connection.");
        if (mBluetoothGatt.connect()) {
            return true;
        } else {
            return false;
        }
    }

    final BluetoothDevice device = mBluetoothAdapter
            .getRemoteDevice(address);
    if (device == null) {
        Log.w(TAG, "Device not found.  Unable to connect.");
        return false;
    }
    // We want to directly connect to the device, so we are setting the
    // autoConnect
    // parameter to false.
    mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
    Log.d(TAG, "Trying to create a new connection.");
    mBluetoothDeviceAddress = address;

    return true;
}

/**
 * Disconnects an existing connection or cancel a pending connection. The
 * disconnection result is reported asynchronously through the
 * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
 * callback.
 */
public void disconnect() {
    if (mBluetoothAdapter == null || mBluetoothGatt == null) {
        Log.w(TAG, "BluetoothAdapter not initialized");
        return;
    }
    mBluetoothGatt.disconnect();
}

/**
 * After using a given BLE device, the app must call this method to ensure
 * resources are released properly.
 */
public void close() {
    if (mBluetoothGatt == null) {
        return;
    }
    mBluetoothGatt.close();
    mBluetoothGatt = null;
}

/**
 * Request a read on a given {@code BluetoothGattCharacteristic}. The read
 * result is reported asynchronously through the
 * {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
 * callback.
 *
 * @param characteristic The characteristic to read from.
 */
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
    if (mBluetoothAdapter == null || mBluetoothGatt == null) {
        Log.w(TAG, "BluetoothAdapter not initialized");
        return;
    }

    mBluetoothGatt.readCharacteristic(characteristic);
}

public void readRssi() {
    if (mBluetoothAdapter == null || mBluetoothGatt == null) {
        Log.w(TAG, "BluetoothAdapter not initialized");
        return;
    }

    mBluetoothGatt.readRemoteRssi();
}

public void writeCharacteristic(BluetoothGattCharacteristic characteristic) {
    if (mBluetoothAdapter == null || mBluetoothGatt == null) {
        Log.w(TAG, "BluetoothAdapter not initialized");
        return;
    }

    mBluetoothGatt.writeCharacteristic(characteristic);
}

/**
 * Enables or disables notification on a give characteristic.
 *
 * @param characteristic Characteristic to act on.
 * @param enabled        If true, enable notification. False otherwise.
 */
public void setCharacteristicNotification(
        BluetoothGattCharacteristic characteristic, boolean enabled) {
    if (mBluetoothAdapter == null || mBluetoothGatt == null) {
        Log.w(TAG, "BluetoothAdapter not initialized");
        return;
    }
    mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

    if (UUID_BLE_SHIELD_RX.equals(characteristic.getUuid())) {
        BluetoothGattDescriptor descriptor = characteristic
                .getDescriptor(UUID
                        .fromString(RBLGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
        descriptor
                .setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        mBluetoothGatt.writeDescriptor(descriptor);
    }
}

/**
 * Retrieves a list of supported GATT services on the connected device. This
 * should be invoked only after {@code BluetoothGatt#discoverServices()}
 * completes successfully.
 *
 * @return A {@code List} of supported services.
 */
public BluetoothGattService getSupportedGattService() {
    if (mBluetoothGatt == null)
        return null;

    return mBluetoothGatt.getService(UUID_BLE_SHIELD_SERVICE);
}

}


更新后的问题如下:

在 greeble31 的(非常感谢)帮助和以下代码的帮助下,当调用 displayData 时,我已经设法按照我的需要获取了最后一次已知位置。我现在想做的是获取当前位置(我猜是使用 requestLocationUpdate),但不是每次位置更改时都触发操作。如何更新几秒钟以获得正确的 GPS 定位,并在获取经纬度后停止位置更新以节省电池电量,直到下一次调用 displayData 到来?

这是现在可以运行的代码部分,我需要更改,但我无法理解要放置什么侦听器,而且我总是出错,所以请帮助我:

private void displayData(final byte[] byteArray) {
    try {

        mFusedLocationClient.getLastLocation()
                .addOnSuccessListener(this, new OnSuccessListener<Location>() {
                    @Override
                    public void onSuccess(Location location) {
                        // Got last known location. In some rare situations this can be null.

                        if (byteArray != null) {
                            String data = new String(byteArray);
                            tv.setText(n/2 + " measurements since startup...");
                            n += 1;

                            if (location != null) {
                                double lat = location.getLatitude();
                                double lng = location.getLongitude();
                                latitude = String.valueOf(lat);
                                longitude = String.valueOf(lng);
                            }

                            try
                            {
                                FileWriter fw = new FileWriter(textfile,true); //the true will append the new data
                                if (writeDate()) {
                                    fw.write("\n");
                                    fw.write(stringDate);
                                    fw.write(data); //appends the string to the file
                                }
                                else {
                                    fw.write(data); //appends the string to the file
                                    fw.write(" - ");
                                    fw.write(latitude);
                                    fw.write(",");
                                    fw.write(longitude);
                                }
                                fw.close();
                            }
                            catch(IOException ioe)
                            {
                                System.err.println("IOException: " + ioe.getMessage());
                            }


                            // find the amount we need to scroll. This works by
                            // asking the TextView's internal layout for the position
                            // of the final line and then subtracting the TextView's height
                            final int scrollAmount = tv.getLayout().getLineTop(
                                    tv.getLineCount())
                                    - tv.getHeight();
                            // if there is no need to scroll, scrollAmount will be <=0
                            if (scrollAmount > 0)
                                tv.scrollTo(0, scrollAmount);
                            else
                                tv.scrollTo(0, 0);
                        }
                    }
                });

    } catch (SecurityException e) {
        // lets the user know there is a problem with the gps
    }
}

所以,这里有几件事:

  1. 仅请求 ACCESS_FINE_LOCATION 权限并不能保证 FusedLocationProvider 正在使用 GPS。由于您只调用 getLastLocation(),它只会为您提供最后的定位信息,无论那是很久以前的事了。该操作本身不会打开 GPS 或任何东西。即使是这样,GPS 也可能需要几秒钟到 "warm up" 才能得到修复。我不确定它缓存最后一个 GPS 定位多长时间,或者为什么当你有 ACCESS_FINE_LOCATION 时它至少不给你一个网络定位。你试过ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION吗?
  2. 注意getLastLocation()returns一个Task。这是该操作是异步操作的线索。也就是说,onSuccess()实际上在getLocation()之后执行了,而确实displayData()已经返回了,在实践中。为什么?因为 getLastLocation() 必须通过 IPC 与 Google Play 服务(托管 FusedLocationProvider)对话才能得到答案。这需要时间,尽管只有几毫秒。因此,它不会阻塞直到得到答案,而是给你一个 Task 并承诺在将来的某个时间调用 onSuccess()。这就是为什么您的 lat/long 出现在结尾而不是开头的原因。

我怀疑如果您始终保持 GPS 运行 会获得更好的结果——例如,每当您的 activity 出现在屏幕上时——通过使用 requestLocationUpdates() 或其他一些API 打开 GPS。

您的 Task 问题可以通过将大部分 displayData() 逻辑移动到 onSuccess() 来解决。换句话说,你等到 之后你有 lat/long,然后才开始将 ByteArray 数据附加到 TextView