鸿蒙HarmonyOS实战:IPC与RPC设备内进程通信

news2024/9/22 7:35:19

基本

IPC(Inter-Process Communication)与RPC(Remote Procedure Call)用于实现跨进程通信,不同的是前者使用Binder驱动,用于设备内的跨进程通信,后者使用软总线驱动,用于跨设备跨进程通信。需要跨进程通信的原因是因为每个进程都有自己独立的资源和内存空间,其他进程不能随意访问不同进程的内存和资源,IPC/RPC便是为了突破这一点。

说明

Stage模型不能直接使用本文介绍的IPC和RPC,需要通过以下能力实现相关业务场景:

  • IPC典型使用场景在后台服务,应用的后台服务通过IPC机制提供跨进程的服务调用能力。
  • RPC典型使用场景在多端协同,多端协同通过RPC机制提供远端接口调用与数据传递能力。

实现原理

IPC和RPC通常采用客户端-服务器(Client-Server)模型,在使用时,请求服务的(Client)一端进程可获取提供服务(Server)一端所在进程的代理(Proxy),并通过此代理读写数据来实现进程间的数据通信,更具体的讲,首先请求服务的(Client)一端会建立一个服务提供端(Server)的代理对象,这个代理对象具备和服务提供端(Server)一样的功能,若想访问服务提供端(Server)中的某一个方法,只需访问代理对象中对应的方法即可,代理对象会将请求发送给服务提供端(Server);然后服务提供端(Server)处理接受到的请求,处理完之后通过驱动返回处理结果给代理对象;最后代理对象将请求结果进一步返回给请求服务端(Client)。通常,Server会先注册系统能力(System Ability)到系统能力管理者(System Ability Manager,缩写SAMgr)中,SAMgr负责管理这些SA并向Client提供相关的接口。Client要和某个具体的SA通信,必须先从SAMgr中获取该SA的代理,然后使用代理和SA通信。下文直接使用Proxy表示服务请求方,Stub表示服务提供方。

约束与限制

  • 单个设备上跨进程通信时,传输的数据量最大约为1MB,过大的数据量请使用匿名共享内存匿名共享内存匿名共享内存。

  • 不支持在RPC中订阅匿名Stub对象(没有向SAMgr注册Stub对象)的死亡通知。

  • 不支持把跨设备的Proxy对象传递回该Proxy对象所指向的Stub对象所在的设备,即指向远端设备Stub的Proxy对象不能在本设备内进行二次跨进程传递。

场景介绍

IPC/RPC的主要工作是让运行在不同进程的Proxy和Stub互相通信,包括Proxy和Stub运行在不同设备的情况。

开发步骤

