android蓝牙配对自动连接传输数据,android实现主动连接和被动连接的蓝牙聊天功能...

android实现主动连接和被动连接的蓝牙聊天功能

发布时间:2020-09-13 21:11:37

来源:脚本之家

阅读:151

作者:mmsx

在项目中经常用到蓝牙的应用,在这里特意写了一个demo。并且封装了代码,可以主动连接和被动连接一起使用,也可以分开使用。方便后面以后查询使用,也重新踩了部分坑。

项目地址:android实现蓝牙聊天功能

1、程序简单的界面

04241ae2dd3ac3e393ac391ad9a90340.png

2825c1ba5ddfedf02ce4d6282b539224.png

c253d442f5ca001e8e8c504bdd948a8f.png

2、客户端,主动连接

package com.bluetooth.tool;

import android.bluetooth.BluetoothAdapter;

import android.bluetooth.BluetoothDevice;

import android.bluetooth.BluetoothSocket;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.util.UUID;

//蓝牙连接管理类

public class BluetoothManage {

private static final Object mLock = new Object();

//蓝牙类的具体实现核心成员

private BluetoothAdapter mBtAdapter = BluetoothAdapter.getDefaultAdapter();

//蓝牙类的具体数据核心成员

private BluetoothSocket mTransferSocket = null;

//当前连接的蓝牙地址

String mstrName = "";//当前连接用到的IP地址

String mstrAddress = "";//当前连接用到的IP地址

//读线程

ReadThread mReadThread = null;

//从数据核心成员拿到的输入输出

InputStream mInputStream = null;

OutputStream mOutputStream = null;

private static BluetoothManage manage = null;

public static BluetoothManage getInstance(){

synchronized (BluetoothManage.class){

if(manage == null)

manage = new BluetoothManage();

}

return manage;

}

public boolean sendData(int nLength, byte[] data) {

if (mOutputStream == null) return false;

try {

mOutputStream.write(data, 0, nLength);

return true;

} catch (IOException e) {

e.printStackTrace();

}

return false;

}

ConnectListener mConnectListener = null;

public void regConnectListener(ConnectListener arg0) {

mConnectListener = arg0;

}

TopDataIOListener mIOListener = null;

public void regIOListener(TopDataIOListener arg0) {

mIOListener = arg0;

}

public void unRegIOListener() {

mIOListener = null;

}

public boolean setSelectedDevice(String strDevice) {

String[] strings = strDevice.split("\\|");

if (strings.length == 2) {

mstrName = strings[0];

mstrAddress = strings[1];

return true;

}

return false;

}

public String getSelectedDeviceName() {

if (mstrAddress.length() == 0) {

return null;

}

return String.format("%s|%s", mstrName, mstrAddress);

}

public void connect() {

if (mstrAddress.length() == 0) return;

final BluetoothDevice device = mBtAdapter.getRemoteDevice(mstrAddress);

new Thread(new Runnable() {

@Override

public void run() {

synchronized (mLock) {

String strLogString = "";

try {

try {

mTransferSocket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));

} catch (IOException e1) {

mTransferSocket = null;

}

if (mTransferSocket == null) {

if (null != mConnectListener)

mConnectListener.OnConnectStatusCallBack(false);

return;

}

long nStartMillTime = System.currentTimeMillis();

//连接

try {

mTransferSocket.connect();

} catch (IOException e1) {

try {

mTransferSocket.close();

} catch (IOException e2) {

e2.printStackTrace();

}

//等待一定时间

mTransferSocket = null;

try {

long havePassTime = System.currentTimeMillis() - nStartMillTime;

if (havePassTime < 6000) {

Thread.sleep(7000 - havePassTime);

}

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//连接失败

if (mTransferSocket == null) {

if (null != mConnectListener)

mConnectListener.OnConnectStatusCallBack(false);

return;

}

try {

mInputStream = mTransferSocket.getInputStream();

mOutputStream = mTransferSocket.getOutputStream();

mReadThread = new ReadThread();

mReadThread.start();

if (null != mConnectListener)

mConnectListener.OnConnectStatusCallBack(true);

} catch (IOException e1) {

//断开连接

try {

if (mTransferSocket != null)

mTransferSocket.close();

} catch (IOException e2) {

e2.printStackTrace();

}

mTransferSocket = null;

e1.printStackTrace();

if (null != mConnectListener)

mConnectListener.OnConnectStatusCallBack(false);

}

} catch (Exception e) {

//总体异常

if (null != mConnectListener)

mConnectListener.OnConnectStatusCallBack(false);

}

}

}//run()

}).start();

}

//读取数据

class ReadThread extends Thread {

public void run() {

int nMaxBufLength = 1024;

byte[] buffer = new byte[nMaxBufLength];

int byteRead = -1;

synchronized (mLock) {

while (!isInterrupted()) {

try {

if (mInputStream != null) {

byteRead = mInputStream.read(buffer);

if (byteRead > 0 && byteRead <= buffer.length) {

if (mIOListener != null)

mIOListener.OnIOCallBack(byteRead, buffer);

} else /*if (byteRead < 0 || byteRead > buffer.length)*/ {

//连接已断开

if (mConnectListener != null) {

mConnectListener.OnDisConnectCallBack();

}

break;

}

} else {

break;

}

} catch (IOException e) {

//连接已断开

if (mConnectListener != null) {

mConnectListener.OnDisConnectCallBack();

}

break;

}

}//while(!isInterrupted())

}//synchronized (mLock)

}

}

//断开蓝牙

public void disConnect() {

mConnectListener = null;

//结束读线程

if (mReadThread != null) {

mReadThread.interrupt();

mReadThread = null;

}

//取消所有连接

if (mTransferSocket != null) {

try {

mTransferSocket.close();

if (mInputStream != null)

mInputStream.close();

if (mOutputStream != null)

mOutputStream.close();

mInputStream = null;

mOutputStream = null;

mTransferSocket = null;

} catch (IOException e) {

e.printStackTrace();

} catch (Exception e) {

}

}

}

}

