gatt.writeCharacteristic() 可能返回 false 的原因是啥?

Posted

技术标签:

【中文标题】gatt.writeCharacteristic() 可能返回 false 的原因是啥?【英文标题】:What are the reasons why gatt.writeCharacteristic () could return false?gatt.writeCharacteristic() 可能返回 false 的原因是什么? 【发布时间】:2018-02-22 09:48:02 【问题描述】:

我正在开发一个 android BLE 应用程序。在某些 Android 设备中,我面对的是,当我使用 writeCharacteristic() 方法在特征中写入数据时,它返回 false 并且不写入。 你知道什么情况下它会返回 false 吗? 使用我的 BQ Aquaris U Plus,它不会发生,只是写入成功并返回 true。但是对于我的华为 Y6,它发生了很多次。也许 7/10 的情况下它返回错误。 这是我的代码,很大但可以提供帮助:

public class ServicioFirmadoNuevo extends Service

private static final long NUMEROS_DE_RETRYS = Constantes.NUMEROS_DE_RETRYS;
private ServiceConnection serviceConn;
private ArrayList<byte[]> arrayDatosPartidos;
private Handler handler;
private BluetoothGatt mGatt;
private final IBinder mBinder = new ServicioFirmadoNuevo.LocalBinder();
private android.bluetooth.BluetoothDevice mBluetoothDevice;
private byte[] byteFinal;
private ArrayList<byte[]> datosAJuntar;
private int numeroDeParticionesDeDatos;
private boolean esperandoDesconexion =  false;
private boolean protocoloLectura = false;
private boolean comunicacionTerminada = false;
private boolean retry = true;
private int contadorRetrys = 0;
private int iteracionesLectura = 0;
private String idMotivo;
private int vecesOnChanged = 0;
private int nSerie;

private Intent intentError = new Intent("errorFirmado");
public ServicioFirmadoNuevo()

@Override
public int onStartCommand(Intent intent, int flags, int startId) 


    return START_STICKY;


public void onStartCommand2(Intent intent, BluetoothDevice device, String motivoCodigo, String motivoLetra, int nserie) 

    Log.i("INICiosERVICIOFIRMADO", "TRUE");
    mGatt = null;
    handler = new Handler();
    datosAJuntar = new ArrayList<byte[]>();
    contadorRetrys = 0;
    Log.d("pruebitas", motivoCodigo+motivoLetra);
    mBluetoothDevice = device;
    nSerie = nserie;
    Log.d("NSERIEAFIRMAR", nserie+"");


    /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 
        if (device.getBondState() == BluetoothDevice.BOND_NONE)
            Log.d("BOUNDING??? ", device.createBond()+"");
        
    
    final String motCod = motivoCodigo;
    new android.os.Handler().postDelayed(
            new Runnable() 
                public void run() 
                    conectaConEsteMotivo(motCod);
                
            ,
            4000);*/
    conectaConEsteMotivo(motivoCodigo);


public void setServiceConn(ServiceConnection serviceConn) 
    this.serviceConn = serviceConn;

public class LocalBinder extends Binder 
    public ServicioFirmadoNuevo getServiceInstance()

        return ServicioFirmadoNuevo.this;

    

public void conectaConEsteMotivo(String idmotivo)

    handler.postDelayed(timeOutFirmado, 10000);
    handler.postDelayed(runnableCodeTimeOutConversacion, 9000);
    this.idMotivo = idmotivo;

    if (mGatt == null) 
        Log.e("mGATT", "IS NULL");
        try 

            Handler mHandler = new Handler(getApplicationContext().getMainLooper());
            mHandler.post(new Runnable() 

                @Override
                public void run() 

                    Log.e("TRY", "1");
                    if (mBluetoothDevice!=null)

                        /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 
                            Log.d("BOUNDING??? ", mBluetoothDevice.createBond()+"");
                        */
                        Log.d("mBluetoothDevice", "mBluetoothDevice.connectGatt called...");
                        mGatt = mBluetoothDevice.connectGatt(getApplicationContext(), false, gattCallback);

                        /*new Handler().postDelayed(
                                new Runnable() 
                                    public void run() 
                                        Log.d("mBluetoothDevice", "mBluetoothDevice.connectGatt called...");
                                        mGatt = mBluetoothDevice.connectGatt(getApplicationContext(), false, gattCallback);
                                    
                                ,
                                1000);*/

                    

                

            );



        catch (Throwable e)

            e.printStackTrace();

            if (mGatt != null)

                mGatt.disconnect();
                mGatt.close();

            

            if (!retry)

                Log.e("ERROR","ERROR");
                //presenter.escribirEnToast("Problema Null Pointer en método : conectaConEsteMotivo(String a) ");
                //presenter.pararServicioFirmado();

            

        

     else 

        Log.i("MGATTISNOTNULL","MGATT NO ES NULL");

    


