Android开发之蓝牙通信(三)

Android BLE设备蓝牙通信框架BluetoothKit 这个框架源于目前一个智能家居APP项目,是蓝牙部分基础模块,用于蓝牙设备扫描、连接、通知等。由于目前接入的蓝牙设备大多为低功耗蓝牙设备,所以本框架暂不支持经典蓝牙的连接。项目地址: https://github.com/dingjikerbo/BluetoothKit先介绍本框架的特点: 一、支持经典蓝牙和BLE蓝牙混合扫描,支持自定义扫描策略 作为智能家居平台,接入的设备包括经典蓝牙和BLE, 阅读详情

接着上篇蓝牙通信往下写,若有不对还请指出,大家共同进步。

Android开发之蓝牙通信(一)

Android开发之蓝牙通信(二)

Android开发之蓝牙通信(三)


前两篇提到了蓝牙开发的基本使用流程以及通信框架BluetoothKit,博主的蓝牙通信项目已经迭代了几个版本了,决定最后来做一个总结,主要是蓝牙通信协议相关,蓝牙通信一次限制传输字节大小,虽然蓝牙支持增量,但并不能保证成功率(可能硬件并没有增量),最好的解决办法就是切片,这里博主以下面协议为例:

  • type 请求类型:命令和一般请求

  • readHeadType 蓝牙读头类型

  • api 请求接口

  • pieceCount 当前分片是第几个

  • bodyLength 当前请求body长度

  • body 请求json

首先定义常量以及注解类型,这里以type为例,api 、readHeadType雷同(注解部分可以忽略)


/**
 * Created by idea on 2017/11/13.
 */

public class Type {

    public static final byte COMMAND = 0x00;

    public static final byte REQUEST = 0x01;



}


/**
 * Created by idea on 2017/11/13.
 */

@ByteDef({Type.COMMAND,Type.REQUEST})
@Retention(RetentionPolicy.SOURCE)
public @interface BluetoothType {

}

把协议文档编程代码后,开始编写传输的Message封装,为了使用方便,提供一个builder


/**
 * Created by idea on 2017/11/13.
 */

public class Message {

    private byte type;

    private byte readHeadType;

    private byte api;

    private byte pieceCount;

    private byte bodyLength;

    private byte[] body = null;

    private Message(){

    }

    public byte getType() {
        return type;
    }

    public void setType(byte type) {
        this.type = type;
    }

    public byte getReadHeadType() {
        return readHeadType;
    }

    public void setReadHeadType(byte readHeadType) {
        this.readHeadType = readHeadType;
    }

    public byte getApi() {
        return api;
    }

    public void setApi(byte api) {
        this.api = api;
    }

    public byte getPieceCount() {
        return pieceCount;
    }

    public void setPieceCount(byte pieceCount) {
        this.pieceCount = pieceCount;
    }

    public byte getBodyLength() {
        return bodyLength;
    }

    public void setBodyLength(byte bodyLength) {
        this.bodyLength = bodyLength;
    }

    public byte[] getBody() {
        return body;
    }

    public void setBody(byte[] body) {
        this.body = body;
    }

    public ArrayList<byte[]> getMessages(){
        ArrayList<byte[]> result = new ArrayList<>();
        if(body!=null&&body.length>15){
            boolean flag = body.length % 15 == 0;
            int count = body.length / 15 + (flag ? 0 : 1);
            for (int i = 0; i < count; i++) {
                byte[] message = null;
                if (i == count - 1) {
                    message = new byte[body.length - 15 * i+5];
                    message[0] = type;
                    message[1] = readHeadType;
                    message[2] = api;
                    message[3] = (byte)i;
                    message[4] = (byte) (body.length-15*i);
                    System.arraycopy(body, 15 * i, message, 5, body.length - 15 * i);
                    result.add(message);
                } else {
                    message = new byte[20];
                    message[0] = type;
                    message[1] = readHeadType;
                    message[2] = api;
                    message[3] = (byte) i;
                    message[4] = (byte)15;
                    System.arraycopy(body, 15 * i, message, 5, 15);
                    result.add(message);
                }
            }

        }else{
            int bodyLength = body==null?0:body.length;
            byte[] message = new byte[5+bodyLength];
            message[0] = type;
            message[1] = readHeadType;
            message[2] = api;
            message[3] = 0x00;
            message[4] = (byte) bodyLength;
            if(bodyLength>0){
                System.arraycopy(body,0,message,5,bodyLength);
            }
            result.add(message);
        }
        return result;
    }