主动连接应该是比较简单的,一个类就能实现,包括数据的收发。

3、蓝牙服务端,接收蓝牙连接

/**

* Copyright (C) 2009 The Android Open Source Project

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

* http://www.apache.org/licenses/LICENSE-2.0

*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.

*/

package com.bluetooth.tool;

import android.bluetooth.BluetoothAdapter;

import android.bluetooth.BluetoothDevice;

import android.bluetooth.BluetoothServerSocket;

import android.bluetooth.BluetoothSocket;

import android.content.Context;

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.util.Log;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.util.UUID;

/**

* This class does all the work for setting up and managing Bluetooth

* connections with other devices. It has a thread that listens for incoming

* connections, a thread for connecting with a device, and a thread for

* performing data transmissions when connected.

*/

public class BluetoothChatService {

// Debugging

private static final String TAG = "BluetoothChatService";

private static final boolean D = true;

// Name for the SDP record when creating server socket

private static final String NAME = "BluetoothChat";

// Unique UUID for this application

// private static final UUID MY_UUID =

// UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");

private static final UUID MY_UUID = UUID

.fromString("00001101-0000-1000-8000-00805F9B34FB");

//

// Member fields

private final BluetoothAdapter mAdapter;

private final Handler mHandler;

private AcceptThread mAcceptThread;

private ConnectThread mConnectThread;

private ConnectedThread mConnectedThread;

private int mState;

private BluetoothDevice mBluetoothDevice = null;

// Constants that indicate the current connection state

public static final int STATE_NONE = 0; // we're doing nothing

public static final int STATE_LISTEN = 1; // now listening for incoming

// connections

public static final int STATE_CONNECTING = 2; // now initiating an outgoing

// connection

public static final int STATE_CONNECTED = 3; // now connected to a remote

// device

public static boolean mbIsOpenTimer = false;

/**

* Constructor. Prepares a new BluetoothChat session.

*

* @param context

* The UI Activity Context

* @param handler

* A Handler to send messages back to the UI Activity

*/

public BluetoothChatService(Context context, Handler handler) {

mAdapter = BluetoothAdapter.getDefaultAdapter();

mState = STATE_NONE;

mHandler = handler;

}

/**

* Set the current state of the chat connection

*

* @param state

* An integer defining the current connection state

*/

private synchronized void setState(int state) {

if (D)

Log.d(TAG, "setState() " + mState + " -> " + state);

mState = state;

// Give the new state to the Handler so the UI Activity can update

mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, -1)

.sendToTarget();

}