private Runnable runnableCodeTimeOutConversacion = new Runnable() 

    @Override
    public void run() 

        //contadorRetrys = (int) NUMEROS_DE_RETRYS;

        if (!esperandoDesconexion)

            Log.e("TIMEOUTCONVERSACION", "TRUE");

            if(mGatt != null)

                mGatt.disconnect();
                mGatt.close();

            

            //presenter.escribirEnToast("TIMEOUT Conversación");
            //presenter.setServicioEnEjecucion(false);
            //presenter.pararServicioFirmado();
            retrySameMotivo();

        

    
;
private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() 

    @Override
    public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) 

        Log.i("onConnectionStateChange", "Status: " + status);

        switch (newState) 

            case BluetoothProfile.STATE_CONNECTED:

                Log.i("gattCallback", "STATE_CONNECTED");

                boolean discover_services = gatt.discoverServices();

                if (discover_services) 

                    Log.i("Hadescubiertoservicios", "TRUE");

                 else 

                    Log.i("Hadescubiertoservicios", "FALSE");

                

                break;

            case BluetoothProfile.STATE_DISCONNECTED:

                Log.e("gattCallback", "STATE_DISCONNECTED STATUS: "+ status);
                //gatt.disconnect();
                //gatt.close();
                //mGatt.disconnect();
                //mGatt.close();

                if(esperandoDesconexion)

                    if (mGatt != null)

                        mGatt.disconnect();
                        mGatt.close();

                    

                    //presenter.setServicioEnEjecucion(false);
                    //presenter.pararServicioYReiniciar();
                    sendBroadcast(intentError);

                

                else 

                    if (idMotivo == null)

                        Log.e("ERRORIDMOTIVONULL", " IDMOTIVO ES NULL");
                        gatt.disconnect();
                        gatt.close();

                        if (mGatt != null)

                            mGatt.disconnect();
                            mGatt.close();

                        

                        //presenter.escribirEnToast("IDMOTIVO ES NULL");
                        esperandoDesconexion = true;
                        sendBroadcast(intentError);


                    else 

                        if(contadorRetrys < NUMEROS_DE_RETRYS )

                            retrySameMotivo();

                        else 

                            Log.e("LimiteRetrysSuperado", " Ya se han hecho suficientes retrys y no se ha logrado firmar");

                            gatt.disconnect();
                            gatt.close();

                            if (mGatt != null)

                                mGatt.disconnect();
                                mGatt.close();

                            

                            Log.e("retrys", "Límite Retrys alcanzado.");
                            //presenter.setServicioEnEjecucion(false);
                            //presenter.pararServicioYReiniciar();
                            esperandoDesconexion = true;
                            sendBroadcast(intentError);

                        

                    

                

                break;

            default:

                Log.e("gattCallback", "STATE_OTHER");

        

    


    @Override
    public void onServicesDiscovered(final BluetoothGatt gatt, int status) 

        Log.i("onServicesDiscovered", "enter");


        List<BluetoothGattService> services = gatt.getServices();

        if (services.size() == 0) 

            Log.i("ServiciosDeLaBaliza=0","el sice de los servicios entontrados es = 0    :"+ services.toString());

        

        Log.i("onServicesDiscovered", services.toString());

        //Muestreo de Servicios con sus respectivas Characteristics
        for (BluetoothGattService servicio : services) 

            Log.i("Service", servicio.getUuid().toString());

            for (BluetoothGattCharacteristic characteristic : servicio.getCharacteristics()) 

                Log.i("Char", characteristic.getUuid().toString());

            

        

        Log.d("PASAMITADonSERVICEDISCD","-----");

        try 

            BluetoothGattCharacteristic characteristic = gatt.getServices().get(2).getCharacteristics().get(0);//todo: CAMBIAR get 2 por su uuid
            Log.d("CHARACTERISTIC -->", characteristic!= null ? "NO ES NULL": "ES NULL");
            //habilitar notificaciones
            gatt.setCharacteristicNotification(characteristic, true);
            BluetoothGattDescriptor desc = characteristic.getDescriptors().get(0);
            desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            gatt.writeDescriptor(desc);

        catch (Exception e)

            sendBroadcast(intentError);
            e.printStackTrace();
            Log.e("ERRORDESCUBRIENDO", "TRUE");

        

    

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) 

        Log.e("ENTRA EN LEERCHAR", characteristic.getValue()[0]+"");
        protocolo(gatt, characteristic);

    

    //Cuando se escribe una caracteristica lanza esto
    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) 

        Log.e("ENTRAONWRITE", "STATUS: "+status);

        if (status == BluetoothGatt.GATT_SUCCESS) 

            handler.postDelayed(runnableCodeTimeOutTransmisiones, 1000);
            Log.i("ESCRIBIR","OK");
            Log.i("bytesescritos", characteristic.getValue().length+""+ "////////"+ new String(characteristic.getValue(), Charset.forName("UTF-8")));
            Log.i("1stEscrito", (char) characteristic.getValue()[0] + "");

            if(new String(characteristic.getValue(), Charset.forName("UTF-8")).equals(Constantes.PM_END_ACK+""))

                characteristic.setValue(Constantes.PM_END_COM+"");
                boolean porcentaje;
                porcentaje = gatt.writeCharacteristic(characteristic);
                Log.w("LOQUEESCRIBO: ", BateriaDeMetodosUtiles.bytesToHex(characteristic.getValue())+ " : "+porcentaje);
                Log.w("ESCPORCENT",  porcentaje+"");

            else 

                if (new String(characteristic.getValue(), Charset.forName("UTF-8")).equals(Constantes.PM_END_COM + "")) 

                    byteFinal = BateriaDeMetodosUtiles.juntarBytesDeUnArrayListDeBytes(datosAJuntar);
                    Log.e("FINALBYTE", new String(byteFinal, Charset.forName("UTF-8")));
                    Log.e("FINALBYTEHEX", BateriaDeMetodosUtiles.bytesToHex(byteFinal));
                    protocoloLectura = false;
                    iteracionesLectura = 0;
                    esperandoDesconexion = true;
                    Log.e("VamosASalirDelProcess", "Deberíamos salir de la conexión al haber escrito esto: " + new String(characteristic.getValue(), Charset.forName("UTF-8")));

                    Intent intentBD =  new Intent("fichajeCompletado");
                    Log.d("PRUEBABYTEEE", BateriaDeMetodosUtiles.bytesToHex(byteFinal));
                    intentBD.putExtra("byteFinal", byteFinal);
                    sendBroadcast(intentBD);
                    //presenter.pararServicioEscaneo();

                

            

            if (vecesOnChanged == 2)

                gatt.readCharacteristic(characteristic);

            

            vecesOnChanged = 0;

         else 

            Log.e("ESCRIBIRERROR","ERROR Lo que intentaste escribir era : "+characteristic.getValue() + "O en otras palabras: "+ BateriaDeMetodosUtiles.bytesToHex(characteristic.getValue())+" STATUS = "+status);
            gatt.disconnect();
            gatt.close();

            if (mGatt != null)

                mGatt.disconnect();
                mGatt.close();

            

            //presenter.escribirEnToast("ERROR ESCRIBIENDO");
            Log.e("ERROR","ESCRIBIENDO");
            sendBroadcast(intentError);

        

    

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) 

        Log.i("onDescriptorWrite","enter");

        byte[] paraLaBaliza ;//= BateriaDeMetodosUtiles.creacionDeRespuestaParaLaBaliza(idMotivo);

        //nuevoMETODOO//////////
        BaseDeDatos mBaseDeDatos = Room.databaseBuilder(getApplicationContext(), BaseDeDatos.class, Constantes.nombreBaseDeDatos).fallbackToDestructiveMigration().allowMainThreadQueries().build();
        ControlHoras controlHoras = mBaseDeDatos.controlHorasDao().seleccionarTodasControlHoras();
        paraLaBaliza = BateriaDeMetodosUtiles.creacionDeRespuestaParaBaliza2(idMotivo, controlHoras.getHoraPasada(), controlHoras.getHash(), controlHoras.getUpTimePasada(), nSerie);

        Log.d("QUEENVIOBALIZA", ""+idMotivo);
        //presenter.setFHDispositivo(BateriaDeMetodosUtiles.obtenerFH(paraLaBaliza)); LO HAGO CUANDO FICHO
        Log.e("DatosQueRecibeBaliza", BateriaDeMetodosUtiles.bytesToHex(paraLaBaliza));
        for (int i = 0; i < paraLaBaliza.length; i++)

            Log.d("paraLabaliza", i+ " "+paraLaBaliza[i]);

        
        ArrayList<byte[]> tmp = BateriaDeMetodosUtiles.particionDeBytes(paraLaBaliza);
        arrayDatosPartidos = BateriaDeMetodosUtiles.creacionBytesDelProtocoloTam20(tmp);
        Log.i("LENGTHESCRITURA", arrayDatosPartidos.get(0).length+"IS LEN");
        numeroDeParticionesDeDatos = arrayDatosPartidos.size();
        Log.i("DESCRIPTORWRITE","YES");
        descriptor.getCharacteristic().setValue(Constantes.PM_START+"");

        if ( gatt.writeCharacteristic( descriptor.getCharacteristic()) ) 

            Log.i("ESCRIBECHARINONDESCRIPT","TRUE");

         else 

            Log.i("ESCRIBECHARINONDESCRIPT","FALSE");

        

    

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) 
        Log.i("onCharacteristicChanger", "enter");

        vecesOnChanged++;
        char charrr = (char) characteristic.getValue()[0];
        Log.e("QUEESCHANGE1st", charrr+"");

        if (vecesOnChanged == 2 && charrr == '$')

            Log.e("RESTAONCHANGE","TRUE");
            vecesOnChanged --;

        

        byte[] valorCambiado = characteristic.getValue();
        Log.i("CHANGED", "TRUE");
        Log.i("itera", iteracionesLectura+"");
        Log.i("VALORCHANGED",new String(characteristic.getValue(), Charset.forName("UTF-8")));
        Log.i("VALORCHANGEDLEN",characteristic.getValue().length+"");

        if ((!protocoloLectura) || (protocoloLectura && (valorCambiado[0] != Constantes.PM_ACK))) 

            Log.d("C_PROTOCOLO_ON_CHANGED", "TRUE");
            protocolo(gatt, characteristic);

        

    