ArkTS侧开发步骤

  • 此文档中的示例代码描述的是系统应用跨进程通信。

  • 当前不支持三方应用实现ServiceExtensionAbility,三方应用的UIAbility组件可以通过Context连接系统提供的ServiceExtensionAbility。

  • 当前使用场景: 仅限客户端是三方应用,服务端是系统应用。

  1. 添加依赖

     // FA模型需要从@kit.AbilityKit导入featureAbility
     // import { featureAbility } from '@kit.AbilityKit';
     import { rpc } from '@kit.IPCKit';
  2. 绑定Ability

    首先,构造变量want,指定要绑定的Ability所在应用的包名、组件名,如果是跨设备的场景,还需要绑定目标设备NetworkId(组网场景下对应设备的标识符,可以使用distributedDeviceManager获取目标设备的NetworkId);然后,构造变量connect,指定绑定成功、绑定失败、断开连接时的回调函数;最后,FA模型使用featureAbility提供的接口绑定Ability,Stage模型通过context获取服务后用提供的接口绑定Ability。

     // FA模型需要从@kit.AbilityKit导入featureAbility
     // import { featureAbility } from "@kit.AbilityKit";
     import { Want, common } from '@kit.AbilityKit';
     import { rpc } from '@kit.IPCKit';
     import { hilog } from '@kit.PerformanceAnalysisKit';
     import { distributedDeviceManager } from '@kit.DistributedServiceKit';
     import { BusinessError } from '@kit.BasicServicesKit';
    
     let dmInstance: distributedDeviceManager.DeviceManager | undefined;
     let proxy: rpc.IRemoteObject | undefined;
     let connectId: number;
    
     // 单个设备绑定Ability
     let want: Want = {
       // 包名和组件名写实际的值
       bundleName: "ohos.rpc.test.server",
       abilityName: "ohos.rpc.test.server.ServiceAbility",
     };
     let connect: common.ConnectOptions = {
       onConnect: (elementName, remoteProxy) => {
         hilog.info(0x0000, 'testTag', 'RpcClient: js onConnect called');
         proxy = remoteProxy;
       },
       onDisconnect: (elementName) => {
         hilog.info(0x0000, 'testTag', 'RpcClient: onDisconnect');
       },
       onFailed: () => {
         hilog.info(0x0000, 'testTag', 'RpcClient: onFailed');
       }
     };
     // FA模型使用此方法连接服务
     // connectId = featureAbility.connectAbility(want, connect);
    
     let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
     // 建立连接后返回的Id需要保存下来,在解绑服务时需要作为参数传入
     connectId = context.connectServiceExtensionAbility(want,connect);
    
     // 跨设备绑定
     try{
       dmInstance = distributedDeviceManager.createDeviceManager("ohos.rpc.test");
     } catch(error) {
       let err: BusinessError = error as BusinessError;
       hilog.error(0x0000, 'testTag', 'createDeviceManager errCode:' + err.code + ', errMessage:' + err.message);
     }
    
     // 使用distributedDeviceManager获取目标设备NetworkId
     if (dmInstance != undefined) {
       let deviceList = dmInstance.getAvailableDeviceListSync();
       let networkId = deviceList[0].networkId;
       let want: Want = {
         bundleName: "ohos.rpc.test.server",
         abilityName: "ohos.rpc.test.service.ServiceAbility",
         deviceId: networkId,
         flags: 256
       };
       // 建立连接后返回的Id需要保存下来,在断开连接时需要作为参数传入
       // FA模型使用此方法连接服务
       // connectId = featureAbility.connectAbility(want, connect);
    
       // 第一个参数是本应用的包名,第二个参数是接收distributedDeviceManager的回调函数
       connectId = context.connectServiceExtensionAbility(want,connect);
     }
  3. 服务端处理客户端请求

    服务端被绑定的Ability在onConnect方法里返回继承自rpc.RemoteObject的对象,该对象需要实现onRemoteMessageRequest方法,处理客户端的请求。

     import { rpc } from '@kit.IPCKit';
     import { Want } from '@kit.AbilityKit';
     class Stub extends rpc.RemoteObject {
       constructor(descriptor: string) {
         super(descriptor);
       }
       onRemoteMessageRequest(code: number, data: rpc.MessageSequence, reply: rpc.MessageSequence, option: rpc.MessageOption): boolean | Promise<boolean> {
         // 根据code处理客户端的请求
         return true;
       }
    
       onConnect(want: Want) {
         const robj: rpc.RemoteObject = new Stub("rpcTestAbility");
         return robj;
       }
     }
  4. 客户端处理服务端响应

    客户端在onConnect回调里接收到代理对象,调用sendMessageRequestsendMessageRequestsendMessageRequest方法发起请求,在期约(用于表示一个异步操作的最终完成或失败及其结果值)或者回调函数里接收结果。

     import { rpc } from '@kit.IPCKit';
     import { hilog } from '@kit.PerformanceAnalysisKit';
    
     // 使用期约
     let option = new rpc.MessageOption();
     let data = rpc.MessageSequence.create();
     let reply = rpc.MessageSequence.create();
     // 往data里写入参数
     let proxy: rpc.IRemoteObject | undefined;
     if (proxy != undefined) {
       proxy.sendMessageRequest(1, data, reply, option)
         .then((result: rpc.RequestResult) => {
           if (result.errCode != 0) {
             hilog.error(0x0000, 'testTag', 'sendMessageRequest failed, errCode: ' + result.errCode);
             return;
           }
           // 从result.reply里读取结果
         })
         .catch((e: Error) => {
           hilog.error(0x0000, 'testTag', 'sendMessageRequest got exception: ' + e);
         })
         .finally(() => {
           data.reclaim();
           reply.reclaim();
         })
     }
    
     // 使用回调函数
     function sendRequestCallback(err: Error, result: rpc.RequestResult) {
       try {
         if (result.errCode != 0) {
           hilog.error(0x0000, 'testTag', 'sendMessageRequest failed, errCode: ' + result.errCode);
           return;
         }
         // 从result.reply里读取结果
       } finally {
           result.data.reclaim();
           result.reply.reclaim();
       }
     }
     let options = new rpc.MessageOption();
     let datas = rpc.MessageSequence.create();
     let replys = rpc.MessageSequence.create();
     // 往data里写入参数
     if (proxy != undefined) {
       proxy.sendMessageRequest(1, datas, replys, options, sendRequestCallback);
     }
  5. 断开连接

    IPC通信结束后,FA模型使用featureAbility的接口断开连接,Stage模型在获取context后用提供的接口断开连接。

     // FA模型需要从@kit.AbilityKit导入featureAbility
     // import { featureAbility } from "@kit.AbilityKit";
     import { Want, common } from '@kit.AbilityKit';
     import { rpc } from '@kit.IPCKit';
     import { hilog } from '@kit.PerformanceAnalysisKit';
    
     function disconnectCallback() {
       hilog.info(0x0000, 'testTag', 'disconnect ability done');
     }
     // FA模型使用此方法断开连接
     // featureAbility.disconnectAbility(connectId, disconnectCallback);
    
     let proxy: rpc.IRemoteObject | undefined;
     let connectId: number;
    
     // 单个设备绑定Ability
     let want: Want = {
       // 包名和组件名写实际的值
       bundleName: "ohos.rpc.test.server",
       abilityName: "ohos.rpc.test.server.ServiceAbility",
     };
     let connect: common.ConnectOptions = {
       onConnect: (elementName, remote) => {
         proxy = remote;
       },
       onDisconnect: (elementName) => {
       },
       onFailed: () => {
         proxy;
       }
     };
     // FA模型使用此方法连接服务
     // connectId = featureAbility.connectAbility(want, connect);
    
     connectId = this.context.connectServiceExtensionAbility(want,connect);
    
     this.context.disconnectServiceExtensionAbility(connectId);

