传统蓝牙配对连接,为何有些蓝牙模块只配对没连接上?

最近调试需求,针对性对某个无屏幕的设备编写了个蓝牙日志传输应用,也很简单,即使把log和log文件通过蓝牙传输到另一台设备查看,不多说,讲下蓝牙配对连接。直接上代码

public class BleLogMonitorAty extends FragmentActivity implements AdapterView.OnItemClickListener, View.OnClickListener {

    private static final boolean DEBUG = true;
    public static BleLogMonitorAty logMonitorAty;
    private static final String TAG = "BleLogMonitorAty";
    private ArrayAdapter<String> devAdapter;
    private List<BluetoothDevice> devices = new ArrayList<>();
    private List<BluetoothDevice> bondeDevices = new ArrayList<>();
    private List<String> devNames = new ArrayList<>();
    // private List<String> bondeDevNames = new ArrayList<>();
    private BluetoothAdapter mBluetoothAdapter;
    private ProgressBar progressBar;
    // private ArrayAdapter<String> bonDevAdapter;
    private BluetoothDevice curDevice;

    private BleLogFragment logFragment;
    private DevAdapter bonDevAdapter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.ble_layout);
        logMonitorAty = this;
        ActivityMger.addActivity(this);
        initView();
        initBluetooth();
        updateBondDevlist();
        Intent intent = new Intent(this, LogService.class);
        startService(intent);
        bindService(intent, conn, Context.BIND_AUTO_CREATE);

    }


    private void updateBondDevlist() {
        bondeDevices.clear();
        bondeDevices.addAll(mBluetoothAdapter.getBondedDevices());
        bonDevAdapter.notifyDataSetChanged();
    }

    private void initBluetooth() {
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mBluetoothAdapter.enable();
        //每搜索到一个设备就会发送一个该广播
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST); //配对请求广播
        registerReceiver(receiver, filter);

    }

    private void discovery() {
        if (mBluetoothAdapter.isDiscovering()) {
            mBluetoothAdapter.cancelDiscovery();
        }
        //开启搜索
        mBluetoothAdapter.startDiscovery();
        progressBar.setVisibility(View.VISIBLE);
        devNames.clear();
        devices.clear();
    }

    private void initView() {
        progressBar = findViewById(R.id.progressbar);
        findViewById(R.id.bt_search).setOnClickListener(this);
        ListView listView2 = findViewById(R.id.bondDev_list);
        ListView listView = findViewById(R.id.devices_list);
        devAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, devNames);
        listView.setAdapter(devAdapter);
        listView.setOnItemClickListener(this);
        bonDevAdapter = new DevAdapter();
        // bonDevAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, bondeDevNames);
        listView2.setAdapter(bonDevAdapter);
        listView2.setOnItemClickListener(this);
        listView2.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                BluetoothDevice device = bondeDevices.get(position);
                if (device != null && device.getBondState() == 12 || device.getBondState() == 11) {
                    showCancelBondeDialog(device);
                }
                return true;
            }
        });
    }

    private void showCancelBondeDialog(final BluetoothDevice device) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this).setMessage("取消配对?" + device.getName())
                .setNegativeButton("NO", null).setPositiveButton("YES", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        try {
                            boolean removeBond = ClsUtils.removeBond(device.getClass(), device);
                            if (removeBond) {
                                Toast.makeText(logService, "取消配对成功!", Toast.LENGTH_SHORT).show();
                                // discovery();
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
        builder.create().show();
    }

    class DevAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return bondeDevices == null ? 0 : bondeDevices.size();
        }

        @Override
        public BluetoothDevice getItem(int position) {
            return bondeDevices.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            DevViewHolder dh = null;
            if (convertView == null) {
                convertView = getLayoutInflater().inflate(R.layout.dev_item, null);
                dh = new DevViewHolder();
                dh.tv_name = convertView.findViewById(R.id.tv_name);
                dh.bt_cnnt = convertView.findViewById(R.id.btn_cnnt);
                convertView.setTag(dh);
            } else {
                dh = (DevViewHolder) convertView.getTag();
            }
            BluetoothDevice device = getItem(position);
            dh.tv_name.setText(device.getName() + " " + device.getAddress() + " state:" + device.getBondState());
            if (LogService.hasCnntAddr != null && LogService.hasCnntAddr.equals(device.getAddress())) {
                dh.bt_cnnt.setVisibility(View.VISIBLE);
                dh.bt_cnnt.setText("已连接");
                dh.bt_cnnt.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        disConnectDev();
                        Toast.makeText(logService, "断开连接!", Toast.LENGTH_SHORT).show();
                        v.setVisibility(View.GONE);
                    }
                });
            }
            return convertView;
        }
    }

    static class DevViewHolder {
        TextView tv_name;
        Button bt_cnnt;
    }

    private final BroadcastReceiver receiver = new BroadcastReceiver() {
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
        @Override
        public void onReceive(Context context, Intent intent) {
            if (DEBUG) Log.v(TAG, "ACTION:" + intent.getAction());
            if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

                Log.v(TAG, device.getName() + ":" + device.getAddress() + ",state:" + device.getBondState() + "," + device.getType());
                if (device.getBondState() != BluetoothDevice.BOND_BONDED || device.getBondState() != BluetoothDevice.BOND_BONDING) {
                    devNames.add(device.getName() + ":" + device.getAddress());
                    devices.add(device);
                    devAdapter.notifyDataSetChanged();
                }
                updateBondDevlist();
            } else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) {
                //开始搜索

            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) {
                //已搜素完成
                progressBar.setVisibility(View.GONE);
            } else if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) {
                Toast.makeText(context, "" + intent.getAction(), Toast.LENGTH_SHORT).show();
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
                        BluetoothDevice.ERROR);
                Log.v(TAG, "PAIRING type=" + type); //3
                if (type == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION || type == 4 || type == 5) {
                    int pairingKey = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY,
                            BluetoothDevice.ERROR);
                    Log.v(TAG, "pairingKey=" + pairingKey);
                }
                try {
                    abortBroadcast();
                   // if (true) return;
                    boolean pair = ClsUtils.setPin(device.getClass(), device, "1234");
                    //1.确认配对
                    ClsUtils.setPairingConfirmation(device.getClass(), device, true);
                    if (DEBUG) Log.v(TAG, "pair=" + pair + ",state:" + device.getBondState());

                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else if (BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
                int connectState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1);
                if (DEBUG) Log.v(TAG, "++++connectState=" + connectState);
            } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
                if (DEBUG) Log.v(TAG, "++++ACTION_BOND_STATE_CHANGED");
                final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                int state = device.getBondState();
                if (DEBUG) Log.v(TAG, device.getName() + "+++++bond state:" + state);
                if (state == BluetoothDevice.BOND_BONDED) {
                    if (Build.VERSION.SDK_INT < 23) {
                        int deviceClass = device.getBluetoothClass().getDeviceClass();
                        int majorDeviceClass = device.getBluetoothClass().getMajorDeviceClass();
                        if (DEBUG) Log.v(TAG, "deviceClass=" + deviceClass);
                        if (DEBUG)Log.v(TAG, "MajorDeviceClass=" + majorDeviceClass);
                        if(deviceClass==1344&&majorDeviceClass==1280) //  PERIPHERAL_KEYBOARD&&PERIPHERAL 外围设备  PROFILE_HID类型 3
                        cnntToInputDevice(device);
                    }
                    if (!containsThisDev(bondeDevices, device)) {
                        bondeDevices.add(device);
                        bonDevAdapter.notifyDataSetChanged();
                    }
                    if (containsThisDev(devices, device)) {
                        int i = devices.indexOf(device);
                        devices.remove(device);
                        devNames.remove(i);
                        devAdapter.notifyDataSetChanged();
                    }
                    mBluetoothAdapter.cancelDiscovery();
                } else if (state == BluetoothDevice.BOND_NONE) {
                    if (containsThisDev(bondeDevices, device)) {
                        bondeDevices.remove(device);
                        bonDevAdapter.notifyDataSetChanged();
                    }
                    if (!containsThisDev(devices, device)) {
                        devices.add(device);
                        devNames.add(device.getName() + ":" + device.getAddress());
                        devAdapter.notifyDataSetChanged();
                    }
                }
            }

        }
    };
    private void cnntToInputDevice(final BluetoothDevice device) {
        if (DEBUG) Log.v(TAG, "===cnntToInputDevice==");
        final int INPUT_DEVICE = 4; // this is hidden memeber in BluetoothDevice
       // BluetoothAdapter.getDefaultAdapter().closeProfileProxy();  调用此方法关闭代理服务
        BluetoothAdapter.getDefaultAdapter().getProfileProxy(BleLogMonitorAty.this, new BluetoothProfile.ServiceListener() {
            @Override
            public void onServiceConnected(int profile, BluetoothProfile proxy) {
                Class<?> clazz = null;
                try {
                    clazz = Class.forName("android.bluetooth.BluetoothInputDevice");
                    Object obj = clazz.cast(proxy);
                    Method connectMethod = clazz.getDeclaredMethod("connect", BluetoothDevice.class);
                    boolean resultCode = (boolean) connectMethod.invoke(obj, device);
                    Method setPriority = clazz.getDeclaredMethod("setPriority", BluetoothDevice.class, int.class);
                    setPriority.invoke(obj, device, 1000);
                    if (DEBUG) Log.v(TAG, "cnntToInputDevice  resultCode=" + resultCode);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onServiceDisconnected(int profile) {
                if (DEBUG) Log.d("wtf", "onservice disconnected " + profile);
            }
        }, INPUT_DEVICE);
    }
    
    private boolean containsThisDev(List<BluetoothDevice> deviceList, BluetoothDevice device) {
        Log.v(TAG, "containsThisDev deviceList=" + deviceList.size());
        if (deviceList == null || deviceList.size() < 1) return false;
        for (int i = 0; i < deviceList.size(); i++) {
            if (deviceList.get(i).getAddress().equals(device.getAddress()))
                return true;
        }
        return false;
    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        switch (parent.getId()) {
            case R.id.devices_list:
                if (mBluetoothAdapter.isDiscovering())
                    mBluetoothAdapter.cancelDiscovery();
                curDevice = devices.get(position);
                try {
                    boolean bond = ClsUtils.createBond(curDevice.getClass(), curDevice);
                    if (bond) {
                        Toast.makeText(logMonitorAty, "配对成功!", Toast.LENGTH_SHORT).show();
                        updateBondDevlist();
                    } else {
                        Toast.makeText(logMonitorAty, "配对失败!", Toast.LENGTH_SHORT).show();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
            case R.id.bondDev_list:
                BluetoothDevice bondeDev = bondeDevices.get(position);
                if (logService != null) {
                    if (LogService.hasCnntAddr != null && !bondeDev.getAddress().equals(LogService.hasCnntAddr)) {
                        Toast.makeText(BleLogMonitorAty.this, "请先断开上一个设备", Toast.LENGTH_SHORT).show();
                        return;
                    }
                    logService.connectDev(bondeDev);
                }
                updateBondDevlist();
                // logFragment = new BleLogFragment();
                // getSupportFragmentManager().beginTransaction().add(R.id.framecontent, logFragment, "LOG_FRAGMENT").addToBackStack("LF").commit();
                break;
        }
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        logMonitorAty = null;
        ActivityMger.removeActivity(this);
        if (DEBUG) Log.v(TAG, "==onDestroy");
        unregisterReceiver(receiver);
        unbindService(conn);

    }

    private LogService logService;
    public ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            logService = ((LogService.MyBinder) service).getService();

            logService.setDeviceCallback(new LogService.DeviceCallback() {
                @Override
                public void connect(BluetoothDevice remote) {
                    if (DEBUG) Log.v(TAG, "Dev connect ..." + LogService.hasCnntAddr);
                    recLog("Dev connect ...");
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            updateBondDevlist();
                            logFragment = new BleLogFragment();
                            getSupportFragmentManager().beginTransaction().add(R.id.framecontent, logFragment, "LOG_FRAGMENT").addToBackStack("LF").commit();
                        }
                    });


                }

                @Override
                public void disConnect(BluetoothDevice remote) {
                    if (DEBUG) Log.v(TAG, "Dev disConnect ...");
                    recLog("Dev disConnect ...");
                }

                @Override
                public void recLog(final String log) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            if (logFragment != null)
                                logFragment.recLog(log);
                        }
                    });
                }
            });
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    public void disConnectDev() {
        if (logService != null)
            logService.disConnectDev();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt_search:
                if (mBluetoothAdapter.isDiscovering()) {
                    mBluetoothAdapter.cancelDiscovery();
                    ((Button) v).setText("搜索设备");
                } else {
                    discovery();
                    ((Button) v).setText("停止搜索");
                }

                break;
            default:
                break;
        }
    }

