Android HAL到Framework

一、为什么需要Framwork?

Framework实际上是⼀个应⽤程序的框架,提供了很多服务:

1、丰富⽽⼜可扩展的视图(Views),

可以⽤来构建应⽤程序,它包括列表(lists),⽹格(grids),⽂本框(text boxes),按钮(buttons),甚⾄可嵌⼊的web浏览器。

2、内容提供器(Content Providers)

使得应⽤程序可以访问另⼀个应⽤程序的数据(如联系⼈数据库),或者共享它们⾃⼰的数据

3、资源管理器(Resource Manager)

提供⾮代码资源的访问,如本地字符串,图形,和布局⽂件(layout files)。

4、通知管理器(Notification Manager)

使得应⽤程序可以在状态栏中显⽰⾃定义的提⽰信息。

5、活动管理器(Activity Manager)

⽤来管理应⽤程序⽣命周期并提供常⽤的导航回退功能。

二、应用层访问硬件,如何自定义系统Service?

1、应用层如何访问硬件

(1)Linux

        对于Linux来说的话,就比较简单,应用层的APP直接通过open一类的接口直接访问我们底层的驱动文件

(2)Android 

        对于Android来说的话,它就会有多种方式去访问,

1) APP ----- JNI ----- Kernel:

        这种就很直接明了,上层app访问JNI,再去访问kernel

2)APP ----- Service ----- JNI ----- Kernel:

        当我们要往系统里添加一个硬件的话,我们更希望把它封装为一个系统的服务,就可以以这种方式去访问到底层

3)APP ----- Service ----- JNI ----- HAL ----- Kernel:

        一些驱动厂商的一个源码呢他是不希望开放给我们的一个开发者是吧,但是他们又依赖着Android的开源框架,所以就有一种比较好的方法,既不需要公开源码,又可以实现同样的功能。就是把它封装成库,这样可以让厂家去提供一个现成的库,然后我们直接去使用,他就不用开放这一层的源码,这就是HAL层的存在意义。

 为什么需要JNI? 

        应⽤使⽤java编写,驱动⼀般使⽤c/cpp编写,提供⼀种Java访问c/cpp的⽅法。也就是Java代码可通过JNI接⼝调⽤C/C++⽅法。

 JNI开发流程的步骤: 

1)编写JNI⽅法表并注册
2)实现JNI的.c⽂件

2、自定义系统Service 

        Framework还有一个很重要的功能,就是系统server。所有的硬件呢都是通过我们的系统server去进行管理,那我们怎样为我们的硬件接口去添加一个自定义的系统serve呢?

(1)建立aidl通信接口;

(2)在system_server中注册service到servicemanager;

(3)实现service,对应aidl中的接口函数。

(4)client向servicemanager请求service,成功后,调用aidl接口函数,建立client进程和service进程的通信关系。

总结来说就是:

1)system_server完成注册功能;
2)servicemanager完成服务管理功能;
3)aidl完成通讯功能;

(1)建立aidl通信接口

在frameworks/base/core/java/android/os/路径下新建对应名称的一个aidl文件

下面我们以顾凯歌的一个蓝牙模块的服务为大家举例:

路径:frameworks/base/core/java/android/os/IGocsdkService.aidl:
(因为他是Interface的一个接口,所有在前面加个 "I")

+ package android.os;