远端状态订阅开发实例

IPC/RPC提供对远端Stub对象状态的订阅机制,在远端Stub对象消亡时,可触发消亡通知告诉本地Proxy对象。这种状态通知订阅需要调用特定接口完成,当不再需要订阅时也需要调用特定接口取消。使用这种订阅机制的用户,需要实现消亡通知接口DeathRecipient并实现onRemoteDied方法清理资源。该方法会在远端Stub对象所在进程消亡或所在设备离开组网时被回调。值得注意的是,调用这些接口有一定的顺序。首先,需要Proxy订阅Stub消亡通知,若在订阅期间Stub状态正常,则在不再需要时取消订阅;若在订阅期间Stub所在进程退出或者所在设备退出组网,则会自动触发Proxy自定义的后续操作。

使用场景

这种订阅机制适用于本地Proxy对象需要感知远端Stub对象所在进程消亡,或所在设备离开组网的场景。当Proxy感知到Stub端消亡后,可适当清理本地资源。此外,RPC目前不提供匿名Stub对象的消亡通知,即只有向SAMgr注册过的服务才能被订阅消亡通知,IPC则支持匿名对象的消亡通知。

ArkTS侧接口

  • 此文档中的示例代码描述的是系统应用跨进程通信。

  • 当前不支持三方应用实现ServiceExtensionAbility,三方应用的UIAbility组件可以通过Context连接系统提供的ServiceExtensionAbility。

  • 当前使用场景: 仅限客户端是三方应用,服务端是系统应用。

