如何让Android平台像IPC一样实现GB28181前端设备接入

简介: 好多开发者在做国标对接的时候,首先想到的是IPC摄像头,通过参数化配置,接入到国标平台,实现媒体数据的按需查看等操作。

技术背景

好多开发者在做国标对接的时候,首先想到的是IPC摄像头,通过参数化配置,接入到国标平台,实现媒体数据的按需查看等操作。


像执法记录仪等智能终端,跑在Android平台,对接GB28181平台的需求也非常大,网上相关demo也不少,但真正设计符合相关协议规范、功能完善、长时间稳定运行的并不多。基于此,我们研发了Android平台GB28181接入模块,目前功能设计,总的来说,IPC有的功能要有,IPC缺失的,但是对业务诉求有要求的,也要有,比如MobilePosition实时位置上报,实时快照、本地录像、屏幕采集、拉取RTSP或RTMP对接到GB28181平台、实时动态水印、实时音量调节、外部编码前后对接等:

0aca38d375d045c1a00c819f1abe6c0a.png


  • [视频格式]H.264/H.265(Android H.265硬编码);
  • [音频格式]G.711 A律、AAC;
  • [音量调节]Android平台采集端支持实时音量调节;
  • [H.264硬编码]支持H.264特定机型硬编码;
  • [H.265硬编码]支持H.265特定机型硬编码;
  • [软硬编码参数配置]支持gop间隔、帧率、bit-rate设置;
  • [软编码参数配置]支持软编码profile、软编码速度、可变码率设置;
  • 支持横屏、竖屏推流;
  • Android平台支持后台service推送屏幕(推送屏幕需要5.0+版本);
  • 支持纯视频、音视频PS打包传输;
  • 支持RTP OVER UDP和RTP OVER TCP被动模式;
  • 支持信令通道网络传输协议TCP/UDP设置;
  • 支持注册、注销,支持注册刷新及注册有效期设置;
  • 支持设备目录查询应答;
  • 支持心跳机制,支持心跳间隔、心跳检测次数设置;
  • 支持移动设备位置(MobilePosition)订阅和通知;
  • 支持国标GB/T28181—2016平台接入;
  • 支持语音广播及语音对讲;
  • [实时水印]支持动态文字水印、png水印;
  • [镜像]Android平台支持前置摄像头实时镜像功能;
  • [实时静音]支持实时静音/取消静音;
  • [实时快照]支持实时快照;
  • [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
  • [外部编码前视频数据对接]支持YUV数据对接;
  • [外部编码前音频数据对接]支持PCM对接;
  • [外部编码后视频数据对接]支持外部H.264数据对接;
  • [外部编码后音频数据对接]外部AAC数据对接;
  • [扩展录像功能]支持录像相关功能。

技术接口设计

接口设计这块,我们分三个部分:一个是供Publisher实例调用的接口,一个是RTP发送相关的接口,还有一个是针对语音广播这块,实现的接收RTP数据播放接口。


先说供GB28181推送实例调用的接口:

  /*+++++++++++++++GB28181相关接口+++++++++++++++*/
  /**
   * 设置GB28181 RTP Sender
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param rtp_payload_type, 对于GB28181 PS, 协议定义是96, 具体以SDP为准
   *
   * @return {0} if successful
   */
  public native int SetGB28181RTPSender(long handle, long rtp_sender_handle, int rtp_payload_type);
  /**
   * 启动 GB28181 媒体流
   *
   * @return {0} if successful
   */
  public native int StartGB28181MediaStream(long handle);
  /**
   * 停止 GB28181 媒体流
   *
   * @return {0} if successful
   */
  public native int StopGB28181MediaStream(long handle);
  /*---------------GB28181相关接口---------------*/

再说RTP发送相关的接口:

    /*
     * SmartPublisherJniV2.java
     * SmartPublisherJniV2
     *
     * WebSite: https://daniusdk.com
     * Github: https://github.com/daniulive/SmarterStreaming
     */
    /*+++++++++++++++RTP Sender相关接口+++++++++++++++*/
  /*
   * 创建RTP Sender实例
   *
   * @param reserve:保留参数传0
   *
   * @return RTP Sender 句柄,0表示失败
   */
  public native long CreateRTPSender(int reserve);
  /**
   *设置 RTP Sender传输协议
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param transport_protocol, 0:UDP, 1:TCP, 默认是UDP
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderTransportProtocol(long rtp_sender_handle, int transport_protocol);
  /**
   *设置 RTP Sender IP地址类型
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param ip_address_type, 0:IPV4, 1:IPV6, 默认是IPV4, 当前仅支持IPV4
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderIPAddressType(long rtp_sender_handle, int ip_address_type);
  /**
   *设置 RTP Sender RTP Socket本地端口
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param port, 必须是偶数,设置0的话SDK会自动分配, 默认值是0
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderLocalPort(long rtp_sender_handle, int port);
  /**
   *设置 RTP Sender SSRC
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param ssrc, 如果设置的话,这个字符串要能转换成uint32类型, 否则设置失败
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderSSRC(long rtp_sender_handle, String ssrc);
  /**
   *设置 RTP Sender RTP socket 发送Buffer大小
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param buffer_size, 必须大于0, 默认是512*1024, 当前仅对UDP socket有效, 根据视频码率考虑设置合适的值
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderSocketSendBuffer(long rtp_sender_handle, int buffer_size);
  /**
   *设置 RTP Sender RTP时间戳时钟频率
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param clock_rate, 必须大于0, 对于GB28181 PS规定是90kHz, 也就是90000
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderClockRate(long rtp_sender_handle, int clock_rate);
  /**
   *设置 RTP Sender 目的IP地址, 注意当前用在GB2818推送上,只设置一个地址,将来扩展如果用在其他地方,可能要设置多个目的地址,到时候接口可能会调整
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param address, IP地址
   * @param port, 端口
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderDestination(long rtp_sender_handle, String address, int port);
  /**
   *初始化RTP Sender, 初始化之前先调用上面的接口配置相关参数
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   *
   * @return {0} if successful
   */
  public native int InitRTPSender(long rtp_sender_handle);
  /**
   *获取RTP Sender RTP Socket本地端口
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   *
   * @return 失败返回0, 成功的话返回响应的端口, 请在InitRTPSender返回成功之后调用
   */
  public native int GetRTPSenderLocalPort(long rtp_sender_handle);
  /**
   * UnInit RTP Sender
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   *
   * @return {0} if successful
   */
  public native int UnInitRTPSender(long rtp_sender_handle);
  /**
   * 释放RTP Sender, 释放之后rtp_sender_handle就无效了,请不要再使用
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   *
   * @return {0} if successful
   */
  public native int DestoryRTPSender(long rtp_sender_handle);

最后说,RTP接收相关的接口设计:

  /*
     * SmartPlayerJniV2.java
     * SmartPlayerJniV2
     *
     * WebSite: https://daniusdk.com
     * Github: https://github.com/daniulive/SmarterStreaming
     */
    /*++++++++++++++++++RTP Receiver++++++++++++++++++++++*/
  /*
   * 创建RTP Receiver
   *
   * @param reserve:保留参数传0
   *
   * @return RTP Receiver 句柄,0表示失败
   */
  public native long CreateRTPReceiver(int reserve);
  /**
   *设置 RTP Receiver传输协议
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param transport_protocol, 0:UDP, 1:TCP, 默认是UDP
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverTransportProtocol(long rtp_receiver_handle, int transport_protocol);
  /**
   *设置 RTP Receiver IP地址类型
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param ip_address_type, 0:IPV4, 1:IPV6, 默认是IPV4
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverIPAddressType(long rtp_receiver_handle, int ip_address_type);
  /**
   *设置 RTP Receiver RTP Socket本地端口
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param port, 必须是偶数,设置0的话SDK会自动分配, 默认值是0
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverLocalPort(long rtp_receiver_handle, int port);
  /**
   *设置 RTP Receiver SSRC
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param ssrc, 如果设置的话,这个字符串要能转换成uint32类型, 否则设置失败
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverSSRC(long rtp_receiver_handle, String ssrc);
  /**
   *创建 RTP Receiver 会话
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param reserve, 保留值,目前传0
   *
   * @return {0} if successful
   */
  public native int CreateRTPReceiverSession(long rtp_receiver_handle, int reserve);
  /**
   *获取 RTP Receiver RTP Socket本地端口
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   *
   * @return 失败返回0, 成功的话返回响应的端口, 请在CreateRTPReceiverSession返回成功之后调用
   */
  public native int GetRTPReceiverLocalPort(long rtp_receiver_handle);
  /**
   *设置 RTP Receiver Payload 相关信息
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   *
   * @param payload_type, 请参考 RFC 3551
   *
   * @param encoding_name, 编码名, 请参考 RFC 3551, 如果payload_type不是动态的, 可能传null就好
   *
   * @param media_type, 媒体类型, 请参考 RFC 3551, 1 是视频, 2是音频
   *
   * @param clock_rate, 请参考 RFC 3551
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverPayloadType(long rtp_receiver_handle, int payload_type, String encoding_name, int media_type, int clock_rate);
  /**
   *设置 RTP Receiver 音频采样率
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param sampling_rate, 音频采样率
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverAudioSamplingRate(long rtp_receiver_handle, int sampling_rate);
  /**
   *设置 RTP Receiver 音频通道数
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param channels, 音频通道数
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverAudioChannels(long rtp_receiver_handle, int channels);
  /**
   *设置 RTP Receiver 远端地址
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param address, IP地址
   * @param port, 端口
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverRemoteAddress(long rtp_receiver_handle, String address, int port);
  /**
   *初始化 RTP Receiver
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   *
   * @return {0} if successful
   */
  public native int InitRTPReceiver(long rtp_receiver_handle);
  /**
   *UnInit RTP Receiver
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   *
   * @return {0} if successful
   */
  public native int UnInitRTPReceiver(long rtp_receiver_handle);
  /**
   *Destory RTP Receiver Session
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   *
   * @return {0} if successful
   */
  public native int DestoryRTPReceiverSession(long rtp_receiver_handle);
  /**
   *Destory RTP Receiver
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   *
   * @return {0} if successful
   */
  public native int DestoryRTPReceiver(long rtp_receiver_handle);
  /*++++++++++++++++++RTP Receiver++++++++++++++++++++++*/

上述接口说过之后,大家可能疑惑,信令交互呢?信令交互,我们是在Android上层实现。


如基础参数配置:

    /*** GB28181 相关参数,可以修改相关参数后测试 ***/
    GBSIPAgent     gb28181_agent_             = null;
    private int    gb28181_sip_local_port_base_  = 5060;
    private String gb28181_sip_server_id_     = "34020000002000000001";
    private String gb28181_sip_domain_        =  "3402000000";
    private String gb28181_sip_server_addr_   = "192.168.0.103";
    private int    gb28181_sip_server_port_   = 15060;
    private String gb28181_sip_user_agent_filed_  = "NT GB User Agent V1.3";
    private String gb28181_sip_username_   = "31011500991320000069";
    private String gb28181_sip_password_   = "12345678";
    private long gb28181_rtp_sender_handle_ = 0;
    private int  gb28181_rtp_payload_type_  = 96;
    private long player_handle_ = 0;
    private long rtp_receiver_handle_ = 0;
    private AtomicLong last_received_audio_data_time_ = new AtomicLong(0);
    /*** GB28181 相关参数,可以修改相关参数后测试 ***/

InitGB28181Agent()相关:

    private boolean initGB28181Agent() {
        if ( gb28181_agent_ != null )
            return  true;
        getLocation(context_);
        String local_ip_addr = IPAddrUtils.getIpAddress(context_);
        Log.i(TAG, "initGB28181Agent local ip addr: " + local_ip_addr);
        if ( local_ip_addr == null || local_ip_addr.isEmpty() ) {
            Log.e(TAG, "initGB28181Agent local ip is empty");
            return  false;
        }
        gb28181_agent_ = GBSIPAgentFactory.getInstance().create();
        if ( gb28181_agent_ == null ) {
            Log.e(TAG, "initGB28181Agent create agent failed");
            return false;
        }
        gb28181_agent_.addListener(this);
        // 必填信息
        gb28181_agent_.setLocalAddress(local_ip_addr);
        gb28181_agent_.setServerParameter(gb28181_sip_server_addr_, gb28181_sip_server_port_, gb28181_sip_server_id_, gb28181_sip_domain_);
        gb28181_agent_.setUserInfo(gb28181_sip_username_, gb28181_sip_password_);
        // 可选参数
        gb28181_agent_.setUserAgent(gb28181_sip_user_agent_filed_);
        gb28181_agent_.setTransportProtocol(gb28181_sip_trans_protocol_==0?"UDP":"TCP");
        // GB28181配置
        gb28181_agent_.config(gb28181_reg_expired_, gb28181_heartbeat_interval_, gb28181_heartbeat_count_);
        //com.gb.ntsignalling.Device gb_device = new com.gb.ntsignalling.Device("34020000001380000001", "安卓测试设备", Build.MANUFACTURER, Build.MODEL,
        //            "宇宙","火星1","火星", true);
        com.gb.ntsignalling.Device gb_device = new com.gb.ntsignalling.Device("33010752991327811433", "安卓测试设备", Build.MANUFACTURER, Build.MODEL,
                "宇宙","火星1","火星", true);
        if (mLongitude != null && mLatitude != null) {
            com.gb.ntsignalling.DevicePosition device_pos = new com.gb.ntsignalling.DevicePosition();
            device_pos.setTime(mLocationTime);
            device_pos.setLongitude(mLongitude);
            device_pos.setLatitude(mLatitude);
            gb_device.setPosition(device_pos);
            gb_device.setSupportMobilePosition(true); // 设置支持移动位置上报
        }
        gb28181_agent_.addDevice(gb_device);
/*
        com.gb28181.ntsignalling.Device gb_device1 = new com.gb28181.ntsignalling.Device("34020000001380000002", "安卓测试设备2", Build.MANUFACTURER, Build.MODEL,
                "宇宙","火星1","火星", true);
        if (mLongitude != null && mLatitude != null) {
            com.gb28181.ntsignalling.DevicePosition device_pos = new com.gb28181.ntsignalling.DevicePosition();
            device_pos.setTime(mLocationTime);
            device_pos.setLongitude(mLongitude);
            device_pos.setLatitude(mLatitude);
            gb_device1.setPosition(device_pos);
            gb_device1.setSupportMobilePosition(true);
        }
        gb28181_agent_.addDevice(gb_device1);
 */
        if (!gb28181_agent_.createSipStack()) {
            gb28181_agent_ = null;
            Log.e(TAG, "initGB28181Agent gb28181_agent_.createSipStack failed.");
            return  false;
        }
        boolean is_bind_local_port_ok = false;
        // 最多尝试5000个端口
        int try_end_port = gb28181_sip_local_port_base_ + 5000;
        try_end_port = try_end_port > 65536 ?65536: try_end_port;
        for (int i = gb28181_sip_local_port_base_; i < try_end_port; ++i) {
            if (gb28181_agent_.bindLocalPort(i)) {
                is_bind_local_port_ok = true;
                break;
            }
        }
        if (!is_bind_local_port_ok) {
            gb28181_agent_.releaseSipStack();
            gb28181_agent_ = null;
            Log.e(TAG, "initGB28181Agent gb28181_agent_.bindLocalPort failed.");
            return  false;
        }
        if (!gb28181_agent_.initialize()) {
            gb28181_agent_.unBindLocalPort();
            gb28181_agent_.releaseSipStack();
            gb28181_agent_ = null;
            Log.e(TAG, "initGB28181Agent gb28181_agent_.initialize failed.");
            return  false;
        }
        return true;
    }

信令交互处理:

    @Override
    public void ntsRegisterOK(String dateString) {
        Log.i(TAG, "ntsRegisterOK Date: " + (dateString!= null? dateString : ""));
    }
    @Override
    public void ntsRegisterTimeout() {
        Log.e(TAG, "ntsRegisterTimeout");
    }
    @Override
    public void ntsRegisterTransportError(String errorInfo) {
        Log.e(TAG, "ntsRegisterTransportError error:" + (errorInfo != null?errorInfo :""));
    }
    @Override
    public void ntsOnHeartBeatException(int exceptionCount,  String lastExceptionInfo) {
    }
    @Override
    public void ntsOnInvitePlay(String deviceId, PlaySessionDescription session_des) {
    }
    @Override
    public void ntsOnCancelPlay(String deviceId) {
    }
    @Override
    public void ntsOnAckPlay(String deviceId) {
    }
    @Override
    public void ntsOnPlayInviteResponseException(String deviceId, int statusCode, String errorInfo) {
    }
    @Override
    public void ntsOnByePlay(String deviceId) {
    }
    @Override
    public void ntsOnPlayDialogTerminated(String deviceId) {
    }
    @Override
    public void ntsOnDevicePositionRequest(String deviceId, int interval) {
    }
    @Override
    public void ntsOnNotifyBroadcastCommand(String fromUserName, String fromUserNameAtDomain, String sn, String sourceID, String targetID) {
    }
    @Override
    public void ntsOnAudioBroadcast(String commandFromUserName, String commandFromUserNameAtDomain, String sourceID, String targetID) {
    }
    @Override
    public void ntsOnInviteAudioBroadcastException(String sourceID, String targetID, String errorInfo) {
    }
    @Override
    public void ntsOnInviteAudioBroadcastTimeout(String sourceID, String targetID) {
    }

除了基础的Camera接口外,5.0开始新的Camera2数据依旧可以正常对接,感兴趣的开发者可酌情参考。

相关文章
|
3月前
|
监控 Android开发 数据安全/隐私保护
批量发送短信的平台,安卓群发短信工具插件脚本,批量群发短信软件【autojs版】
这个Auto.js脚本实现了完整的批量短信发送功能,包含联系人管理、短信内容编辑、发送状态监控等功能
|
6月前
|
监控 前端开发 小程序
陪练,代练,护航,代打小程序源码/前端UNIAPP-VUE2.0开发 后端Thinkphp6管理/具备家政服务的综合型平台
这款APP通过技术创新,将代练、家政、娱乐社交等场景融合,打造“全能型生活服务生态圈”。以代练为切入点,提供模块化代码支持快速搭建平台,结合智能匹配与技能审核机制,拓展家政服务和商业管理功能。技术架构具备高安全性和扩展性,支持多业务复用,如押金冻结、录屏监控等功能跨领域应用。商业模式多元,包括交易抽成、增值服务及广告联名,同时设计跨领域积分体系提升用户粘性,实现生态共生与B端赋能。
514 12
|
7月前
|
存储 编解码 监控
Android平台GB28181执法记录仪技术方案与实现
本文介绍了大牛直播SDK的SmartGBD在执法记录仪场景中的应用。GB28181协议作为视频监控联网的国家标准,为设备互联互通提供规范。SmartGBD专为Android平台设计,支持音视频采集、编码与传输,具备自适应算法和多功能扩展优势。文章分析了执法记录仪的需求,如实时音视频传输、设备管理及数据安全,并详细阐述了基于SmartGBD的技术实现方案,包括环境准备、SDK集成、设备注册、音视频处理及功能扩展等步骤。最后展望了SmartGBD在未来智慧物联领域的广阔应用前景。
277 13
|
7月前
|
存储 编解码 开发工具
Android平台毫秒级低延迟HTTP-FLV直播播放器技术探究与实现
本文详细探讨了在Android平台上实现HTTP-FLV播放器的过程。首先介绍了FLV格式的基础,包括文件头和标签结构。接着分析了HTTP-FLV传输原理,通过分块传输实现流畅播放。然后重点讲解了播放器的实现步骤,涵盖网络请求、数据解析、音视频解码与渲染,以及播放控制功能的设计。文章还讨论了性能优化和网络异常处理的方法,并总结了HTTP-FLV播放器的技术价值,尤其是在特定场景下的应用意义。
265 11
|
8月前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
479 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
7月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
8月前
|
安全 前端开发 开发工具
【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
374 5
【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
|
8月前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
206 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
9月前
|
缓存 前端开发 Android开发
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
390 12
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
|
9月前
|
Dart 前端开发 Android开发
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
227 1
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈

热门文章

最新文章