+ interface IEmbededService {
+   interface IFmService {
+
+       //蓝牙状态回调注册去注销 
+       void registerCallback(IGocsdkCallback callback);
+       // 注销蓝牙状态
+       void unregisterCallback(IGocsdkCallback callback);
+       
+       //注释后面带的为操作后相应的回调回复
+       //蓝牙协议软复位  ---》 onInitSucceed()
+       void restBluetooth();
+       
+       //获取本地蓝牙名称  ---》onCurrentDeviceName()
+       void getLocalName();
+       
+       //设置本地蓝牙名称  ---》onCurrentDeviceName()
+       void setLocalName(String name);

+        ..................//等等一些,都为接口函数,会在下面实现

}

编译到系统

路径:frameworks/base/Android.mk 

diff --git a/android/frameworks/base/Android.bp b/android/frameworks/base/Android.bp
old mode 100644
new mode 100755
index d8a7f06..953759c
--- a/android/frameworks/base/Android.bp
+++ b/android/frameworks/base/Android.bp
@@ -265,6 +265,11 @@ java_defaults {
         "core/java/android/os/IRecoverySystemProgressListener.aidl",
         "core/java/android/os/IRemoteCallback.aidl",
         "core/java/android/os/ISchedulingPolicyService.aidl",
+        "core/java/android/os/IGocsdkService.aidl",
         ":statsd_aidl",
         "core/java/android/os/ISystemUpdateManager.aidl",
         "core/java/android/os/IThermalEventListener.aidl",

(2)在system_server中注册EmbededServicer到servicemanager

路径:frameworks/base/services/java/com/android/server/SystemServer.java

使用ServiceManager.addService添加我们自定义的server

@@ -1097,6 +1097,13 @@ public final class SystemServer {
}
    Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);

 +      try {
 +          Slog.i(TAG, "IGocsdkService");
 +           ServiceManager.addService("gocsdkService ", new GocsdkService());
 +   } catch (Throwable e) { 
 +       Slog.e(TAG, "Failure starting Gocsdk Service", e);
 +   }

(3)实现EmbededService,对应aidl中的接⼝函数

路径:frameworks/base/services/java/com/android/server/EmbededService.java

package com.android.server;
import android.content.Context;
import android.os.IGocsdkService;
import android.util.Slog;
public class GocsdkService extends IGocsdkService.Stub {
    private static final String TAG = "GocsdkService";
    GocsdkService(){
        Slog.i(TAG,"GocsdkService init");
    }
        public void registerCallback(IGocsdkCallback callback){
        return xxx;
    }
        public void unregisterCallback(IGocsdkCallback callback){
        return xxx;
    }
        public void getLocalName(){
        return xxx;
    }
        .......................
}

(4)在app中使⽤IEmbededService的大致流程如下

        很好理解吧,把我们对应的一个服务导入,然后去初始化一个类,然后通过ServiceManager去找到我们自定义的这个server,然后使用自定义服务的函数获取数据。

import android.os.IGocsdkService; //导入

private IGocsdkService mGocsdkService = null; //初始化类
mGocsdkService = IGocsdkService .Stub.asInterface(

        ServiceManager.getService("gocsdkService"));

int version= mEmbededService.getLocalName();
String text = String.value(localName);

(5)编译service,烧录

直接全sdk编译,防止有遗漏

(6)验证

使⽤service list查看是否有EmbededService

xxx:/ $ service list | grep gocsdkService
         gocsdkService: [android.os.IGocsdkService]

三、为什么需要Android HAL?

        Hardware Abstract Layer 硬件抽象层,由于Linux Kernel需要遵循GPL开源协议,硬件⼚商为了保护⾃⼰硬件⽅⾯的各项参数不被外泄,⽽⼀个设备的驱动程序包含了硬件的⼀些重要参数,所以驱动的开源势必会使硬件⼚商蒙受损失,Google为了保护硬件⼚商的利益,所以在Android系统中加⼊了HAL层,在HAL层中不必遵循GPL协议,所以代码可以封闭。
        所以如果硬件驱动开源的写在Kernel⾥,Framework直接调⽤,⽽不愿意开源的就写在HAL层⾥,实现闭源。也就是说,编写驱动分为两个部分,⼀个是HAL层的驱动代码,⼀个是Kernel层的驱动代码。


1、内核实现HAL驱动的⽅法有两种:

(1)采⽤直接调⽤so动态链接库⽅式

        采⽤共享库形式,在编译时会调⽤到。由于采⽤function call形式调⽤,因此可被多个进程使⽤,但会被mapping到多个进程空间中,造成浪费,同时需要考虑代码能否安全重⼊的问题。

(2)采⽤Stub代理⽅式调⽤

        采⽤HAL module和HAL stub结合形式,HAL stub不是⼀个share library,编译时
上层只拥有访问HAL stub的函数指针,并不需要HAL stub。上层通过HAL module提供的统⼀接⼝获取并操作HAL stub,so⽂件只会被mapping到⼀个进程,也不存在重复mapping和重⼊问题。

2、如何编写HAL层驱动

        我们现在一般都是采用第二种方式,基于HAL框架提供了三个结构体,分别为hw_device_t、hw_module_t、hw_module_methods_t,编写HAL层驱动则是依据这三个结构体作扩展,我们创建⾃⼰驱动的device_t,module_t代码,并且写hw_module_methods_t这个结构体中⽅法的实现代码,最后JNI层通过hw_get_module调⽤。

(1)在 android/hardware/libhardware/modules/xxx 路径下创建我们的HAL文件夹,例如LED:

mkdirhardware/libhardware/modules/led

path:hardware/libhardware/include/hardware/led_hal.h
path:hardware/libhardware/modules/embeded/led_hal.c

(1)led_hal.c:

#define LOG_TAG "dLed"
#include <hardware/hardware.h>
#include <hardware/led_hal.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#define DEVICE_NAME "sys/led/embeded_blue_led"
#define MODULE_NAME "EmLed"

/*设备打开和关闭接⼝*/
static int embededled_device_open(const struct hw_module_t* module, const
char* name, struct hw_device_t** device);
static int embededled_device_close(struct hw_device_t* device);

/*设备访问接⼝*/
static int embededled_set_val(struct embededled_device_t* dev, int val);
static int embededled_get_val(struct embededled_device_t* dev, int* val);
static int embededled_device_open(const struct hw_module_t* module, const
char* name, struct hw_device_t** device) {
    struct embededled_device_t* dev;dev = (struct
    embededled_device_t*)malloc(sizeof(struct embededled_device_t));

    if(!dev) {
        ALOGI("embededled Stub: failed to alloc space");
        return -EFAULT;
    }
    memset(dev, 0, sizeof(struct embededled_device_t));
    //初始化设备相关信息,实现访问接⼝函数
    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = 0;
    dev->common.module = (hw_module_t*)module;
    dev->common.close = embededled_device_close;
    dev->set_val = embededled_set_val;
    dev->get_val = embededled_get_val;
    if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {
    ALOGI("embededled Stub: failed to open
    sys/embededled/embeded_blue_led -- %s.", strerror(errno));free(dev);
    return -EFAULT;
    }
    int status = 0;
    write(dev->fd, &status, sizeof(status));
    *device = &(dev->common);
    ALOGI("embededled Stub: open sys/embededled/embeded_blue_led
    successfully.");
    return 0;
}

static int embededled_device_close(struct hw_device_t* device) {
    struct embededled_device_t* embededled_device = (struct
    embededled_device_t*)device;
    if(embededled_device) {
        close(embededled_device->fd);
        free(embededled_device);
    }
        return 0;
}

static int embededled_set_val(struct embededled_device_t* dev, int val) {
    ALOGI("embededled Stub: set value %d to device.", val);
    write(dev->fd, &val, sizeof(val));
    return 0;
    }

static int embededled_get_val(struct embededled_device_t* dev, int* val) {
if(!val) {
    ALOGI("embededled Stub: error val pointer");

    return -EFAULT;
}
    read(dev->fd, val, sizeof(*val));
    ALOGI("embededled Stub: get value %d from device", *val);
    return 0;
}

/*模块⽅法表*/
static struct hw_module_methods_t embededled_module_methods = {
open: embededled_device_open
};

/*模块实例变量*/
struct embededled_module_t HAL_MODULE_INFO_SYM = {
common: {
    tag: HARDWARE_MODULE_TAG,
    version_major: 1,
    version_minor: 0,
    id: EMBEDEDLED_HARDWARE_MODULE_ID,
    name: MODULE_NAME,
    author: MODULE_AUTHOR,
    methods: &embededled_module_methods,
}
};

led_hal.h:
path:hardware/libhardware/include/hardware/led_hal.h

#ifndef ANDROID_LED_INTERFACE_H
#define ANDROID_LED_INTERFACE_H
#include <hardware/hardware.h>
__BEGIN_DECLS
/*定义模块ID*/
#define EMBEDEDLED_HARDWARE_MODULE_ID "led_hal"
/*硬件模块结构体*/
struct led_module_t {
        struct hw_module_t common;
};
/*硬件接⼝结构体*/
struct embededled_device_t {
        struct hw_device_t common;
        int fd;
        int (*set_val)(struct led_device_t* dev, int val);
        int (*get_val)(struct led_device_t* dev, int* val);
};
__END_DECLS
#endif

四、JNI层添加

JNI开发流程的步骤:

第1步:编写JNI⽅法表并注册
第2步:实现JNI的.c⽂件

里面呢就是我们要实现的三个函数,然后再把对应的方法注册到我们的server里面去

Android.mk
1 diff --git a/frameworks/base/services/core/jni/Android.mk
            b/frameworks/base/services/core/jni/Android.mk
2     index 0f0124bd46..305773298a 100644
3     --- a/frameworks/base/services/core/jni/Android.mk
4     +++ b/frameworks/base/services/core/jni/Android.mk
5     @@ -36,6 +36,7 @@ LOCAL_SRC_FILES += \
6     $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
7     $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
8     $(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \
9     + $(LOCAL_REL_DIR)/com_android_server_EmbededLedService.cpp \
10     $(LOCAL_REL_DIR)/onload.cpp
11
12     LOCAL_SRC_FILES += \
把注册JNI⽅法函数添加到系统中
1 diff --git a/frameworks/base/services/core/jni/onload.cpp
    b/frameworks/base/services/core/jni/onload.cpp
2     index d5861f8c41..b52f7917fd 100644
3     --- a/frameworks/base/services/core/jni/onload.cpp
4     +++ b/frameworks/base/services/core/jni/onload.cpp
5     @@ -47,6 +47,7 @@ int
    register_android_server_PersistentDataBlockService(JNIEnv* env);
6     int register_android_server_Watchdog(JNIEnv* env);
7     int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
8     int register_com_android_server_rkdisplay_RkDisplayModes(JNIEnv* env);
9     +int register_android_server_EmbededLedService(JNIEnv* env);
10     };
11
12     using namespace android;
13     @@ -89,7 +90,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
14     register_android_server_Watchdog(env);
15     register_android_server_HardwarePropertiesManagerService(env);
16     register_com_android_server_rkdisplay_RkDisplayModes(env);
17     -
18     -
19     + register_android_server_EmbededLedService(env);
20     return JNI_VERSION_1_4;
21         }
然后按照同样的方法去建立AIDL&Service

1、AIDL:

1 package android.os;
2
3 interface IEmbededLedService {
4     void setVal(int val);
5     int getVal();
6 }
添加下⾯mk⽂件内容后,编译⽣成接⼝
1 diff --git a/frameworks/base/Android.mk b/frameworks/base/Android.mk
2 index b9692de0e1..c426a3cd99 100755
3 --- a/frameworks/base/Android.mk
4 +++ b/frameworks/base/Android.mk
5 @@ -240,6 +240,7 @@ LOCAL_SRC_FILES += \
6     core/java/android/os/IUpdateLock.aidl \
7     core/java/android/os/IUserManager.aidl \
8     core/java/android/os/IVibratorService.aidl \
9     + core/java/android/os/IEmbededLedService.aidl \
10     core/java/android/os/IDisplayDeviceManagementService.aidl \
11     core/java/android/os/IRkDisplayDeviceManagementService.aidl \
12     core/java/android/security/IKeystoreService.aidl \

2、Service

frameworks/base/services/java/com/android/server/EmbededLedService.java
1 package com.android.server;
2 import android.content.Context;
3 import android.os.IEmbededLedService;
4 import android.util.Slog;
5     public class EmbededLedService extends IEmbededLedService.Stub {
6         private static final String TAG = "EmbededLedService";
7         EmbededLedService() {
8
9     boolean status = init_native();
10     Slog.i(TAG,"EmbededLedService Stub init"+status);
11     }
12     public void setVal(int val) {
13         setVal_native(val);
14     }
15     public int getVal() {
16     return getVal_native();
17 }
18
19 //JNI⽅法
20 private static native boolean init_native();
21 private static native void setVal_native(int val);
22 private static native int getVal_native();
23 };

3、添加Service到System启动

1 diff --git
a/frameworks/base/services/java/com/android/server/SystemServer.java
b/frameworks/base/services/java/com/android/server/SystemServer.java
2 index cc6f1850e6..b22ecda734 100644
3 --- a/frameworks/base/services/java/com/android/server/SystemServer.java
4 +++ b/frameworks/base/services/java/com/android/server/SystemServer.java
5 @@ -1086,6 +1086,15 @@ public final class SystemServer {
6 } catch (Throwable e) {
7 reportWtf("starting DiskStats Service", e);
8 }
9 +
10 + try {
11 + Slog.i(TAG, "Embededled Service");
12 + ServiceManager.addService("embededled", new
EmbededLedService());
13 + } catch (Throwable e) {
14 + Slog.e(TAG, "Failure starting Embededled Service", e);
15 + }
16 +
17 +
18 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
19
20 if (!disableSamplingProfiler) {

4、编译&烧写

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【前端】深入浅出响应式布局

深入浅出前端响应式布局 在当今的网页设计与前端开发中&#xff0c;创建能够适应多种设备和屏幕尺寸的网页已成为必备技能。响应式布局&#xff08;Responsive Layout&#xff09;旨在通过灵活的设计和技术手段&#xff0c;让网页内容能够根据用户的设备环境自动调整&#xff…

Web 3D 框架简介

前言 3D游戏引擎的历史可以追溯到20世纪80年代末和90年代初。当时,计算机技术迅速发展,人们开始对图形和游戏感兴趣。以下是3D游戏引擎的历史故事: 早期引擎的诞生(1980-1990年代) 在这个时期,一些早期的3D游戏引擎开始出现。其中一个著名的例子是id Software开发的Do…

基于微信小程序的校园捐赠系统的设计与实现

校园捐赠系统是一种便捷的平台&#xff0c;为校园内的各种慈善活动提供支持和便利。通过该系统&#xff0c;学生、教职员工和校友可以方便地进行捐赠&#xff0c;并了解到相关的项目信息和捐助情况。本文将介绍一个基于Java后端和MySQL数据库的校园捐赠系统的设计与实现。 技术…

阿里云ubuntu 24 deb安装mysql5.7问题解决

阿里云最近有了ubuntu24&#xff0c;手欠直接选了24系统来试水&#xff0c;安装mysql这里遇到麻烦了 其它问题参考ubuntu22的即可&#xff0c;以下是3个新问题&#xff1a; 阿里云ubuntu 24 deb安装mysql5.7遇到的3个问题&#xff1a; 1&#xff09;libssl1.1 (&#xff1e; …

TG5032CKN是一种高稳定性晶体振荡器

TG5032CKN的输出频率范围为10 MHz至24 MHz&#xff0c;能够在-40C至105C的温度范围内工作&#xff0c;其频率/温度特性为0.110^-6 Max。这表明该设备具有很好的温度稳定性&#xff0c;适合在极端温度条件下使用。TG5032CKN的尺寸为5.03.21.65 mm&#xff0c;可以选择10针或4针封…

内网安全之搭建ADCS证书服务

在域控上安装ADCS服务时&#xff0c;默认会自动配置完LDAPS&#xff0c;如果不是在域控上安装ADCS服务&#xff0c;需要手动配置LDAPS 安装证书服务ADCS 打开服务器管理器——>添加角色和功能 选择“基于角色或基于功能的安装”选项&#xff0c;然后点击下一步 选择“从…

rabbitMQ本地启动快捷方式

%1 mshta vbscript:CreateObject("Shell.Application").ShellExecute("cmd.exe","/c ""%~s0"" ::","","runas",1)(window.close)&&exit COLOR A TITLE 运行RabbitMQ%comspec% /k "C:\Prog…

【C++】位图/布隆过滤器+海量数据处理

目录 一、位图 1.1 位图的概念 1.2 位图的实现 1.3 位图的应用&#xff08;面试题&#xff09; 二、布隆过滤器 2.1 布隆过滤器的引入 2.2 布隆过滤器概念 2.3 布隆过滤器的插入和查找 2.4 布隆过滤器的实现 2.5 布隆过滤器的优点和缺陷 2.6 布隆过滤器的应用&#…

【C++】详解多态

目录 初识多态 多态的条件 接口继承和实现继承 override 和 final 多态原理 继承与虚函数表 析构函数与多态 抽象类 本篇内容关联知识的链接 【C】详解C的继承-CSDN博客 【C】详解C的模板-CSDN博客 【C】C的内存管理-CSDN博客 初识多态 父类被不同子类继承后&#…

STM32控制HC-SR04超声模块获取距离

欢迎入群共同学习交流 时间记录&#xff1a;2024/5/23 一、模块介绍 &#xff08;1&#xff09;引脚介绍 VCC&#xff1a;电源引脚&#xff0c;接单片机3.3/5V GND&#xff1a;电源地 Trig&#xff1a;超声信号触发引脚 Echo&#xff1a;超声信号接收引脚 &#xff08;2&…

多商户消费券系统源码(ThinkPHP+FastAdmin+微信公众号)

打造智能促销新体验 一、引言&#xff1a;消费券系统的时代意义 在当今这个数字化高速发展的时代&#xff0c;电子商务和移动支付已经成为人们日常生活的重要组成部分。随着市场竞争的加剧&#xff0c;多商户消费券系统作为一种创新的促销手段&#xff0c;正逐渐受到商家和消…

安全工程师考试摸拟试题

安全工程师考试摸拟试题安全工程师是指在工程项目中负责安全管理和安全技术服务的专业人员。他们需要具备扎实的理论知识和丰富的实践经验&#xff0c;能够有效预防和控制各类安全风险… 1 安全工程师考试摸拟试题 安全工程师是指在工程项目中负责安全管理和安全技术服务的专业…

基于windows通过kind部署轻量级便携式k8s集群

感谢老师的视频教程&#xff1a; 基于windows通过kind部署轻量级便携式k8s集群 wsl windows下的linux wsl --set-default-version 2 wsl --help wsl --list --online wsl --install -d Ubuntu wsl -l -v &#xff08;看看版本是不是2&#xff0c;否则docker那边识别不到&…

vite+ts+mock+vue-router+pinia实现vue的路由权限

0.权限管理 前端的权限管理主要分为如下&#xff1a; 接口权限路由权限菜单权限按钮权限 权限是对特定资源的访问许可&#xff0c;所谓权限控制&#xff0c;也就是确保用户只能访问到被分配的资源 1.项目搭建 创建vite项目 yarn create vite配置别名 npm install path -…

查看cpu

cpu是几核的怎么查看_windows查看cpu核数-CSDN博客文章浏览阅读1.4w次&#xff0c;点赞11次&#xff0c;收藏24次。cpu是几核的怎么查看_windows查看cpu核数https://blog.csdn.net/llg___/article/details/125317223?ops_request_misc&request_id&biz_id102&utm_t…

多模态大模型新进展——GPT-4o、Project Astra关键技术丨青源Workshop第27期

青源Workshop丨No.27 多模态大模型新进展—GPT-4o、Project Astra关键技术主题闭门研讨会 刚刚过去的两天&#xff0c;OpenAI、Google纷纷发布了多模态大模型的最新成果&#xff0c;GPT-4o、Project Astra先后亮相。 本周五&#xff08;北京时间5月17日&#xff09;18点&#x…

力扣1809 没有广告的剧集(postgresql)

需求 Table: Playback ----------------- | Column Name | Type | ----------------- | session_id | int | | customer_id | int | | start_time | int | | end_time | int | ----------------- 该表主键为&#xff1a;session_id &#xff08;剧集id&#xff09; customer_…

v-md-editor和SSE实现ChatGPT的打字机式输出

概述 不论是GPT还是文心一言&#xff0c;在回答的时候类似于打字机式的将答案呈现给我们&#xff0c;这样的交互一方面比较友好&#xff0c;另一方面&#xff0c;当答案比较多、生成比较慢的时候也能争取一些答案的生成时间。本文后端使用express和stream&#xff0c;使用SSE将…

WXML模板语法-数据绑定

1.数据绑定的基本原则 (1)在data中定义数据 (2)在WXML中使用数据 2.在data页面中定义数据&#xff1a;在页面对应的.js文件中&#xff0c;把数据定义在data对象中即可 &#xff08;这里打错了 应该是数组类型的数据... 报意思啊&#xff09; 3.Mustache语法的格式 把data中的…

容器组件:栅格布局,侧边栏容器(HarmonyOS学习第四课【4.5】)

栅格布局 栅格布局可以为布局提供规律性的结构&#xff0c;解决多尺寸多设备的动态布局问题&#xff0c;保证不同设备上各个模块的布局一致性。 栅格容器组件&#xff0c;仅可以和栅格子组件(GridCol)在栅格布局场景中使用。 说明 该组件从API Version 9开始支持。后续版本…