;

private void protocolo(final BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) 

    Log.w("ENTRAPROTOCOLO", "TRUE");
    try 

        handler.removeCallbacks(runnableCodeTimeOutTransmisiones);

    catch (NullPointerException e) 

        e.printStackTrace();

    

    byte[] valorLeido = characteristic.getValue();
    Log.i("Lectura: ", new String(characteristic.getValue(), Charset.forName("UTF-8")));
    String resultadoLegible = "";

    for (int i = 0; i < valorLeido.length; i++) 

        resultadoLegible += valorLeido[i] +"";
        //Log.i("Legible", resultadoLegible);

    

    String valorEnHex = BateriaDeMetodosUtiles.bytesToHex(valorLeido);
    Log.i("VALORREADED:",resultadoLegible);
    Log.i("VALORHEX", valorEnHex);
    Log.i("ValorString", new String (characteristic.getValue(),Charset.forName("UTF-8")));
    Log.i("VALORLENGTH:",""+characteristic.getValue().length);

    if (iteracionesLectura <= numeroDeParticionesDeDatos) // es menor o igual porque la primera vez lees solo para empezar la comunicacion, si no sería menor estrícto

        if(valorLeido[0] == Constantes.PM_ACK) 

            if (iteracionesLectura == numeroDeParticionesDeDatos) 

                Log.i("ESCRIBODESPEDIDA","TRUE");
                characteristic.setValue(Constantes.PM_END+"");

             else 

                Log.i("LENGTHESCRITURA", arrayDatosPartidos.get(iteracionesLectura).length+"IS LEN");
                characteristic.setValue(arrayDatosPartidos.get(iteracionesLectura));


            

            Log.e("LENGTHCHARESCRI", characteristic.getValue().length+"");
            boolean porcentaje;
            porcentaje = gatt.writeCharacteristic(characteristic);
            Log.w("LOQUEESCRIBO: ", BateriaDeMetodosUtiles.bytesToHex(characteristic.getValue())+ " : "+porcentaje);
            iteracionesLectura++;

        else 

            Log.e("ERRORHEADER","no me ha devuelto un +");
            //gatt.disconnect();
            //gatt.close();
            //mGatt = null;
            iteracionesLectura = 0;
            sendBroadcast(intentError);
        

     else 

        protocoloLectura = true;

        if(!comunicacionTerminada)

            if(valorLeido[0] != Constantes.PM_END_ACK)

                if (valorLeido[0] ==  Constantes.PM_START) 

                    characteristic.setValue(Constantes.PM_ACK+"");

                    boolean porcentaje;
                    porcentaje = gatt.writeCharacteristic(characteristic);
                    Log.w("LOQUEESCRIBO: ", BateriaDeMetodosUtiles.bytesToHex(characteristic.getValue())+ " : "+porcentaje);

                else if (valorLeido[0] == Constantes.PM_END)

                    characteristic.setValue(Constantes.PM_END_ACK+"");
                    boolean porcentaje;
                    porcentaje = gatt.writeCharacteristic(characteristic);
                    Log.w("LOQUEESCRIBO: ", BateriaDeMetodosUtiles.bytesToHex(characteristic.getValue())+ " : "+porcentaje);

                 else 

                    if (valorLeido[1] > 0 && valorLeido[1] <= 18) 

                        datosAJuntar.add(BateriaDeMetodosUtiles.addByteAlArrayFinal(valorLeido));//almaceno en un arrayList todos los datos que leo para después juntarlo.
                        characteristic.setValue(Constantes.PM_ACK+"");
                        boolean porcentaje;
                        porcentaje = gatt.writeCharacteristic(characteristic);
                        Log.w("LOQUEESCRIBO: ", BateriaDeMetodosUtiles.bytesToHex(characteristic.getValue())+ " : "+porcentaje);

                    

                

            

        else 

            //presenter.escribirEnToast("Valor leído en el Value de la Característica: " + new String(characteristic.getValue(), Charset.forName("UTF-8")));
            //gatt.disconnect();
            //gatt.close();
            //mGatt = null;
            iteracionesLectura = 0;
            retry = false;
            Log.i("Comunicacion terminada", "TRUE");

        

    