    public interface IBuilder{

        Message.IBuilder setType(@BluetoothType byte type);

        Message.IBuilder setReadHeadType(@BluetoothReadHeadType byte type);

        Message.IBuilder setApi(@BluetoothApi byte type);

        Message.IBuilder setBody(byte[] bytes);

        ArrayList<byte[]> creat();
    }

    public static class Builder implements Message.IBuilder {

        private Message requstMessage;

        private Builder(){
            requstMessage = new Message();
        }

        public static Message.Builder newInstance() {
            return new Message.Builder();
        }


        @Override
        public Message.IBuilder setType(@BluetoothType byte type) {
            requstMessage.setType(type);
            return this;
        }

        @Override
        public Message.IBuilder setReadHeadType(@BluetoothReadHeadType byte readHeadType) {
            requstMessage.setReadHeadType(readHeadType);
            return this;
        }

        @Override
        public Message.IBuilder setApi(@BluetoothApi byte api) {
            requstMessage.setApi(api);
            return this;
        }

        @Override
        public Message.IBuilder setBody(byte[] bytes) {
            requstMessage.setBody(bytes);
            return this;
        }

        @Override
        public ArrayList<byte[]> creat() {
            return requstMessage.getMessages();
        }
    }
}

代码调用发送消息示例如下


ArrayList<byte[]> messages = Message.Builder.newInstance()
                .setType(Type.REQUEST)
                .setApi(Api.USER_LOGIN)
                .setReadHeadType(ReadHeadType.CHILD)
                .setBody(body)
                .creat();

 public void write(ArrayList<byte[]> messages) {
        for(int i=0;i<messages.size();i++){
            this.mClient.write(mac, Constant.SERVICE_UUID, Constant.WRITE_CHARA, messages.get(i), new BleWriteResponse() {
                public void onResponse(int code) {
                    if (code == Code.REQUEST_SUCCESS) {
                        Logger.e("发送成功");
                    } else {
                        onFail("发送数据失败");
                    }
                }
            });
        }
    }

很多时候我们发送数据后都会需要响应,那么此时我们需要编写响应实体类Response


/**
 * Created by idea on 2017/11/13.
 */

public class Response {

    private boolean isResponseFinished;

    private byte type;

    private byte readHeadType;

    private byte api;

    private byte pieceCount ;

    private byte bodyLength;

    private byte[] body = null;

    public byte getType() {
        return type;
    }

    public void setType(byte type) {
        this.type = type;
    }

    public byte getReadHeadType() {
        return readHeadType;
    }

    public void setReadHeadType(byte readHeadType) {
        this.readHeadType = readHeadType;
    }

    public byte getApi() {
        return api;
    }

    public void setApi(byte api) {
        this.api = api;
    }

    public byte getPieceCount() {
        return pieceCount;
    }

    public void setPieceCount(byte pieceCount) {
        this.pieceCount = pieceCount;
    }

    public byte getBodyLength() {
        return bodyLength;
    }

    public void setBodyLength(byte bodyLength) {
        this.bodyLength = bodyLength;
    }

    public byte[] getBody() {
        return body;
    }

    public void setBody(byte[] body) {
        this.body = body;
    }


    ArrayList<byte[]> responses = new ArrayList<>();

    public boolean isResponseFinished() {
        return isResponseFinished;
    }

    public void setIsResponseFinished(boolean isResponseFinished) {
        this.isResponseFinished = isResponseFinished;
        if(isResponseFinished){
            formatBytes();
        }
    }

    public Response(byte[] bytes) {
        addResponse(bytes);
    }

    public void addResponse(byte[] bytes){
        responses.add(bytes);
        //TODO 这里根据服务器约定协议判定是否接收完成数据字段来设定
        //setIsResponseFinished(boolean);
    }

    private Response formatBytes() {

        type = responses.get(0)[0];
        readHeadType = responses.get(0)[1];
        api = responses.get(0)[2];

        int length= 0;
        for(int i=0;i<responses.size();i++){
            length+=responses.get(i).length-4;
        }

        bodyLength = (byte) length;

        body = new byte[bodyLength];
        for(int i=0;i<responses.size();i++){
            System.arraycopy(responses.get(i),5,body,15*i,responses.get(i)[5]);
        }
        return this;
    }
}