接口名返回值类型功能描述
registerDeathRecipientvoid注册用于接收远程对象消亡通知的回调,增加 proxy 对象上的消亡通知。
unregisterDeathRecipientvoid注销用于接收远程对象消亡通知的回调。
onRemoteDiedvoid在成功添加死亡通知订阅后,当远端对象死亡时,将自动调用本方法。

参考代码

// FA模型需要从@kit.AbilityKit导入featureAbility
// import { featureAbility } from '@kit.AbilityKit';
import { Want, common } from '@kit.AbilityKit';
import { rpc } from '@kit.IPCKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

let proxy: rpc.IRemoteObject | undefined;
let connect: common.ConnectOptions = {
  onConnect: (elementName, remoteProxy) => {
    hilog.info(0x0000, 'testTag', 'RpcClient: js onConnect called.');
    proxy = remoteProxy;
  },
  onDisconnect: (elementName) => {
    hilog.info(0x0000, 'testTag', 'RpcClient: onDisconnect');
  },
  onFailed: () => {
    hilog.info(0x0000, 'testTag', 'RpcClient: onFailed');
  }
};
let want: Want = {
  bundleName: "com.ohos.server",
  abilityName: "com.ohos.server.EntryAbility",
};
// FA模型通过此方法连接服务
// FA.connectAbility(want, connect);

// 建立连接后返回的Id需要保存下来,在解绑服务时需要作为参数传入
let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // UIAbilityContext
// 建立连接后返回的Id需要保存下来,在解绑服务时需要作为参数传入
let connectionId = context.connectServiceExtensionAbility(want, connect);

上述onConnect回调函数中的proxy对象需要等ability异步连接成功后才会被赋值,然后才可调用proxy对象的unregisterDeathRecipient接口方法注销死亡回调

import { rpc } from '@kit.IPCKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

class MyDeathRecipient implements rpc.DeathRecipient{
  onRemoteDied() {
    hilog.info(0x0000, 'testTag', 'server died');
  }
}
let deathRecipient = new MyDeathRecipient();
if (proxy != undefined) {
  proxy.registerDeathRecipient(deathRecipient, 0);
  proxy.unregisterDeathRecipient(deathRecipient, 0);
}

Stub感知Proxy消亡(匿名Stub的使用)

正向的消亡通知是Proxy感知Stub的状态,若想达到反向的死消亡通知,即Stub感知Proxy的状态,可以巧妙的利用正向消亡通知。如两个进程A(原Stub所在进程)和B(原Proxy所在进程),进程B在获取到进程A的Proxy对象后,在B进程新建一个匿名Stub对象(匿名指未向SAMgr注册),可称之为回调Stub,再通过SendRequest接口将回调Stub传给进程A的原Stub。这样一来,进程A便获取到了进程B的回调Proxy。当进程B消亡或B所在设备离开组网时,回调Stub会消亡,回调Proxy会感知,进而通知给原Stub,便实现了反向消亡通知。

注意:

反向死亡通知仅限设备内跨进程通信使用,不可用于跨设备。

当匿名Stub对象没有被任何一个Proxy指向的时候,内核会自动回收。

参考代码

// Proxy
int TestAbilityProxy::TestAnonymousStub()
{
    MessageOption option;
    MessageParcel dataParcel, replyParcel;
    dataParcel.UpdateDataVersion(Remote());
    dataParcel.WriteRemoteObject(new TestAbilityStub());
    int error = Remote()->SendRequest(TRANS_ID_REVERSED_MONITOR,dataParcel, replyParcel, option);
    int result = (error == ERR_NONE) ? replyParcel.ReadInt32() : -1;
    return result;
}

// Stub

int TestAbilityStub::OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option)
{
    switch (code) {
        case TRANS_ID_REVERSED_MONITOR: {
            sptr<IRemoteObject> obj = data.ReadRemoteObject();
            if (obj == nullptr) {
                reply.WriteInt32(ERR_NULL_OBJECT);
                return ERR_NULL_OBJECT;
            }
            bool result = obj->AddDeathRecipient(new TestDeathRecipient());
            result ? reply.WriteInt32(ERR_NONE) : reply.WriteInt32(-1);
            break;
        }
        default:
            break;
    }
    return ERR_NONE;
}

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频学习资料+学习PDF文档

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、(南向驱动、嵌入式等)鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