private void retrySameMotivo() 

    contadorRetrys++;
    Log.e("RetryMOTIVOSAME", "TRUE");
    //mGatt = null;
    try 

        mGatt.disconnect();
        mGatt.close();
        conectaConEsteMotivoRetry(idMotivo);

    catch (NullPointerException e)

        e.printStackTrace();
        sendBroadcast(intentError);

    



public void conectaConEsteMotivoRetry(String idmotivo)

    this.idMotivo = idmotivo;
    handler.removeCallbacks(runnableCodeTimeOutConversacion);
    //handler.postDelayed(timeOutFirmado, 10000);
    if (mGatt != null) 

        try

            //myTimerProtocolo = null;
            handler.removeCallbacks(runnableCodeTimeOutTransmisiones);
            Log.d("mBluetoothDevice", "mBluetoothDevice.connectGatt called in the retry...");
            /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 
                            Log.d("BOUNDING??? ", mBluetoothDevice.createBond()+"");
                        */
            mGatt = mBluetoothDevice.connectGatt(getApplicationContext(), false, gattCallback);
            //stopScan();// will stop after first device detection

        catch (NullPointerException e)

            e.printStackTrace();

        

     else 

        Log.i("MGATTISNULL","MGATT ES NULL");

    



private Runnable runnableCodeTimeOutTransmisiones = new Runnable() 
    @Override
    public void run() 

        if (!esperandoDesconexion) 

            //contadorRetrys = (int) NUMEROS_DE_RETRYS;

            try 

                Log.e("TIMERPROTOCOLO", "TRUE");
                if(mGatt!= null)

                    mGatt.disconnect();
                    mGatt.close();

                

                if (!retry)


                    Log.e("TIMEOUT","TRANSMISIONES");
                    // presenter.escribirEnToast("TIMEOUT Transmisiones");
                    sendBroadcast(intentError);
                /*else 

                retrySameMotivo();

            */

            catch (NullPointerException e)

                e.printStackTrace();

            

        

    