收到报文后解析如下

 mClient.notify(mac, Constant.SERVICE_UUID, Constant.READ_CHARA, new BleNotifyResponse() {
                        public void onNotify(UUID service, UUID character, byte[] value) {
                            logE("接收到数据响应");
                            parseResponse(value);
                        }

                        public void onResponse(int code) {

                         //************略***************
                        }
                    });

 private void parseResponse(byte[] value) {

        if (responseMessage == null) {
            responseMessage = new ResponseMessage(value);
        } else if (!responseMessage.isResponseFinished()) {
            responseMessage.addResponse(value);
        }

        if (responseMessage.isResponseFinished()) {
            //TODO 
            logE("》》》》》》》》》》》》》》》》》》》》》》》》》 接受完成响应数据");

            switch (responseMessage.getApi()) {

                case Api.LOGIN:
                    if (responseMessage.getBody()[0] == 0x00) {
                        responseMessage = null;
                        //登陆成功
                        onSuccess();   
                    } else {
                        onFail("上传数据失败");
                    }
                    break;
            }
        }
    }

以上内容为蓝牙通信协议切片封装相关,蓝牙相关项目已经结束,接着继续搞音视频项目,待博主日后再来约几篇相关blog。

Android BLE设备蓝牙通信框架BluetoothKit,flutter中文网 一、统一解决Android蓝牙通信过程中的兼容性问题二、提供尽可能简单易用的接口,屏蔽蓝牙通信中的技术细节,只开放连接,读写,通知等语义。三、实现串行化任务队列,统一处理蓝牙通信中的失败以及超时,支持可配置的容错处理四、统一管理连接句柄,避免句柄泄露五、方便监控各设备连接状态,比如手机可同时连接的设备数有限,可以检测设备连接活跃度,在尽可能维持连接的情况下,将最不活跃的设备自动断开。六、支持多进程APP架构下蓝牙连接的管理七、支持拦截所有对蓝牙原生接口的调用。 阅读详情

相关推荐

基于QT的安卓手机蓝牙APP开发

最近做项目,需要开发安卓应用,实现串口的收发,目测CH340G在安卓手机上非常麻烦,而且驱动都是Java版本的, 就没选择,博主在大二的时候学习过Java SE基本的语法,写过一些小程序就放弃了Java的道路。最后选择了蓝牙无线透传模块,实现串口通信。现在Qt跨平台支持安卓,是在是令人欣喜。在网上找资料,用Qt on Android做蓝牙驱动的几乎没有,也没有相关例程,所以准备撰写此文,献给广大嵌入式程序员们。 一、软硬件平台 1.1 硬件平台 1、蓝牙:HC-05,(淘宝上有卖),它的接口就是跟串口一样的

果果小师弟的博客 1万+

Android 蓝牙通信

Android 蓝牙通信一、现在的无线通信方案及差别二、蓝牙传输功能开发1、准备阶段1)权限2)蓝牙的开启/关闭/状态判断设备是支持蓝牙获取蓝牙状态启动/关闭2、查找设备1) 让设备可见2) 查找设备首先启动查找设备然后是通过广播来获取到查找的结果。3) 绑定设备对于已经绑定的设备对于未绑定的设备3、通讯过程通讯的两个设备蓝牙一定要绑定的吗?总结 一、现在的无线通信方案及差别 上图是2018年甚至是更老的时候的方案 除以上之外还有 红外线传输 但是红外线是直线的传输方式(单向传输?)不适应无线通讯 二、蓝

csdn_pun的博客 3316

Android 蓝牙开发(一)蓝牙通信

随着可穿戴设备的流行,研究蓝牙是必不可少的一门技术了。 总结了下蓝牙开发使用的一些东西分享一下。 首先需要AndroidManifest.xml文件中添加操作蓝牙的权限。 <uses-permissionandroid:name="android.permission.BLUETOOTH" /> 允许程序连接到已配对的蓝牙设备。 <uses-permissionandro

奋斗的菜鸟ing 7万+

Android硬件通信之 蓝牙通信

蓝牙通信