蓝牙配对流程

1.搜索蓝牙 使用api  startDiscovery;

2.绑定使用工具类  ClsUtils的createBond,这个在网上都能找到

boolean bond = ClsUtils.createBond(curDevice.getClass(), curDevice);
if (bond) {
    Toast.makeText(logMonitorAty, "配对成功!", Toast.LENGTH_SHORT).show();
    updateBondDevlist();
} else {
    Toast.makeText(logMonitorAty, "配对失败!", Toast.LENGTH_SHORT).show();
}

我测试用的是一个蓝牙手柄,用于拍照的,类型属于 profile HID,一般输入类型的都是使用这种接口协议,类似蓝牙键盘,游戏手柄,参考博客https://blog.csdn.net/ZBJDSBJ/article/details/47123595

但是我遇到一个情况,就是直接使用绑定createBond的反射方法执行绑定,在MTK6.0上(其他品牌设备6.0没测)可以直接配对并且连接上蓝牙手柄,而5.1上就只能绑定蓝牙手柄而已,没有连接上,就不能对设备进行输入操作,必须还得使用蓝牙Profile  ,通过BluetoothAdapter.getDefaultAdapter().getProfileProxy进行连接,但是这种类似蓝牙手柄属于输入蓝牙类型,就是INPUT_DEVICE = 4 ,从BluetoothProfile可以找到,发现是hide类型的,而且还需要用的一个BluetoothInputDevice类,也是个系统hide类型,感谢国外溢出论坛的大佬们,https://stackoverflow.com/questions/27504900/android-bluetooth-paring-input-device ,通过反射得到BluetoothInputDevice并进行连接