/**

* Return the current connection state.

*/

public synchronized int getState() {

return mState;

}

/**

* Start the chat service. Specifically start AcceptThread to begin a

* session in listening (server) mode. Called by the Activity onResume()

*/

public synchronized void start() {

if (D)

Log.d(TAG, "start");

// Cancel any thread attempting to make a connection

if (mConnectThread != null) {

mConnectThread.cancel();

mConnectThread = null;

}

// Cancel any thread currently running a connection

if (mConnectedThread != null) {

mConnectedThread.cancel();

mConnectedThread = null;

}

// Start the thread to listen on a BluetoothServerSocket

if (mAcceptThread == null) {

Log.d(TAG, "start mAcceptThread");

mAcceptThread = new AcceptThread();

mAcceptThread.start();

}

setState(STATE_LISTEN);

}

/**

* Start the ConnectThread to initiate a connection to a remote device.

*

* @param device

* The BluetoothDevice to connect

*/

public synchronized void connect(BluetoothDevice device) {

if (D)

Log.d(TAG, "connect to: " + device);

// Cancel any thread attempting to make a connection

if (mState == STATE_CONNECTING) {

if (mConnectThread != null) {

mConnectThread.cancel();

mConnectThread = null;

}

}

// Cancel any thread currently running a connection

if (mConnectedThread != null) {

mConnectedThread.cancel();

mConnectedThread = null;

}

// Start the thread to connect with the given device

mConnectThread = new ConnectThread(device);

mConnectThread.start();

setState(STATE_CONNECTING);

mBluetoothDevice = device;

}

/**

* Start the ConnectedThread to begin managing a Bluetooth connection

*

* @param socket

* The BluetoothSocket on which the connection was made

* @param device

* The BluetoothDevice that has been connected

*/

public synchronized void connected(BluetoothSocket socket,

BluetoothDevice device) {

if (D)

Log.d(TAG, "connected");

// Cancel the thread that completed the connection

if (mConnectThread != null) {

mConnectThread.cancel();

mConnectThread = null;

}

// Cancel any thread currently running a connection

if (mConnectedThread != null) {

mConnectedThread.cancel();

mConnectedThread = null;

}

// Cancel the accept thread because we only want to connect to one

// device

if (mAcceptThread != null) {

mAcceptThread.cancel();

mAcceptThread = null;

}

// Start the thread to manage the connection and perform transmissions

mConnectedThread = new ConnectedThread(socket);

mConnectedThread.start();

// Send the name of the connected device back to the UI Activity

Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME);

Bundle bundle = new Bundle();

bundle.putString(BluetoothChat.DEVICE_NAME, device.getName());

msg.setData(bundle);

mHandler.sendMessage(msg);

setState(STATE_CONNECTED);

}

/**

* Stop all threads

*/

public synchronized void stop() {

if (D)

Log.d(TAG, "stop");

if (mConnectThread != null) {

mConnectThread.cancel();

mConnectThread = null;

}

if (mConnectedThread != null) {

mConnectedThread.cancel();

mConnectedThread = null;

}

if (mAcceptThread != null) {

mAcceptThread.cancel();

mAcceptThread = null;

}

setState(STATE_NONE);

}

/**

* Write to the ConnectedThread in an unsynchronized manner

*

* @param out

* The bytes to write

* @see ConnectedThread#write(byte[])

*/

