Hola,
les comento gente estoy haciendo un controlador BLE (Bluetooth low energy). Estoy montando una arquitectura separando el controlador BLE de la GUI y demás pero tengo un error que aunque el programa funciona me gustaría corregirlo.
Os explico:
Tengo una clase para manejar los dispositivos BLE:
package pfc.teleco.upct.es.iot_ble.BLE;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter.LeScanCallback;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import pfc.teleco.upct.es.iot_ble.BEAN.BeanBluetoothDevice;
import pfc.teleco.upct.es.iot_ble.Constant;
import pfc.teleco.upct.es.iot_ble.DEVICES.Device;
public class HandlerBLE implements LeScanCallback
{
public static final String ACTION_DEVICE_CONNECTED = "pfc.teleco.upct.es.iot_ble.DEVICE_FOUND";
private static HandlerBLE mHandlerBLE;
private static Context mContext;
private static BluetoothDevice mDevice;
private static String mDeviceAddress;
private static BluetoothGatt mGatt;
private static BluetoothAdapter mBlueAdapter = null;
public static boolean isScanning = false;
//###################################################################
/****************** Constructor **********************/
//###################################################################
public HandlerBLE(Context context)
{
mContext = context;
mDeviceAddress= null;
}
//###################################################################
/********************* statics methods **************************/
//###################################################################
public static HandlerBLE getInstance(Context context) {
if(mHandlerBLE==null){
mHandlerBLE = new HandlerBLE(context);
setup();
}
return mHandlerBLE;
}
public static void resetHandlerBLE()
{
mDeviceAddress=null;
disconnect();
mGatt=null;
}
public static void setup()
{
BluetoothManager manager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
mBlueAdapter = manager.getAdapter();
}
//###################################################################
/********************* methods bluetooth *************/
//###################################################################
public void setDeviceAddress(String address) {
mDeviceAddress=address;
}
public String getDeviceAddress() {
return mDeviceAddress;
}
public void startLeScan()
{
try
{
mBlueAdapter.startLeScan(this);//deprecated
isScanning = true;
//mBlueAdapter.startDiscovery();
}
catch (Exception e)
{
Log.i(Constant.TAG,"(HandlerBLE)[Error]:"+e.getStackTrace()+" "+e.getCause()+" "+e.getMessage()+
" "+e.getLocalizedMessage());
}
}
public void stopLeScan()
{
try
{
mBlueAdapter.stopLeScan(this); //deprecated
//mBlueAdapter.startDiscovery();
isScanning = false;
}
catch(Exception e)
{
Log.i(Constant.TAG,"(HandlerBLE)[Error]:"+e.getStackTrace()+" "+e.getCause()+" "+e.getMessage()+
" "+e.getLocalizedMessage());
}
}
/*
public void connect() {
mDevice = mBlueAdapter.getRemoteDevice(mDeviceAddress);
mServices.clear();
if(mGatt!=null){
mGatt.connect();
}else{
mDevice.connectGatt(mContext, false, mCallBack);
}
}
*/
public void discoverServices() {
if (Constant.DEBUG)
Log.i(Constant.TAG, "(HandlerBLE)Scanning services and caracteristics");
mGatt.discoverServices();
}
public static void disconnect(){
if (mGatt!=null) {
try{
mGatt.disconnect();
mGatt.close();
if (Constant.DEBUG)
Log.i(Constant.TAG, "(HandlerBLE)Disconnecting GATT");
} catch(Exception ex){};
}
mGatt = null;
}
public boolean isConnected(){
return (mGatt!=null);
}
//###################################################################
/********************* methods Scan bluetooth *************/
//###################################################################
/*
* this method is used to receive devices which were found durind a scan*/
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord)
{
if(Constant.DEBUG)
Log.i(Constant.TAG,"(HandlerBLE) -- onLeScan -> throwing information to the listener.");
//create the packet wich will be sent to listener.
Intent intent = new Intent();
intent.setAction(HandlerBLE.ACTION_DEVICE_CONNECTED);
BeanBluetoothDevice beanBlue = new BeanBluetoothDevice();
beanBlue.setBluetoothDevice(device);
beanBlue.setmRssi(rssi);
beanBlue.setmScanRecord(scanRecord);
intent.putExtra(Constant.EXTRA_BEAN_BLUETOOTHDEVICE,beanBlue);
mContext.sendBroadcast(intent);
}
}
Esta clase se encargará de controlar el BLE sanea, conexión y comunicación con otros dispositivos. Y a su vez se comunicará con el resto de la aplicación con Broadcastreceiver , es decir en el método implementado en la interfaz onLeScan, me avisa cuando se ha encontrado un dispositivo y envia la señal broadcast.
Como se puede ver tengo un objeto llamado BeanBluetoothDevice este simplemente es una encapsulación de la información que necesito y serializado.
public class BeanBluetoothDevice implements Parcelable
{
private BluetoothDevice mdevice;
private int mRssi;
private byte[] mScanRecord;
public BeanBluetoothDevice() {
super();
}
//###################################################################
protected BeanBluetoothDevice(Parcel in) {
mdevice = in.readParcelable(BluetoothDevice.class.getClassLoader());
mRssi = in.readInt();
mScanRecord = in.createByteArray();
}
public static final Creator<BeanBluetoothDevice> CREATOR = new Creator<BeanBluetoothDevice>() {
@Override
public BeanBluetoothDevice createFromParcel(Parcel in) {
return new BeanBluetoothDevice(in);
}
@Override
public BeanBluetoothDevice[] newArray(int size) {
return new BeanBluetoothDevice[size];
}
};
//###################################################################
/****************** gets and sets methods **********************/
//###################################################################
public void setBluetoothDevice(BluetoothDevice device)
{mdevice = device;}
public BluetoothDevice getBluetoothDevice()
{return mdevice;}
public int getmRssi() {
return mRssi;
}
public void setmRssi(int mRssi) {
this.mRssi = mRssi;
}
public byte[] getmScanRecord() {
return mScanRecord;
}
public void setmScanRecord(byte[] mScanRecord) {
this.mScanRecord = mScanRecord;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mdevice, flags);
dest.writeInt(mRssi);
dest.writeByteArray(mScanRecord);
}
}
una vez e envia la señal yo la recojo en la activity que me interesa, en mi casa es la principal para mostrar los dispositivos.
public class ScanActivity extends ListActivity implements OnItemClickListener{ //}, LeScanCallback {
private static final int SCAN_ITEM = 1;
private static ListView mListView;
private static Context mContext;
private static HandlerBLE mHandlerBLE;
private Handler mHandler;
private static MySimpleArrayAdapter mAdapter;
private static List<BluetoothDevice> mDeviceList;
private Menu mMenu;
private Activity mActivity;
private BLEBroadcastReceiver mScanBroadcastReceiver;
//###################################################################
/****************** metodos del flujo Android. **********************/
//###################################################################
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scan);
mActivity = this;
mContext = this;
mHandler = new Handler() ;
mDeviceList = new ArrayList<BluetoothDevice>();
mAdapter = new MySimpleArrayAdapter(mContext, mDeviceList);
mScanBroadcastReceiver = new BLEBroadcastReceiver(this,mAdapter);
IntentFilter i = new IntentFilter(HandlerBLE.ACTION_DEVICE_CONNECTED);
registerReceiver(mScanBroadcastReceiver,i);
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, "BLE TECHNOLOGY NOT SUPPORTED ON THIS DEVICE", Toast.LENGTH_SHORT).show();
finish();
}
//run service
Intent service = new Intent(this, ServiceDetectionTag.class);
startService(service);
//manejador BLE
mHandlerBLE = ((BLE_Application) getApplication()).getmHandlerBLEInstance(this);
((BLE_Application) getApplication()).resetHandlerBLE();
mListView = getListView();
mListView.setVisibility(View.VISIBLE);
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
super.onCreateOptionsMenu(menu);
mMenu = menu;
String menuTitle= getResources().getString(R.string.scan);
menu.add(0,SCAN_ITEM,0,menuTitle);
/*
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_scan, menu);*/
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId()){
case SCAN_ITEM:
scan();
break;
}
return true;
/*
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);*/
}
@Override
protected void onResume()
{
super.onResume();
//mAdapter.clear();
HandlerBLE.setup();
}
@Override
protected void onPause() {
super.onStop();
//Make sure that there is no pending Callback
mHandler.removeCallbacks(mStopRunnable);
//stop service
Intent service = new Intent(this, ServiceDetectionTag.class);
stopService(service);
//mAdapter.clear();
if (mHandlerBLE.isScanning)
{
mHandlerBLE.stopLeScan();
unregisterReceiver(mScanBroadcastReceiver);
}
}
@Override
protected void onStop()
{
super.onStop();
}
//###################################################################
/****************** metodos manejo Tag **********************/
//###################################################################
@Override
//recogemos los metodos del tag seleccionado y recogemos los datos.
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
if (Constant.DEBUG)
Log.i(Constant.TAG, "Selected device " + mDeviceList.get(position).getAddress());
if (mHandlerBLE.isScanning)
{ //stop scanning
configureScan(false);
mHandlerBLE.stopLeScan();
if (Constant.DEBUG)
Log.i(Constant.TAG, "Stop scanning");
}
String address = mDeviceList.get(position).getAddress();
String name = mDeviceList.get(position).getName();
if (name==null)
name="unknown";
Intent intentActivity= new Intent(this, DeviceActivity.class);
intentActivity.putExtra(Constant.EXTRA_ADDRESS, address);
intentActivity.putExtra(Constant.EXTRA_NAME, name);
this.startActivity(intentActivity);
if (Constant.DEBUG)
Log.i(Constant.TAG, "Trying to connect");
//mConnectionManager.connect(address,true);
Toast.makeText(this, "Wait for connection to selected device", Toast.LENGTH_LONG).show();
}
//Handle automatic stop of LEScan
private Runnable mStopRunnable = new Runnable() {
@Override
public void run() {
mHandlerBLE.stopLeScan();
configureScan(false);
if (Constant.DEBUG)
Log.i(Constant.TAG, "Stop scanning");
}
};
public void configureScan(boolean flag)
{
//isScanning = flag;
String itemText = null;
if (mHandlerBLE.isScanning)
{
itemText = getResources().getString(R.string.stopScan);
mHandlerBLE.stopLeScan();
if (Constant.DEBUG)
Log.i(Constant.TAG, "ScanActivity -- StopScan");
}
else
{
itemText= getResources().getString(R.string.scan);
mHandlerBLE.startLeScan();
if (Constant.DEBUG)
Log.i(Constant.TAG, "ScanActivity -- StartScan");
}
mMenu.findItem(SCAN_ITEM).setTitle(itemText);
if (Constant.DEBUG)
Log.i(Constant.TAG, "Updated Menu Item. New value: " + itemText);
}
// Metodo para iniciar el scaneo cuando te llaman manualmente.
private void scan() {
if (mHandlerBLE.isScanning) { //stop scanning
configureScan(false);
mHandlerBLE.stopLeScan();
if (Constant.DEBUG)
Log.i(Constant.TAG, "Stop scanning");
return;
} else {
mAdapter.clear();
mAdapter.notifyDataSetChanged();
configureScan(true);
if (Constant.DEBUG)
Log.i(Constant.TAG, "Start scanning for BLE devices...");
mHandlerBLE.startLeScan();
//automatically stop LE scan after 5 seconds
mHandler.postDelayed(mStopRunnable, 30000);
}
}
/*
Clase para crear el adaptador de dispositos Bluetooh
*/
public class MySimpleArrayAdapter extends ArrayAdapter<BluetoothDevice> {
private final Context context;
public MySimpleArrayAdapter(Context context, List<BluetoothDevice> deviceList)
{
super(context, R.layout.activity_scan_item,R.id.deviceName, deviceList);
this.context = context;
}
}
/*
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord)
{
String name="unknown";
if (device.getName()!=null)
name=device.getName();
final String finalName = name;
final String finalAddress = device.getAddress();
if (Constant.DEBUG)
Log.i(Constant.TAG, "Found new device "+ finalAddress + " --- Name: " + finalName);
final BluetoothDevice finalDevice= device;
// This callback from Bluetooth LEScan can arrive at any moment not necessarily on UI thread.
// Use this mechanism to update list View
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.add(finalDevice);
mAdapter.notifyDataSetChanged();
if (Constant.DEBUG)
Log.i(Constant.TAG, "Added new device "+ finalAddress + " --- Name: " + finalName);
}
}
);
}*/
}
Como se puede ver en el método onCreate doy de alta la señal que espero recibir.
mScanBroadcastReceiver = new BLEBroadcastReceiver(this,mAdapter);
IntentFilter i = new IntentFilter(HandlerBLE.ACTION_DEVICE_CONNECTED);
registerReceiver(mScanBroadcastReceiver,i);
Ahora nos dirigimos en la clase donde creo que está el problema, es la clase donde se recibe la señal Broadcast y se procesa.
public class BLEBroadcastReceiver extends BroadcastReceiver
{
private Activity mActivity;
private ScanActivity.MySimpleArrayAdapter mAdapter;
public BLEBroadcastReceiver(Activity activity, ScanActivity.MySimpleArrayAdapter adapter)
{
super();
mAdapter = adapter;
mActivity = activity;
}
public BLEBroadcastReceiver()
{
super();
}
@Override
public void onReceive(Context context, Intent intent)
{
if(Constant.DEBUG)
Log.i(Constant.TAG, "ScanActivity -- OnReceive() -> BroadcastReceiver new device found.");
//get signal and add new device into MyarrayAdapter
if(intent.getAction().equals(HandlerBLE.ACTION_DEVICE_CONNECTED))
{
try
{
BeanBluetoothDevice beanDeviceFound = intent.getExtras().getParcelable(Constant.EXTRA_BEAN_BLUETOOTHDEVICE);
final BluetoothDevice deviceFound = beanDeviceFound.getBluetoothDevice();
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.add(deviceFound);
mAdapter.notifyDataSetChanged();
if (Constant.DEBUG)
Log.i(Constant.TAG, "Added new device " + deviceFound.getAddress() + " --- Name: " + deviceFound.getName());
}
});
}catch(Exception e)
{
Log.i(Constant.TAG,"[Error(BLEBroadcastReceiver)]: "+e.getCause()+"\n"+e.getStackTrace()+"\n"+e.getLocalizedMessage());
}
}
}
}
aquí el problema me aparice cuando quito el constructor vacío, es decir, al que no se le pasan parámetros. Si lo eliminamos en tiempo de ejecución el programa peta y cuando se lo pongo todo corre normalmente, es decir, si me introduce el dispositivo en el arrayList pero a su vez me da una excepción de los objetos mAdapter y mActivity.
¿A qué puede deberse esto? Si al final lo introduce correctamente.
PD: soy un patata programando, no ser muy duros conmigo.
Has dado demasiado info de cómo funciona, pero no de la excepción objeto de este tema. Pega el rastreo de pila para analizarlo. Otra cosa, ¿la clase que hereda de BroadcastReceiver es interna? Si es así, necesitas hacerla estática. Una clase interna es asociada a una instancia de la clase externa.
Tienes toda la razón. Me explico mejor o trato de hacerlo.
El error, basicamente, es que me salta la excepción
catch(Exception e)
{
Log.i(Constant.TAG,"[Error(BLEBroadcastReceiver)]: "+e.getCause()+"\n"+e.getStackTrace()+"\n"+e.getLocalizedMessage());
}
de la clase BLEBroadcastReceiver, la cual es publica y no pertenece al mismo paquete que la activity, la activity está en el paquete GUI.
El error parece debido a la clase Parcelable de la serialización pero no lo que entiendo es a que se debe el fallo.
07-27 17:18:26.156 8954-9111/pfc.teleco.upct.es.iot_ble I/IoT-APP﹕ ServiceDetectionTag Start scanning
07-27 17:18:41.156 8954-9111/pfc.teleco.upct.es.iot_ble I/IoT-APP﹕ ServiceDetectionTag Stop scanning
07-27 17:18:53.537 8954-8954/pfc.teleco.upct.es.iot_ble I/IoT-APP﹕ BLEBroadcastReceiver -- OnReceive() -> Added new device EC:4A:C2:B4:E3:B4 --- Name: I'm Flowmeter!!
07-27 17:18:56.156 8954-9111/pfc.teleco.upct.es.iot_ble I/IoT-APP﹕ ServiceDetectionTag Start scanning
07-27 17:18:57.287 8954-8971/pfc.teleco.upct.es.iot_ble I/IoT-APP﹕ (HandlerBLE) -- onLeScan -> throwing information to the listener.
07-27 17:19:07.781 8954-8954/pfc.teleco.upct.es.iot_ble I/IoT-APP﹕ BLEBroadcastReceiver -- OnReceive() -> BroadcastReceiver new device found.
07-27 17:19:09.139 8954-8954/pfc.teleco.upct.es.iot_ble I/IoT-APP﹕ [Error(BLEBroadcastReceiver)]:
Message: null
Cause:null
StackTrace[Ljava.lang.StackTraceElement;@41d76bb0
null
y este es el error que obtengo
java.lang.SecurityException: need INSTALL_LOCATION_PROVIDER permission, or UID of a currently bound location provider
at android.os.Parcel.readException(Parcel.java:1465)
at android.os.Parcel.readException(Parcel.java:1419)
at android.location.ILocationManager$Stub$Proxy.reportLocation(ILocationManager.java:1122)
at com.android.location.provider.LocationProviderBase.reportLocation(LocationProviderBase.java:137)
at com.google.android.location.network.NetworkLocationService.onHandleIntent(SourceFile:99)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loop(Looper.java:193)
at android.os.HandlerThread.run(HandlerThread.java:61)
A mí me suena más a un bug con el tipo de dispositivo. Lo que no entiendo, es por qué cuando especificas un constructor vacío no sucede. De alguna manera, ese super() hace algo en el constructor de BroadcasterReceiver o el runtime instancia internamente a las subclases de BroadcasterReceiver y al no encontrar un constructor vacío, lanza excepción. Es raro, trata de reportarlo como bug a ver qué te dicen xD.
ok muchísimas gracias. Yo pensaba que estaba haciendo un uso indebido del BroadcasterReceiver.