qq_29848853的博客 3845

Android基于Modbus RTU协议通过蓝牙与蓝牙串口开发板通信

Android基于ModbusRtu协议通过蓝牙与蓝牙串口通信实现开发板读写前言实现原理代码实现 前言 最近接到一位老师的项目,他们公司要求利用Android基于ModbusRtu协议通过蓝牙与蓝牙串口通信实现开发板读写,然后通过开发板控制硬件设备从而实现Android移动设备与硬件交互。所以今天我给大家分享下Android基于ModbusRtu协议的蓝牙通信。当然Android也可以直接通过串口利用ModbusRtu协议与开发板通信,或者基于Modbus TCP通信。至于非蓝牙非Modbus协议通信,大家

weixin_42644340的博客 7359

Android 蓝牙通信及WiFi开发

在我们正常的Android蓝牙功能开发步骤中,一般要经过系统权限和蓝牙开关状态监测、设备扫描、设备连接、蓝牙数据通信这几个过程。 在Android 4.3系统之后,我们可以使用蓝牙4.0(低功耗蓝牙),它最主要的特点是低功耗,普及率高。现在所说的蓝牙设备,大部分都是在说4.0设备,ble也特指4.0设备。 在4.0之前重要的版本有2.1版本-基本速率/增强数据率(BR/EDR)和3.0 高速蓝牙版

Bug不知所起,一往而深。 5565

Android蓝牙开发(三)之蓝牙通信

上篇文章:https://blog.youkuaiyun.com/huangliniqng/article/details/82187966 上篇文章我们讲解了,蓝牙配对和蓝牙连接相关知识,还没有了解的朋友可先移步上篇文章。 1.蓝牙通信简介 无论是做Java还是Android开发的朋友肯定都比较熟悉Socket的连接,在java中通信用的是Socket,同样的蓝牙之间通信(这里说的是经典蓝牙)方式...

黄林晴 2万+

基于Android的蓝牙聊天应用开发实战(BluetoothChat)

字段长度类型说明Length4int载荷长度Timestamp8long时间戳Type1byte消息类型(0=文本,1=心跳)PayloadNbyte[]实际内容打包函数:接收端先读13字节头部,再根据Length读取完整载荷,完美解决粘包问题。经过这一整套流程,你会发现:✅经典蓝牙仍然是实时通信的最佳选择✅。

weixin_42601702的博客 766

Android 蓝牙通信开发(三) 蓝牙通信

蓝牙通信是我们日常生活中比较方便的一种通信技术,Android从2.0版本的SDK就开始支持蓝牙。对开发人员来说,应用程序中蓝牙还是一种用来创建点对点连接通信的简单而高效的方式。

magician_Coder的博客 1707

Android蓝牙通信框架BluetoothKit.zip