public void write(byte[] out) {

// Create temporary object

ConnectedThread r;

// Synchronize a copy of the ConnectedThread

synchronized (this) {

if (mState != STATE_CONNECTED)

return;

r = mConnectedThread;

}

// Perform the write unsynchronized

r.write(out);

}

/**

* Indicate that the connection attempt failed and notify the UI Activity.

*/

private void connectionFailed() {

setState(STATE_LISTEN);

// Send a failure message back to the Activity

Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);

Bundle bundle = new Bundle();

bundle.putString(BluetoothChat.TOAST, "Unable to connect device");

msg.setData(bundle);

mHandler.sendMessage(msg);

}

/**

* Indicate that the connection was lost and notify the UI Activity.

*/

private void connectionLost() {

setState(STATE_LISTEN);

// Send a failure message back to the Activity

Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);

Bundle bundle = new Bundle();

bundle.putString(BluetoothChat.TOAST, "Device connection was lost");

msg.setData(bundle);

mHandler.sendMessage(msg);

start();

}

/**

* This thread runs while listening for incoming connections. It behaves

* like a server-side client. It runs until a connection is accepted (or

* until cancelled).

*/

private class AcceptThread extends Thread {

// The local server socket

private final BluetoothServerSocket mmServerSocket;

public AcceptThread() {

BluetoothServerSocket tmp = null;

// Create a new listening server socket

try {

tmp = mAdapter

.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);

} catch (IOException e) {

Log.e(TAG, "listen() failed", e);

}

mmServerSocket = tmp;

}

public void run() {

if (D)

Log.d(TAG, "BEGIN mAcceptThread" + this);

setName("AcceptThread");

BluetoothSocket socket = null;

// Listen to the server socket if we're not connected

while (mState != STATE_CONNECTED) {

try {

// This is a blocking call and will only return on a

// successful connection or an exception

if(mmServerSocket != null)

{

Log.d(TAG, "waitting accept!");

socket = mmServerSocket.accept();

Log.d(TAG, "accpting!");

}

else

{

setState(STATE_NONE);

if (mAcceptThread != null) {

mAcceptThread = null;

}

Log.d(TAG, "mmServerSocket = null!");

break;

}

} catch (IOException e) {

Log.e(TAG, "accept() failed", e);

break;

}

// If a connection was accepted

if (socket != null) {

synchronized (BluetoothChatService.this) {

switch (mState) {

case STATE_LISTEN:

case STATE_CONNECTING:

// Situation normal. Start the connected thread.

connected(socket, socket.getRemoteDevice());

break;

case STATE_NONE:

case STATE_CONNECTED:

// Either not ready or already connected. Terminate

// new socket.

try {

socket.close();

} catch (IOException e) {

Log.e(TAG, "Could not close unwanted socket", e);

}

break;

}

}

}

}

if (D)

Log.i(TAG, "END mAcceptThread");

}

public void cancel() {

if (D)

Log.d(TAG, "cancel " + this);

try {

if(mmServerSocket != null)

mmServerSocket.close();

} catch (IOException e) {

Log.e(TAG, "close() of server failed", e);

}

}

}

/**

* This thread runs while attempting to make an outgoing connection with a

* device. It runs straight through; the connection either succeeds or

* fails.

*/

private class ConnectThread extends Thread {

private final BluetoothSocket mmSocket;

private final BluetoothDevice mmDevice;

public ConnectThread(BluetoothDevice device) {

mmDevice = device;

BluetoothSocket tmp = null;

// Get a BluetoothSocket for a connection with the

// given BluetoothDevice

try {

tmp = device.createRfcommSocketToServiceRecord(MY_UUID);

} catch (IOException e) {

Log.e(TAG, "create() failed", e);

}

mmSocket = tmp;

}

public void run() {

Log.i(TAG, "BEGIN mConnectThread");

setName("ConnectThread");

// Always cancel discovery because it will slow down a connection

mAdapter.cancelDiscovery();

// Make a connection to the BluetoothSocket

try {

// This is a blocking call and will only return on a

// successful connection or an exception

mmSocket.connect();

} catch (IOException e) {

connectionFailed();

// Close the socket

try {

mmSocket.close();

} catch (IOException e2) {

Log.e(TAG,

"unable to close() socket during connection failure",

e2);

}

// Start the service over to restart listening mode

BluetoothChatService.this.start();

return;

}

// Reset the ConnectThread because we're done

synchronized (BluetoothChatService.this) {

mConnectThread = null;

}

// Start the connected thread

connected(mmSocket, mmDevice);

}

public void cancel() {

try {

mmSocket.close();

} catch (IOException e) {

Log.e(TAG, "close() of connect socket failed", e);

}

}

}

