安卓通讯开发——蓝牙

通讯(Connectivity)

除了提供标准的网络连接外,Android还提供API,让您的应用程序能够使用蓝牙、NFC、Wi-Fi、P2P、USB和SIP等协议与其他设备进行连接和交互。

In addition to providing standard network connectivity, Android provides APIs to let your app connect and interact with other devices with protocols such as Bluetooth, NFC, Wi-Fi P2P, USB, and SIP.

https://developer.android.google.cn/guide/topics/connectivity


首先,什么是蓝牙? 

  • 蓝牙技术最初由电信巨头爱立信公司于1994年创制,当时是作为RS232数据线的替代方案
  • 是一种支持设备短距离通信(一般10m内,且无阻隔媒介)的无线电技术。
  • 使用2.4—2.485GHz的ISM波段的UHF无线电波
  • 能在包括移动电话、PDA、无线耳机、笔记本电脑等众多设备之间进行无线信息交换。
  • 利用“蓝牙”技术,能够有效的简化移动通信终端设备之间的通信,也能够成功的简化设备与Internet之间的通信,这样数据传输变得更加迅速高效,为无线通信拓宽道路。

注意:Android 2.0 引入蓝牙接口,在开发时,需要真机测试,如果需要数据传输,还需要两台机器,另外蓝牙下哟硬件支持。

 

蓝牙

https://developer.android.google.cn/guide/topics/connectivity/bluetooth

Android 平台包含蓝牙网络堆栈支持,凭借此项支持,设备能以无线方式与其他蓝牙设备交换数据。应用框架提供了通过 Android Bluetooth API 访问蓝牙功能的途径。 这些 API 允许应用以无线方式连接到其他蓝牙设备,从而实现点到点和多点无线功能。

使用 Bluetooth API,Android 应用可执行以下操作:

  • 扫描其他蓝牙设备
  • 查询本地蓝牙适配器的配对蓝牙设备
  • 建立 RFCOMM 通道
  • 通过服务发现连接到其他设备
  • 与其他设备进行双向数据传输
  • 管理多个连接

本文将介绍如何使用传统蓝牙。传统蓝牙适用于电池使用强度较大的操作,例如 Android 设备之间的流式传输和通信等。 针对具有低功耗要求的蓝牙设备,Android 4.3(API 级别 18)中引入了面向低功耗蓝牙的 API 支持。 如需了解更多信息,请参阅低功耗蓝牙


蓝牙设备操作

权限

要在应用中使用蓝牙功能,必须声明蓝牙权限 BLUETOOTH。您需要此权限才能执行任何蓝牙通信,例如请求连接、接受连接和传输数据等。

如果您希望您的应用启动设备发现或操作蓝牙设置,则还必须声明 BLUETOOTH_ADMIN 权限。 大多数应用需要此权限仅仅为了能够发现本地蓝牙设备。 除非该应用是将要应用户请求修改蓝牙设置的“超级管理员”,否则不应使用此权限所授予的其他能力。 :如果要使用 BLUETOOTH_ADMIN 权限,则还必须拥有 BLUETOOTH 权限。

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />

打开操作

//第一种方法(弹出对话框提示信息)
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
//打开本机的蓝牙发现功能(默认打开120秒,一个应用程序可以设定的最长持续时间为3600秒)
//enabler.putExtra(BluetoothAdapter.EXTRA SCOVERABLE_DURATION, 300)
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(enabler,REQUEST_ENABLE);

//第二种方法(直接打开,没有提示)
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
bluetoothAdapter.enable();

上面是老的API文档提供的方法,下面是目前API文档的提供的写法

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
    // Device does not support Bluetooth
}