BluetoothKit是一款功能强大的Android蓝牙通信框架,支持经典蓝牙和低功耗蓝牙设备混合扫描,提供了一系列简单易用的接口用于低功耗蓝牙设备的连接,数据读写,通知等。 特点 一、支持经典蓝牙和BLE蓝牙混合扫描,支持自定义扫描策略  作为智能家居平台,接入的设备包括经典蓝牙和BLE,因此扫描设备时需要两者混合进行,而设备扫描场景不同,扫描策略也会不一样,因此需要支持扫描策略可配置。 二、充分解决了Android中BLE兼容性和稳定性问题  Android系统对蓝牙4.0支持得并不完善,有许多bug, BluetoothKit很好地解决了其中大部分已知的问题。  三、简单易用,接口简洁明了  BluetoothKit采用异步串行化策略处理所有设备操作,并支持任务超时及出错重试。  技术 一、实现了一个完整的跨进程异步任务队列,支持任务超时、出错重试及防御队列溢出 二、拦截并Hook系统层蓝牙Binder,实现对所有蓝牙设备通信的监控,当同时连接设备数过多时会自动断掉活跃度最低的设备 三、整个框架封装在一个service中,可灵活指定service所在进程。通过client与service通信,client可源于多个不同进程,因此适用于多进程架构的app 四、屏蔽了接口异步回调可能持有调用端Activity引用导致的内存泄露 五、利用动态代理自动将所有操作封闭在工作线程,所以整个框架无锁 使用 // 首先,需要按如下方式初始化BluetoothClient: BluetoothClient mClient = BluetoothClient.getInstance(context); // 扫描设备:支持经典蓝牙和BLE设备混合扫描,可自由定制扫描策略如下: SearchRequest request = new SearchRequest.Builder() .searchBluetoothLeDevice(3000, 3) .searchBluetoothClassicDevice(5000) .searchBluetoothLeDevice(2000) .build(); mClient.search(request, new SearchResponse() { @Override public void onSearchStarted() { } @Override public void onDeviceFounded(SearchResult device) { } @Override public void onSearchStopped() { } @Override public void onSearchCanceled() { } }); // 停止蓝牙扫描 mClient.stopSearch(); // BLE设备连接 mClient.connect(MAC, new BleConnectResponse() { @Override public void onResponse(int code, Bundle data) { if (code == REQUEST_SUCCESS) { } } }); // BLE设备断开连接 mClient.disconnect(MAC); // 读取BLE设备 mClient.read(MAC, serviceUUID, characterUUID, new BleReadResponse() { @Override public void onResponse(int code, byte[] data) { if (code == REQUEST_SUCCESS) { } } }); // 写BLE设备 mClient.write(MAC, serviceUUID, characterUUID, bytes, new BleWriteResponse() { @Override public void onResponse(int code) { if (code == REQUEST_SUCCESS) { } } }); // 打开设备通知 mClient.notify(MAC, serviceUUID, characterUUID, new BleNotifyResponse() { @Override public void onNotify(UUID service, UUID character, byte[] value) { } @Override public void onResponse(int code) { if (code == REQUEST_SUCCESS) { } } }); // 关闭设备通知 mClient.unnotify(MAC, serviceUUID, characterUUID, new BleUnnotifyResponse() { @Override public void onResponse(int code) { if (code == REQUEST_SUCCESS) { } } }); // 读取rssi mClient.readRssi(MAC, new BleReadRssiResponse() { @Override public void onResponse(int code, Integer rssi) { if (code == REQUEST_SUCCESS) { } } }); 标签:BluetoothKit

例题讲解拉格朗日乘子法、线性可分支持向量机(SVM)的推导

支持向量机(Support Vector Machine, SVM)的简单推导及例题

zzzssszzzzsz的博客 5681

【安卓可穿戴设备APP开发】BLE蓝牙、MySQL数据库、websocket网络通信的综合应用案例

本部分将详细介绍每个功能类的具体使用和功能实现,由于代码量较大,部分代码请参阅者自行对照源码查看~

weixin_45743645的博客 1169

【故障处理】- ORA-01722

在执行升级脚本@?/rdbms/admin/catupgrd.sql时报错ora-01722。

weixin_39099660的博客 854

android 传统蓝牙开发(三):蓝牙设备连接及通信

蓝牙设备连接及通信 1.设备连接,服务器端开启线程一直等待连接,客户端点击某个目标设备,关闭服务器线程监听,并开启线程,发出连接请求。 注意:客户端连接前,一定cancelDiscovery() // 蓝牙已开启 if (bluetoothAdapter.isEnabled()) { showBondDevice(); // 默认开启服务线程监听 if (serverThre...

lumingzhang的博客 1610

AWR射频仿真基本操作(一)

这里只讲述一个基本的操作: 软件是AWR 14版本

钢丝 2万+

Abaqus子程序开发避坑指南:从UMESHMOTION到齿轮磨损分析实战

本文深入探讨Abaqus子程序开发中的关键技术与避坑指南,重点解析UMESHMOTION在齿轮磨损分析中的实战应用。从开发环境搭建、调试框架构建到性能优化和大规模计算,提供系统化的解决方案。通过真实案例展示如何避免内存泄漏、网格畸变等常见问题,并介绍数据驱动的磨损预测等前沿技术,帮助工程师提升仿真精度与效率。

weixin_30892037的博客 87

android编译51单片机,GitHub - whaoming/SCMAndroidCommunicate: Android与51单片机的信息交互...

SCMAndroidCommunicateAndroid与51单片机的通信前言本篇文章将围绕App与单片机的蓝牙通信来说说lz最近进行开发的一些方案与思考此文分为三部分:单片机的PWM与串口通信Android的蓝牙开发单片机与Android App的通信方案预览视频观看:环境单片机单片机:STC89C52c语言编写环境:Keil uVision4烧录:stc-isp-15xx-v6.82Andro...