/**

* This thread runs during a connection with a remote device. It handles all

* incoming and outgoing transmissions.

*/

private class ConnectedThread extends Thread {

private final BluetoothSocket mmSocket;

private final InputStream mmInStream;

private final OutputStream mmOutStream;

public ConnectedThread(BluetoothSocket socket) {

Log.d(TAG, "create ConnectedThread");

mmSocket = socket;

InputStream tmpIn = null;

OutputStream tmpOut = null;

try {

tmpIn = socket.getInputStream();

tmpOut = socket.getOutputStream();

} catch (IOException e) {

Log.e(TAG, "temp sockets not created", e);

}

mmInStream = tmpIn;

mmOutStream = tmpOut;

}

public void run() {

Log.i(TAG, "BEGIN mConnectedThread");

byte[] buffer = new byte[1024];

int bytes;

// Keep listening to the InputStream while connected

while (true) {

try {

// Read from the InputStream

if(mmInStream != null ){

bytes = mmInStream.read(buffer);

if(bytes > 0 && bytes <= buffer.length)

{

onDadaReceive(buffer,0,bytes);

}

else{

Log.i("recieve", "Baddata");

}

}

else{

Log.i(TAG, "BadInputStream");

connectionLost();

break;

}

}

catch (IOException e) {

Log.i(TAG, "disconnected" + e.toString(), e);

connectionLost();

break;

} catch (Exception e) {

e.printStackTrace();

}

}

}

private void onDadaReceive(byte[] buffer, int i, int bytes) {

if(bytes>0)

{

//֪ͨܘַ

mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes,

-1, buffer).sendToTarget();

}

else

Log.e("recieve","null");

}

/**

* Write to the connected OutStream.

*

* @param buffer

* The bytes to write

*/

public void write(byte[] buffer) {

try {

mmOutStream.write(buffer);

// Share the sent message back to the UI Activity

mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1,

buffer).sendToTarget();

} catch (IOException e) {

Log.e(TAG, "Exception during write", e);

}

}

public void cancel() {

try {

mmSocket.close();

} catch (IOException e) {

Log.e(TAG, "close() of connect socket failed", e);

}

}

}

}

这个蓝牙服务的代码,是标准蓝牙示例demo代码

我根据上面,自已封装了一层,方便管理数据。

package com.bluetooth.tool;

import android.content.Context;

import android.os.Handler;

import android.os.Message;

import android.util.Log;

/**

* 蓝牙服务,接收蓝牙连接

*/