;

private Runnable timeOutFirmado = new Runnable() 

    @Override
    public void run() 

        try 

            Log.e("TIMEOUTGLOBALMAXFIRMADO", "TRUE");
            //presenter.escribirEnToast("TIMEOUT Global FIRMANDO");
            //PRUEBA LISTASCAN
            sendBroadcast(intentError);
            mGatt.disconnect();
            mGatt.close();
        catch (Exception e)

            e.printStackTrace();

            try 

                Log.e("HACER DESCONAMANO","TRUE");
                mGatt.disconnect();
                mGatt.close();


            catch (Exception exc)

                exc.printStackTrace();

            

        

    
;

@Nullable
@Override
public IBinder onBind(Intent intent) 

    return mBinder;



@Override
public boolean onUnbind(Intent intent) 

    try
        mGatt.disconnect();
        mGatt.close();
        Log.e("UNBINDFirmado", "UNBIND");
        //presenter.lanzarServicioFirmado();

        try 

            handler.removeCallbacks(runnableCodeTimeOutTransmisiones);
            handler.removeCallbacks(runnableCodeTimeOutConversacion);
            handler.removeCallbacks(timeOutFirmado);

            /*if (presenter.getErrorFirmando() && presenter.getNumeroIntentosFirmar() <= 2)

                presenter.setNumeroIntentosFirmar(presenter.getNumeroIntentosFirmar() + 1);
                presenter.setFlagScanDevice(true);
                presenter.escribirEnToast("Reintento...");
                presenter.connectToDeviceServicio(presenter.getActualDevice(), presenter.getNserieString());

            else 


                presenter.setNumeroIntentosFirmar(0);
                //presenter.desaparecerBotonesMotivos();
                presenter.setVisibilityMensajeDespuesDeFirmar(View.VISIBLE);
                presenter.setVisibilityBotonAceptarDespuesDeFirmar(View.VISIBLE);
                presenter.setIconFirmadoEstado(R.drawable.check_gif);

                if(presenter.getTextDespuesFirmado().equals("Ha ocurrido un problema con su fichaje, vuelva a intentarlo."))

                    presenter.setVisibilityBotonReintentar(View.VISIBLE);

                

            */

        catch (NullPointerException e)

            Log.e("ERROR IN UNBIND", "A NULLPOINT");
            e.printStackTrace();

        

    catch (Exception e)

        e.printStackTrace();

    

    return super.onUnbind(intent);


【问题讨论】:

您的代码中有几个 writeCharacteristic 调用。是每次调用都会返回 false 还是只有部分调用返回 false? 我在完全阅读长(和西班牙语)代码时遇到了一些麻烦,但是用 Notification 属性写入特征对我来说看起来很奇怪,尽管理论上它可能会发生。 抱歉演示文稿。我使用通知来进行回调。 第一次写返回true但没有调用onWriteCharacteristic回调,第二次写,write方法返回false,这次调用回调! 【参考方案1】:

它可能会返回 false,因为在 BLE 中您一次可以执行一项操作。如果您同时执行多个写入,则它只执行最后一次写入。

所以,你必须等到一个写完,然后你才能写另一个。

【讨论】:

有道理,但我一直在确保我不会同时写两次.. 我只是为了发布它! 常见的要求是一次只能执行一个操作,并且特征属性必须是可写的。

以上是关于gatt.writeCharacteristic() 可能返回 false 的原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章