HarmonyOS Next 最新全套视频教程

  纯血版鸿蒙全套学习资料(面试、文档、全套视频等)              

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2057726.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode JAVA刷刷站(53)最大子数组和

一、题目概述 二、思路方向 这个问题是一个经典的算法问题&#xff0c;称为“最大子序和”&#xff08;Maximum Subarray Problem&#xff09;。解决这个问题的一个高效方法是使用“Kadanes Algorithm”&#xff0c;它只需要遍历数组一次&#xff0c;就能在 O(n) 时间复杂度内…

模拟JDBC本质

以记事本形式模拟 编译所有文件,并运行 如果换成Oracle 重新编译并运行 利用反射,但是这种方式,还是需要重新编译 改成Oracle 再次编译 以读取配置文件的方式 编译之后 以后无需再次编译源码 只需要改配置文件 修改配置

如何将网站地图Sitemap提交至百度、谷歌及Bing搜索引擎

原文&#xff1a;如何将网站地图Sitemap提交至百度、谷歌及Bing搜索引擎 - 孔乙己大叔 (rebootvip.com) 在当今高度竞争的互联网环境中&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;对于网站的可见性和成功至关重要。网站地图&#xff08;Sitemap&#xff09;&#xff…

新媒体智能化与定制开发 AI 智能名片小程序在工业 4.0 中的作用

摘要&#xff1a;本文探讨了新媒体作为客体所具有的智能以及在工业 4.0 进程中的重要性。分析了人赋予新媒体的四个方面智能&#xff0c;包括感知智能、运算智能、认知智能和智慧行为。同时&#xff0c;结合定制开发 AI 智能名片小程序这一具体案例&#xff0c;阐述了新媒体在工…

【ECMAScript性能优化的技巧与陷阱】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

csrf漏洞(三)

本文仅作为学习参考使用&#xff0c;本文作者对任何使用本文进行渗透攻击破坏不负任何责任。 前言&#xff1a; 本文依靠phpstudy以及dvwa靶场进行操作&#xff0c;具体搭建流程参考&#xff1a;xss漏洞&#xff08;二&#xff0c;xss靶场搭建以及简单利用&#xff09; 前篇…

有头单向链表的函数操作(详细版)

【1】代码展示 #include <stdio.h> #include <stdlib.h> typedef char datatype; // 重定义字符类型 typedef struct node {datatype data; // 数据用来存数据struct node *next; // 指针域用来存下一个节点的地址 } Node_t, *Node_p; // 重定义结构体数据…

5G三防加固平板电脑的特点与应用

随着信息技术的不断发展&#xff0c;5G通信技术也逐渐融入到三防平板电脑中。5G三防加固平板电脑是一种专为恶劣环境和工作场景设计的移动设备。它结合了5G通信技术、三防功能和加固设计&#xff0c;可以为用户带来更加高效、稳定、安全的使用体验。下面&#xff0c;我们来看看…

手把手系列 | 使用Milvus、Llama 3、Ollama、LangChain本地设置RAG应用

随着 Llama、Mistral、Gemma 等开源大语言模型&#xff08;LLM&#xff09;的出现&#xff0c;我们越来越能感受到 LLM 的力量&#xff0c;而本地运行基于 LLM 的 RAG 应用的需求越来越强烈。在将应用推至生产环境前&#xff0c;我们往往都需要先本地运行和测试。 因此&#xf…

[鹏城杯 2022]简单的php