weixin_42493720的博客 718

STM32 使用IO口模拟I2C时序

上一篇《I2C协议详解》 我们了解了I2C的操作流程,这一篇,我们就使用I2C,来对EEPROM进行操作吧。 我们做两种选择: 1.时序由IO口模拟高低电平,需要了解协议并按照协议操作相应的IO口。 2.时序由硬件自行产生,不需要人工干预; 由硬件产生的I2C时序,我们借助Stm32Cube配置实现便可,我们这一篇,抛开Stm32Cube,手撕代码,根据I2C的时序,一步步地实现I2C对...

8937

Android 蓝牙开发详解与实战:基于 BluetoothChat 示例的完整解析

本文深入解析 Android SDK 中的经典蓝牙通信示例 BluetoothChat,从项目结构、核心代码到布局文件进行完整剖析。通过主界面 Activity、后台服务、设备列表、权限配置等模块的详细讲解,帮助开发者快速掌握 Android 蓝牙开发的核心流程与关键技术,实现设备间的稳定数据传输

xingfuyisheng的专栏 1万+

双传声器法测量吸声材料的吸声系数(公式推导与解释)

在双传声器法中,通过测量两个位置的声压 p1和 p2​,可以分离出入射波和反射波的声压分量。双传声器法一般在阻抗管中进行,阻抗管的一端为平面波声源,另一端放置待测材料。两个传声器放置在靠近材料的阻抗管内,用于测量管道内的声压。双传声器法是一种常用于测量材料吸声系数的实验方法,通过在管道内使用两个传声器测量声波的入射和反射,来计算出材料的吸声系数。当测试样品较少时,测试时,需要交换两个传声器的位置,目的是为了相位校准。

m0_57407372的博客 1875

Oracle Primavera P6实战手册:从入门到精通的企业项目管理秘籍

本文详细介绍了Oracle Primavera P6在企业项目管理中的实战应用,从环境配置到高级进度控制技巧,帮助用户从入门到精通。通过WBS设计、资源优化和情景模拟等核心功能,提升项目管理效率。特别适合需要处理复杂项目和多团队协作的企业用户。

weixin_29235923的博客 252

Android开发之蓝牙(二)——基于BLE协议蓝牙模块通信

介绍 基本概念 Android蓝牙开发流程 app是ble与spp选择连接蓝牙模块,关于spp的可以看 介绍摘要:蓝牙4.0——BLE是近年来应用比较广泛的profile,与传统蓝牙相比,其低功耗的特征最为显著,在蓝牙模块中(CC2540),一块纽扣电池可供电半年到一年之间。

zh1豪的博客 9538

prplOS适配USB LAN网卡

本文介绍了将USB LAN网卡(如rndis0)适配到prplOS的LAN管理流程。

四儿家的小祖宗的博客 322

Android蓝牙开发—经典蓝牙详细开发流程

       Android蓝牙开发前,首先要区分是经典蓝牙开发还是BLE(低功耗)蓝牙开发,它们的开发是有区别的,如果还分不清经典蓝牙和BLE(低功耗)蓝牙的小伙伴,可以先看Android蓝牙开发—经典蓝牙和BLE(低功耗)蓝牙的区别 本文是针对经典蓝牙开发的,如果是BLE(低功耗)蓝牙开发,可以看Android蓝牙开发—BLE(低功耗)蓝牙详细开发流程 开发流程 开启蓝牙 扫描蓝牙 ...

zqf_888的博客 5万+

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Android 蓝牙开发之搜索、配对、连接、通信大全

蓝牙( Bluetooth®):是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离数据交换(使用2.4—2.485GHz的ISM波段的UHF无线电波)。蓝牙设备最多可以同时和7个其它蓝牙设备建立连接,进行通信,当然并不是每一个蓝牙都可以达到最大值。下面,我们从蓝牙的基本概念开始,一步一步开始了解蓝牙。(尊重劳动成果,转载请注明出处http://blog....

weixin_33686714的博客 5972

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

上一篇: Android开发之视频监控区域报警设定
下一篇: android开发之音视频(一)
AnalyzeSystem
博客等级 码龄11年 204粉丝 114原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值