if (!mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

 (有提示信息的如图所示)

关闭操作

BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter(); 
_bluetooth.disable ():

搜索操作

BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter();
_bluetooth.startDiscovery();

getBondedDevices()为扫描当前配对列表 


蓝牙通讯案例

蓝牙设备进行连接,需要一个唯一的UUID,在本案例中可以随便写,但是需要保证两个设备是相同的。

关于 UUID

通用唯一标识符 (UUID) 是用于唯一标识信息的字符串 ID 的 128 位标准化格式。 UUID 的特点是其足够庞大,因此您可以选择任意随机值而不会发生冲突。 在此示例中,它被用于唯一标识应用的蓝牙服务。 要获取 UUID 以用于您的应用,您可以使用网络上的众多随机 UUID 生成器之一,然后使用 fromString(String) 初始化一个 UUID

下面列出一些常用的蓝牙服务UUID:

ServiceDiscoveryServerServiceClassID_UUID = '{00001000-0000-1000-8000-00805F9B34FB}'
BrowseGroupDescriptorServiceClassID_UUID = '{00001001-0000-1000-8000-00805F9B34FB}'
PublicBrowseGroupServiceClass_UUID = '{00001002-0000-1000-8000-00805F9B34FB}'

#蓝牙串口服务
SerialPortServiceClass_UUID = '{00001101-0000-1000-8000-00805F9B34FB}'

LANAccessUsingPPPServiceClass_UUID = '{00001102-0000-1000-8000-00805F9B34FB}'

#拨号网络服务
DialupNetworkingServiceClass_UUID = '{00001103-0000-1000-8000-00805F9B34FB}'

#信息同步服务
IrMCSyncServiceClass_UUID = '{00001104-0000-1000-8000-00805F9B34FB}'

SDP_OBEXObjectPushServiceClass_UUID = '{00001105-0000-1000-8000-00805F9B34FB}'

#文件传输服务
OBEXFileTransferServiceClass_UUID = '{00001106-0000-1000-8000-00805F9B34FB}'

IrMCSyncCommandServiceClass_UUID = '{00001107-0000-1000-8000-00805F9B34FB}'
SDP_HeadsetServiceClass_UUID = '{00001108-0000-1000-8000-00805F9B34FB}'
CordlessTelephonyServiceClass_UUID = '{00001109-0000-1000-8000-00805F9B34FB}'
SDP_AudioSourceServiceClass_UUID = '{0000110A-0000-1000-8000-00805F9B34FB}'
SDP_AudioSinkServiceClass_UUID = '{0000110B-0000-1000-8000-00805F9B34FB}'
SDP_AVRemoteControlTargetServiceClass_UUID = '{0000110C-0000-1000-8000-00805F9B34FB}'
SDP_AdvancedAudioDistributionServiceClass_UUID = '{0000110D-0000-1000-8000-00805F9B34FB}'
SDP_AVRemoteControlServiceClass_UUID = '{0000110E-0000-1000-8000-00805F9B34FB}'
VideoConferencingServiceClass_UUID = '{0000110F-0000-1000-8000-00805F9B34FB}'
IntercomServiceClass_UUID = '{00001110-0000-1000-8000-00805F9B34FB}'

#蓝牙传真服务
FaxServiceClass_UUID = '{00001111-0000-1000-8000-00805F9B34FB}'

HeadsetAudioGatewayServiceClass_UUID = '{00001112-0000-1000-8000-00805F9B34FB}'
WAPServiceClass_UUID = '{00001113-0000-1000-8000-00805F9B34FB}'
WAPClientServiceClass_UUID = '{00001114-0000-1000-8000-00805F9B34FB}'

#个人局域网服务
PANUServiceClass_UUID = '{00001115-0000-1000-8000-00805F9B34FB}'

#个人局域网服务
NAPServiceClass_UUID = '{00001116-0000-1000-8000-00805F9B34FB}'

#个人局域网服务
GNServiceClass_UUID = '{00001117-0000-1000-8000-00805F9B34FB}'

DirectPrintingServiceClass_UUID = '{00001118-0000-1000-8000-00805F9B34FB}'
ReferencePrintingServiceClass_UUID = '{00001119-0000-1000-8000-00805F9B34FB}'
ImagingServiceClass_UUID = '{0000111A-0000-1000-8000-00805F9B34FB}'
ImagingResponderServiceClass_UUID = '{0000111B-0000-1000-8000-00805F9B34FB}'
ImagingAutomaticArchiveServiceClass_UUID = '{0000111C-0000-1000-8000-00805F9B34FB}'
ImagingReferenceObjectsServiceClass_UUID = '{0000111D-0000-1000-8000-00805F9B34FB}'
SDP_HandsfreeServiceClass_UUID = '{0000111E-0000-1000-8000-00805F9B34FB}'
HandsfreeAudioGatewayServiceClass_UUID = '{0000111F-0000-1000-8000-00805F9B34FB}'
DirectPrintingReferenceObjectsServiceClass_UUID = '{00001120-0000-1000-8000-00805F9B34FB}'
ReflectedUIServiceClass_UUID = '{00001121-0000-1000-8000-00805F9B34FB}'
BasicPringingServiceClass_UUID = '{00001122-0000-1000-8000-00805F9B34FB}'
PrintingStatusServiceClass_UUID = '{00001123-0000-1000-8000-00805F9B34FB}'

#人机输入服务
HumanInterfaceDeviceServiceClass_UUID = '{00001124-0000-1000-8000-00805F9B34FB}'

HardcopyCableReplacementServiceClass_UUID = '{00001125-0000-1000-8000-00805F9B34FB}'

#蓝牙打印服务
HCRPrintServiceClass_UUID = '{00001126-0000-1000-8000-00805F9B34FB}'

HCRScanServiceClass_UUID = '{00001127-0000-1000-8000-00805F9B34FB}'
CommonISDNAccessServiceClass_UUID = '{00001128-0000-1000-8000-00805F9B34FB}'
VideoConferencingGWServiceClass_UUID = '{00001129-0000-1000-8000-00805F9B34FB}'
UDIMTServiceClass_UUID = '{0000112A-0000-1000-8000-00805F9B34FB}'
UDITAServiceClass_UUID = '{0000112B-0000-1000-8000-00805F9B34FB}'
AudioVideoServiceClass_UUID = '{0000112C-0000-1000-8000-00805F9B34FB}'
SIMAccessServiceClass_UUID = '{0000112D-0000-1000-8000-00805F9B34FB}'
PnPInformationServiceClass_UUID = '{00001200-0000-1000-8000-00805F9B34FB}'
GenericNetworkingServiceClass_UUID = '{00001201-0000-1000-8000-00805F9B34FB}'
GenericFileTransferServiceClass_UUID = '{00001202-0000-1000-8000-00805F9B34FB}'
GenericAudioServiceClass_UUID = '{00001203-0000-1000-8000-00805F9B34FB}'
GenericTelephonyServiceClass_UUID = '{00001204-0000-1000-8000-00805F9B34FB}'

客户端

布局,准备一个仅作为标识的文本框,一个输入框,和一个提示当前状态的文本框(操作的结果及进度...)最后在来一个按钮

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Client">

    <TextView
        android:id="@+id/client_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="客户端"
        android:textSize="33sp"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/client_state"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="当前未执行任何操作"
        android:textSize="22sp"
        app:layout_constraintBottom_toTopOf="@+id/client_send"
        app:layout_constraintTop_toBottomOf="@+id/client_text" />

    <EditText
        android:id="@+id/client_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:hint="请输入要发送的内容"
        app:layout_constraintBottom_toTopOf="@+id/send" />

    <Button
        android:id="@+id/send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="send"
        android:text="发送"
        app:layout_constraintBottom_toBottomOf="parent" />

</android.support.constraint.ConstraintLayout>
package com.example.a5_21bluetooth;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.lang.ref.WeakReference;
import java.util.UUID;

public class Client extends AppCompatActivity {
    private static final int CONN_SUCCESS = 0x1;
    private static final int CONN_FAIL = 0x2;
    private static final int RECEIVER_INFO = 0x3;
    private static final int SET_EDITTEXT_NULL = 0x4;
    private static Button send;
    private static TextView client_state;
    private static EditText client_send;

    BluetoothAdapter bluetooth = null;//本地蓝牙设备
    BluetoothDevice device = null;//远程蓝牙设备
    BluetoothSocket socket = null;//蓝牙设备Socket客户端

    //输入输出流
    PrintStream out;
    BufferedReader in;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_client);
        setTitle("蓝牙客户端");
        client_state = findViewById(R.id.client_state);
        client_send =  findViewById(R.id.client_send);
        send = findViewById(R.id.send);
        init();
    }

    //创建蓝牙客户端端的Socket
    private void init() {
        client_state.setText("客户端已启动,正在等待连接...\n");
        new Thread(new Runnable() {
            @Override
            public void run() {
                //1.得到本地蓝牙设备的默认适配器
                bluetooth = BluetoothAdapter.getDefaultAdapter();
                //2.通过本地蓝牙设备得到远程蓝牙设备,把“输入服务器端的设备地址”换成另一台手机的mac地址
                device = bluetooth.getRemoteDevice("输入服务器端的设备地址");
                //3.根据UUID创建并返回一个BoluetoothSocket
                try {
                    socket = device.createRfcommSocketToServiceRecord(UUID.fromString("00000000-2527-eef3-ffff-ffffe3160865"));
                    if (socket != null) {
                        // 连接
                        socket.connect();
                        //处理客户端输出流
                        out = new PrintStream(socket.getOutputStream());
                        in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                    }
                    handler.sendEmptyMessage(CONN_SUCCESS);
                } catch (IOException e) {
                    e.printStackTrace();
                    Message msg = handler.obtainMessage(CONN_FAIL, e.getLocalizedMessage());
                    handler.sendMessage(msg);

                }

            }
        }).start();
    }

    //防止内存泄漏 正确的使用方法
    private final MyHandler handler = new MyHandler(this);

    public class MyHandler extends Handler {
        //软引用
        WeakReference<Client> weakReference;

        public MyHandler(Client activity) {
            weakReference = new WeakReference<Client>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Client activity = weakReference.get();
            if (activity != null) {
                switch (msg.what) {
                    case RECEIVER_INFO:
                        setInfo(msg.obj.toString() + "\n");
                        break;
                    case SET_EDITTEXT_NULL:
                        client_send.setText("");
                        break;
                    case CONN_SUCCESS:
                        setInfo("连接成功!\n");
                        send.setEnabled(true);
                        System.out.println("name" + device.getName());
                        System.out.println("Uuids" + device.getUuids());
                        System.out.println("Address" + device.getAddress());
                        new Thread(new ReceiverInfoThread()).start();
                        break;
                    case CONN_FAIL:
                        setInfo("连接失败!\n");
                        setInfo(msg.obj.toString() + "\n");
                        break;
                    default:
                        break;
                }
            }
        }
    }


    private boolean isReceiver = true;

    //接收信息的线程
    class ReceiverInfoThread implements Runnable {
        @Override
        public void run() {
            String info = null;
            while (isReceiver) {
                try {
                    System.out.println("--ReceiverInfoThread start --");
                    info = in.readLine();
                    System.out.println("--ReceiverInfoThread read --");
                    Message msg = handler.obtainMessage(RECEIVER_INFO, info);
                    handler.sendMessage(msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    public void send(View v) {
        final String content = client_send.getText().toString();
        if (TextUtils.isEmpty(content)) {
            Toast.makeText(this, "不能发送空消息", Toast.LENGTH_LONG).show();
            return;
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                out.println(content);
                out.flush();
                handler.sendEmptyMessage(SET_EDITTEXT_NULL);
            }
        }).start();
    }

    private void setInfo(String info) {
        StringBuffer sb = new StringBuffer();
        sb.append(client_state.getText());
        sb.append(info);
        client_state.setText(sb);
    }
}

 

服务器端

说是说服务器端,在这个案例实际上基本上没差2333(换了个名字)

package com.example.a5_21bluetooth;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.lang.ref.WeakReference;
import java.util.UUID;

public class Server extends AppCompatActivity {
    private static final int CONN_SUCCESS = 0x1;
    private static final int CONN_FAIL = 0x2;
    private static final int RECEIVER_INFO = 0x3;
    private static final int SET_EDITTEXT_NULL = 0x4;
    private static Button send;
    private static TextView server_state;
    private static EditText server_send;

    BluetoothAdapter bluetooth = null;//本地蓝牙设备
    BluetoothServerSocket serverSocket = null;//蓝牙设备Socket服务端
    BluetoothSocket socket = null;//蓝牙设备Socket客户端

    //输入输出流
    PrintStream out;
    BufferedReader in;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_server);
        setTitle("蓝牙服务端");
        server_state = (TextView) findViewById(R.id.server_state);
        server_send = (EditText) findViewById(R.id.server_send);
        send = (Button) findViewById(R.id.send);
        init();
    }

    //创建蓝牙服务器端的Socket
    private void init() {
        server_state.setText("服务器已启动,正在等待连接...\n");
        new Thread(new Runnable() {
            @Override
            public void run() {
                //1.得到本地设备
                bluetooth = BluetoothAdapter.getDefaultAdapter();
                //2.创建蓝牙Socket服务器
                try {
                    serverSocket = bluetooth.listenUsingRfcommWithServiceRecord("text", UUID.fromString("00000000-2527-eef3-ffff-ffffe3160865"));
                    //3.阻塞等待Socket客户端请求
                    socket = serverSocket.accept();
                    if (socket != null) {
                        out = new PrintStream(socket.getOutputStream());
                        in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    }
                    handler.sendEmptyMessage(CONN_SUCCESS);
                } catch (IOException e) {
                    e.printStackTrace();
                    Message msg = handler.obtainMessage(CONN_FAIL, e.getLocalizedMessage());
                    handler.sendMessage(msg);
                }

            }
        }).start();

    }

    //防止内存泄漏 正确的使用方法
    private final MyHandler handler = new MyHandler(this);

    public class MyHandler extends Handler {
        //软引用
        WeakReference<Server> weakReference;

        public MyHandler(Server activity) {
            weakReference = new WeakReference<Server>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Server activity = weakReference.get();
            if (activity != null) {
                switch (msg.what) {
                    case RECEIVER_INFO:
                        setInfo(msg.obj.toString() + "\n");
                        break;
                    case SET_EDITTEXT_NULL:
                        server_send.setText("");
                        break;
                    case CONN_SUCCESS:
                        setInfo("连接成功!\n");
                        send.setEnabled(true);
                        new Thread(new ReceiverInfoThread()).start();
                        break;
                    case CONN_FAIL:
                        setInfo("连接失败!\n");
                        setInfo(msg.obj.toString() + "\n");
                        break;
                    default:
                        break;
                }
            }
        }
    }

    private boolean isReceiver = true;

    class ReceiverInfoThread implements Runnable {
        @Override
        public void run() {
            String info = null;
            while (isReceiver) {
                try {
                    info = in.readLine();
                    Message msg = handler.obtainMessage(RECEIVER_INFO, info);
                    handler.sendMessage(msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void send(View v) {
        final String content = server_send.getText().toString();
        if (TextUtils.isEmpty(content)) {
            Toast.makeText(Server.this, "不能发送空消息", Toast.LENGTH_LONG).show();
            return;
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                out.println(content);
                out.flush();
                handler.sendEmptyMessage(SET_EDITTEXT_NULL);
            }
        }).start();
    }

    private void setInfo(String info) {
        StringBuffer sb = new StringBuffer();
        sb.append(server_state.getText());
        sb.append(info);
        server_state.setText(sb);
    }

}
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Server">

    <TextView
        android:id="@+id/server_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="服务器端"
        android:textSize="33sp"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/server_state"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="当前未执行任何操作"
        android:textSize="22sp"
        app:layout_constraintBottom_toTopOf="@+id/server_send"
        app:layout_constraintTop_toBottomOf="@+id/server_text" />

    <EditText
        android:id="@+id/server_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:hint="请输入要发送的内容"
        app:layout_constraintBottom_toTopOf="@+id/send" />

    <Button
        android:id="@+id/send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="send"
        android:text="发送"
        app:layout_constraintBottom_toBottomOf="parent" />

</android.support.constraint.ConstraintLayout>

效果不太方便展示,需要两台手机大概界面如下,成功的话会输出对应的内容,通过输入框和按钮可以在两个端间进行交互

  • 5
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云无心鸟知还

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值