public class BluetoothChat {

// Debugging

private static final String TAG = "BluetoothChat";

private static final boolean D = true;

public static final int MESSAGE_STATE_CHANGE = 1;

public static final int MESSAGE_READ = 2;

public static final int MESSAGE_WRITE = 3;

public static final int MESSAGE_DEVICE_NAME = 4;

public static final int MESSAGE_TOAST = 5;

// Key names received from the BluetoothChatService Handler

public static final String DEVICE_NAME = "device_name";

public static final String TOAST = "toast";

private String mConnectedDeviceName = null;

private static StringBuffer mOutStringBuffer;

private static BluetoothChatService mChatService = null;

private static Context mContext;

private volatile static BluetoothChat mBluetoothChat = null;

TopDataIOListener mIOListener = null;

public static BluetoothChat GetInstance(Context context) {

if (mBluetoothChat == null && mContext == null) {

synchronized (BluetoothChat.class){

mBluetoothChat = new BluetoothChat();

mContext = context;

}

}

return mBluetoothChat;

}

public void onStart() {

if (mChatService == null)

setupChat();

if (mChatService != null) {

if (mChatService.getState() == BluetoothChatService.STATE_NONE) {

mChatService.start();

}

}

}

private void setupChat() {

mChatService = new BluetoothChatService(mContext,mHandler);

mOutStringBuffer = new StringBuffer("");

}

public void onDestroy() {

if (mChatService != null)

mChatService.stop();

}

public void sendMessage(String message) {

if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {

Log.i("Show", "");

return;

}

if (message.length() > 0) {

byte[] send = message.getBytes();

mChatService.write(send);

mOutStringBuffer.setLength(0);

}

}

private final Handler mHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

switch (msg.what) {

case MESSAGE_STATE_CHANGE:

if (D)

Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);

switch (msg.arg1) {

case BluetoothChatService.STATE_CONNECTED:

break;

case BluetoothChatService.STATE_CONNECTING:

break;

case BluetoothChatService.STATE_LISTEN:

break;

case BluetoothChatService.STATE_NONE:

break;

}

break;

case MESSAGE_WRITE:

byte[] writeBuf = (byte[]) msg.obj;

String writeMessage = new String(writeBuf);

break;

case MESSAGE_READ:

byte[] readBuf = (byte[]) msg.obj;

//收到的蓝牙数据,回传给界面显示

if (mIOListener != null)

mIOListener.OnIOCallBack(readBuf.length, readBuf);

break;

case MESSAGE_DEVICE_NAME:

mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);

Log.i(TAG, "MESSAGE_DEVICE_NAME " + mConnectedDeviceName);

break;

case MESSAGE_TOAST:

break;

}

}

};

public void regIOListener(TopDataIOListener arg0) {

mIOListener = arg0;

}

}

还有一个蓝牙的广播。这里就不贴代码了。

4、权限

蓝牙服务接收广播注册

5、在上面注释看到了有个bug注释

就是部分手机6.0以上 蓝牙蓝牙startDiscovery方法需要加上这个权限android.permission.ACCESS_COARSE_LOCATION。不然启动搜索蓝牙无效。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android蓝牙配对传输数据协议Demo是一个示例应用程序,用于演示在Android设备之间进行蓝牙配对传输数据的协议。该Demo应用程序旨在帮助开发人员理解和学习蓝牙配对传输数据的原理和实现方式。 该Demo应用程序包含以下几个主要部分: 1. 设备配对:该部分演示如何在两个Android设备之间建立蓝牙连接并进行配对。用户可以在应用程序中选择要配对的设备,然后触发配对过程。在配对成功后,两台设备将建立蓝牙连接。 2. 数据传输:该部分演示如何在已配对蓝牙设备之间传输数据。用户可以在应用程序中选择要传输的数据,然后触发数据传输过程。传输的数据可以是文本、图片或其他类型的文件。在数据传输过程中,应用程序将通过蓝牙连接将数据发送到另一台设备。 3. 数据接收:该部分演示如何在已配对蓝牙设备上接收传输的数据。用户可以在应用程序中查看接收到的数据,并进行相应的处理。接收到的数据可以是文本、图片或其他类型的文件。 通过这个Demo应用程序,开发人员可以学习如何使用Android蓝牙API来实现设备配对和数据传输功能。他们可以了解到蓝牙配对的步骤和过程,以及如何处理传输数据。这个Demo为开发人员提供了一个基础的示例,可以在其基础上进行二次开发和定制,以满足特定应用场景的需求。 总之,Android蓝牙配对传输数据协议Demo是一个有助于学习和理解蓝牙配对传输数据原理和实现方式的示例应用程序。通过该Demo,开发人员可以快速上手并开发出适用于他们应用场景的蓝牙配对传输数据功能

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值