题目源代码 <?phpshow_source(__FILE__); $code $_GET[code]; if(strlen($code) > 80 or preg_match(/[A-Za-z0-9]|\|"||\ |,|\.|-|\||\/|\\|<|>|\$|\?|\^|&|\|/is,$code)){die( Hello); }else if(; preg_replace(/[^\s\(\)]?\((?R)?\)/, , $code…

qemu启动iso/vmdk转换为qcow2镜像

安装qemu安装 https://qemu.weilnetz.de/w64/ 下载链接 Rock安装 https://rockylinux.org/zh-CN/download 下载&#xff0c;可以使用360等国产浏览器&#xff0c;可能自带加速功能&#xff0c;我这里使用最小版本 安装后在vmware中启动&#xff0c;无ip解决方案 执行命令 n…

OV SSL证书:增强网站信任与安全的重要保障

随着互联网的快速发展&#xff0c;电子商务、在线金融等行业的繁荣&#xff0c;网络安全问题日益凸显。为了确保用户数据的安全性和传输过程中的加密性&#xff0c;SSL证书&#xff08;Secure Sockets Layer&#xff09;成为了保护网站安全的重要工具。其中&#xff0c;OV SSL证…

跨平台无缝编辑,2024年免费视频剪辑工具全解析

在众多视频剪辑工具中&#xff0c;免费视频剪辑软件凭借其易用性、功能丰富性以及零成本的优势&#xff0c;赢得了广大用户的青睐。今天&#xff0c;就让我们一起盘点那些2024年大家都在用的免费视频剪辑软件&#xff0c;探索它们如何助力我们轻松实现创意梦想。 1.福昕视频剪…

基于springboot的太原学院商铺管理系统--论文pf

TOC springboot486基于springboot的太原学院商铺管理系统--论文pf 第1章 绪论 1.1 研究背景 互联网概念的产生到如今的蓬勃发展&#xff0c;用了短短的几十年时间就风靡全球&#xff0c;使得全球各个行业都进行了互联网的改造升级&#xff0c;标志着互联网浪潮的来临。在这…

机器人策略学习的Game Changer?伯克利提出Body Transformer

过去几年间&#xff0c;Transformer 架构已经取得了巨大的成功&#xff0c;同时其也衍生出了大量变体&#xff0c;比如擅长处理视觉任务的 Vision Transformer&#xff08;ViT&#xff09;。本文要介绍的 Body Transformer&#xff08;BoT&#xff09; 则是非常适合机器人策略学…

大语言模型 (LLM)是什么_

01.语言模型与大语言模型 2023年&#xff0c;让整个人类最为振奋的AI技术就是ChatGPT。“大语言模型&#xff08;Large Language Model&#xff09;”这个词也随之映入人们的眼帘。ChatGPT让人觉得惊艳之处&#xff0c;能够结合上下文&#xff0c;像人一样有逻辑性地回答问题&…

用Python在Ashare获取金融数据官方文档解读

Ashare&#xff0c;也被写作AKShare&#xff0c;是一个基于Python的开源完全免费的财经数据接口库。它的主要目的是为用户提供股票、期货、期权、基金、外汇、债券、指数、加密货币等金融产品的基本面数据、实时和历史行情数据、衍生数据的采集、清洗和落地的一整套工具。AKSha…

智慧空调离线语音控制方案:NRK3301芯片的深度解析与应用

随着AI技术的大爆发和智能家居的风潮&#xff0c;语音交互已成为智能家居产品的一项必备技能&#xff0c;在家电、音箱、穿戴设备乃至墙壁开关等贴近生活的产品中应用越来越广泛&#xff0c;智能语音识别是当前最热门的方案之一。 九芯智能顺应家居行业智能语音交互市场需求&a…

平面设计艺术:从基础到精通

平面设计是一门结合视觉艺术与技术技能的学科&#xff0c;旨在通过视觉元素传达信息和美感。本文将为初学者提供一个全面的平面设计入门指南&#xff0c;涵盖基础理论、设计流程和实用技巧。在平面设计中&#xff0c;创意和技术相辅相成。设计师使用强大的平面设计工具&#xf…

【C++】入门篇一

【C】入门篇一 一 .缺省参数1.缺省参数的概念2. 缺省参数分类 二. 函数重载1. 函数重载概念2.函数重载代码举例 三.引用1.引用的概念2. 引用特性3. 常引用4. 使用场景(1). 做参数(2). 做返回值 5. 传值、传引用效率比较6. 引用和指针的区别7.引用和指针的不同点 一 .缺省参数 …