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
}
}
所以,这里有几件事:
- 仅请求 ACCESS_FINE_LOCATION 权限并不能保证
FusedLocationProvider
正在使用 GPS。由于您只调用 getLastLocation()
,它只会为您提供最后的定位信息,无论那是很久以前的事了。该操作本身不会打开 GPS 或任何东西。即使是这样,GPS 也可能需要几秒钟到 "warm up" 才能得到修复。我不确定它缓存最后一个 GPS 定位多长时间,或者为什么当你有 ACCESS_FINE_LOCATION 时它至少不给你一个网络定位。你试过ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION吗?
- 注意
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
。
我希望我的应用程序在从蓝牙 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
}
}
所以,这里有几件事:
- 仅请求 ACCESS_FINE_LOCATION 权限并不能保证
FusedLocationProvider
正在使用 GPS。由于您只调用getLastLocation()
,它只会为您提供最后的定位信息,无论那是很久以前的事了。该操作本身不会打开 GPS 或任何东西。即使是这样,GPS 也可能需要几秒钟到 "warm up" 才能得到修复。我不确定它缓存最后一个 GPS 定位多长时间,或者为什么当你有 ACCESS_FINE_LOCATION 时它至少不给你一个网络定位。你试过ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION吗? - 注意
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
。