private void cnntToInputDevice(final BluetoothDevice device) {
    if (DEBUG) Log.v(TAG, "===cnntToInputDevice==");
    final int INPUT_DEVICE = 4; // this is hidden memeber in BluetoothDevice
   // BluetoothAdapter.getDefaultAdapter().closeProfileProxy();  调用此方法关闭代理服务
    BluetoothAdapter.getDefaultAdapter().getProfileProxy(BleLogMonitorAty.this, new BluetoothProfile.ServiceListener() {
        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            Class<?> clazz = null;
            try {
                clazz = Class.forName("android.bluetooth.BluetoothInputDevice");
                Object obj = clazz.cast(proxy);
                Method connectMethod = clazz.getDeclaredMethod("connect", BluetoothDevice.class);
                boolean resultCode = (boolean) connectMethod.invoke(obj, device);
                Method setPriority = clazz.getDeclaredMethod("setPriority", BluetoothDevice.class, int.class);
                setPriority.invoke(obj, device, 1000);
                if (DEBUG) Log.v(TAG, "cnntToInputDevice  resultCode=" + resultCode);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

还有值得一提的是,当设备收到蓝牙手柄的主动配对请求是,会收到广播BluetoothDevice.ACTION_PAIRING_REQUEST

此时我们可以监听此广播,想要实现自动配对的话就调用以下两句代码就可以了

boolean pair = ClsUtils.setPin(device.getClass(), device, "1234");
//1.确认配对
ClsUtils.setPairingConfirmation(device.getClass(), device, true);
if (DEBUG) Log.v(TAG, "pair=" + pair + ",state:" + device.getBondState());

以上博客简单描述,只为了以后记住这些知识点,同时帮助那些还在为经典蓝牙配对有疑问的童鞋,附上ClsUtils代码

public class ClsUtils {
    /**
     * 与设备配对 参考源码:platform/packages/apps/Settings.git
     * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
     */
    static public boolean createBond(Class btClass, BluetoothDevice btDevice) throws Exception {
        Method createBondMethod = btClass.getMethod("createBond");
        Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice);
        return returnValue.booleanValue();
    }

    /**
     * 与设备解除配对 参考源码:platform/packages/apps/Settings.git
     * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
     */
    static public boolean removeBond(Class<?> btClass, BluetoothDevice btDevice) throws Exception {
        Method removeBondMethod = btClass.getMethod("removeBond");
        Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice);
        return returnValue.booleanValue();
    }

    static public boolean setPin(Class<? extends BluetoothDevice> btClass, BluetoothDevice btDevice, String str) throws Exception {
        try {
            Method removeBondMethod = btClass.getDeclaredMethod("setPin", new Class[]{byte[].class});
            Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice,
                    new Object[]
                            {str.getBytes()});
            Log.e("returnValue", "" + returnValue);
        } catch (SecurityException e) {
            // throw new RuntimeException(e.getMessage());
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // throw new RuntimeException(e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return true;

    }

    // 取消用户输入
    static public boolean cancelPairingUserInput(Class<?> btClass, BluetoothDevice device) throws Exception {
        Method createBondMethod = btClass.getMethod("cancelPairingUserInput");
//        cancelBondProcess(btClass, device);
        Boolean returnValue = (Boolean) createBondMethod.invoke(device);
        return returnValue.booleanValue();
    }

    // 取消配对
    static public boolean cancelBondProcess(Class<?> btClass, BluetoothDevice device) throws Exception {
        Method createBondMethod = btClass.getMethod("cancelBondProcess");
        Boolean returnValue = (Boolean) createBondMethod.invoke(device);
        return returnValue.booleanValue();
    }

    //确认配对

    static public void setPairingConfirmation(Class<?> btClass, BluetoothDevice device, boolean isConfirm) throws Exception {
        Method setPairingConfirmation = btClass.getDeclaredMethod("setPairingConfirmation", boolean.class);
        setPairingConfirmation.invoke(device, isConfirm);
    }


    /**
     *
     * @param clsShow
     */
    static public void printAllInform(Class clsShow) {
        try {
            // 取得所有方法
            Method[] hideMethod = clsShow.getMethods();
            int i = 0;
            for (; i < hideMethod.length; i++) {
                Log.e("method name", hideMethod[i].getName() + ";and the i is:"+ i);
            }
            // 取得所有常量
            Field[] allFields = clsShow.getFields();
            for (i = 0; i < allFields.length; i++) {
                Log.e("Field name", allFields[i].getName());
            }
        } catch (SecurityException e) {
            // throw new RuntimeException(e.getMessage());
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // throw new RuntimeException(e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

忘记还有个蓝牙连接,以上针对蓝牙手柄,配对就自动连接了,对应其他的蓝牙设备模块,还得主动连接

1.先定义好UUID

2.BluetoothSocket  socket=remote.createRfcommSocketToServiceRecord(定义的uuid);

3.socket.connect(), socket.getInputstream()获取流来读取对端设备发来的数据

4.当然对端设备需要mBluetoothAdapter.listenUsingRfcommWithServiceRecord (uuid)来监听连接接入,使用同一个UUID即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值