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 gocsdkServicegocsdkService: [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/ledpath: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 = (structembededled_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 opensys/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_ledsuccessfully.");return 0;
}static int embededled_device_close(struct hw_device_t* device) {struct embededled_device_t* embededled_device = (structembededled_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开发流程的步骤:
里面呢就是我们要实现的三个函数,然后再把对应的方法注册到我们的server里面去
1 diff --git a/frameworks/base/services/core/jni/Android.mkb/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 += \
1 diff --git a/frameworks/base/services/core/jni/onload.cppb/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 @@ intregister_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 }
1、AIDL:
1 package android.os;
2
3 interface IEmbededLedService {
4 void setVal(int val);
5 int getVal();
6 }
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
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、编译&烧写
相关文章:

Android HAL到Framework
一、为什么需要Framwork? Framework实际上是⼀个应⽤程序的框架,提供了很多服务: 1、丰富⽽⼜可扩展的视图(Views), 可以⽤来构建应⽤程序,它包括列表(lists),⽹格&am…...

Python数据可视化(七)
绘制 3D 图形 到目前为止,我们一直在讨论有关 2D 图形的绘制方法和绘制技术。3D 图形也是数据可视化的 一个很重要的应用方面,我们接下来就重点讲解有关 3D 图形的实现方法。绘制 3D 图形通常需要导 入 mpl_toolkits 包中的 mplot3d 包的相关模块&#x…...

StringMVC
目录 一,MVC定义 二,SpringMVC的基本使用 2.1建立连接 - RequestMapping("/...") 编辑 2.2请求 1.传递单个参数 2.传递多个参数 3.传递对象 4.参数重命名 5.传递数组 6. 传递集合 7.传递JSON数据 8. 获取url中数据 9. 传递文…...
前端基础入门三大核心之HTML篇 —— SVG的viewBox、width和height:绘制矢量图的魔法比例尺【含代码示例】
前端基础入门三大核心之HTML篇 —— SVG的viewBox、width和height:绘制矢量图的魔法比例尺【含代码示例】 基本概念与作用viewBoxwidth和height 代码示例与实践基础示例动态调整示例 不同角度的使用思路保持比例缩放自动适应容器 实际问题与解决方案结语与讨论 在前…...
Java-Zookeeper
zookeeper是什么 一个分布式、开源的分布式应用程序协调服务,具有配置维护、域名服务、分布式同步、组服务等 zookeeper有哪些功能 功能简介集群管理监控节点状态、运行请求等主节点选举主节点挂掉之后会执行新主选举分布式锁zookeeper提供两种锁:独占…...
Godot游戏引擎有哪些优势
哈喽呀,大家好呀,淼淼又来和大家见面啦,众所周知在当今游戏开发领域,各种游戏引擎如雨后春笋般涌现,为开发者提供了丰富的选择。而在这些众多的选择中,Godot游戏引擎以其独特的特性和开放源代码的优势&…...

一张图看懂大模型性价比:能力、价格、并发量全面PK
最近,国内云厂商的大模型掀起一场降价风暴。火山引擎、阿里云、百度云等纷纷宣布降价,部分模型价格降幅据称高达99%,甚至还有些模型直接免费。 五花八门的降价话术,一眼望去遍地黄金。但事实真的如此吗?今天我们就拨开…...
设计井字棋游戏(一)
创建游戏登录注册窗口 用户名admin 密码admin(可自行改变) 主页面 1. 导包 import pickle:导入 pickle 模块,这是一个 Python 的内置模块,用于将 Python 对象序列化和反序列化。序列化是指将对象转换为字节流&am…...

华为手机卡顿(仅针对于部分人来说,我也不清楚是否真的有用)
关机! 之前一段时间手机变得特别卡顿,然后网上搜了一堆教程一点用没有,结果因为昨天下午在考试所以把手机关机了一个多小时,再打开之后手机就变得很流畅,原因不详,但效果显著,如有需要可尝试一…...

7、按钮无法点击
不能点击,打开f12,删除disabled...

开源博客项目Blog .NET Core源码学习(25:App.Hosting项目结构分析-13)
本文学习并分析App.Hosting项目中后台管理页面的文章管理页面。 文章管理页面用于显示、检索、新建、编辑、删除文章数据,以便在前台页面的首页、文章专栏、文章详情页面显示文章数据。文章管理页面附带一新建及编辑页面,以支撑新建和编辑文章数据。…...

第七节 ConfigurationClassParser 源码分析
tips: ConfigurationClassParser 是 Springframework 中的重要类。 本章主要是源码理解,有难度和深度,也枯燥乏味,可以根据实际情况选择阅读。 位置:org.springframework.context.annotation.ConfigurationClassPars…...

零基础代码随想录【Day42】|| 1049. 最后一块石头的重量 II,494. 目标和,474.一和零
目录 DAY42 1049.最后一块石头的重量II 解题思路&代码 494.目标和 解题思路&代码 474.一和零 解题思路&代码 DAY42 1049.最后一块石头的重量II 力扣题目链接(opens new window) 题目难度:中等 有一堆石头,每块石头的重量都是正整…...

2024-5-24 石群电路-15
2024-5-24,星期五,22:15,天气:晴,心情:晴。今天最后一天上班,终于要放返校假啦,开心!!!!!!不过放假也不能耽误…...
功能测试:核心原理、挑战以及解决之道
在软件开发生命周期中,功能测试占据了至关重要的位置。它是确保软件应用按照既定的要求和规格运行的关键测试阶段。功能测试的目的在于验证软件的功能、行为和用户界面等是否达到了业务需求的标准。本文将深入探讨功能测试的概念,执行过程中可能遇到的挑…...

跨境电商赛道,云手机到底能不能化繁为简?
当下国内电商背景: 从零售额的数据来看:随着互联网的普及和消费者购物习惯的改变,国内电商市场规模持续扩大。据相关数据显示,网络消费亮点纷呈,一季度全国网上零售额达到了3.3万亿元,同比增长12.4%。这表…...

linux:信号深入理解
文章目录 1.信号的概念1.1基本概念1.2信号的处理基本概念1.3信号的发送与保存基本概念 2.信号的产生2.1信号产生的五种方式2.2信号遗留问题(core,temp等) 3.信号的保存3.1 信号阻塞3.2 信号特有类型 sigset_t3.3 信号集操作函数3.4 信号集操作函数的使用 4.信号的处理4.1 信号的…...
Android系统的/etc/mkshrc文件
/etc/mkshrc 文件是用于配置 mksh(MirBSD Korn Shell)环境的启动脚本。mksh 是 Android 默认使用的 shell,在 shell 启动时会读取并执行这个文件中的配置。以下是关于 /etc/mkshrc 文件的详细信息及其用途。 /etc/mkshrc 文件的作用 环境配…...

LeetCode199二叉树的右视图
题目描述 给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 解析 这一题的关键其实就是找到怎么去得到当前是哪一层级,可以利用队列对二叉树进行层次遍历,但…...
JavaScript 基础
一 JavaScript 的书写形式 1.1 行内式 <input type"button" value"点我一下" onclick"alert(hello akai);" > 注意,JS 中的字符串常量可以用单引号表示,也可以使用双引号表示.HTML 中推荐使用双引号,JS 中推荐使用单引号(使用双引号容易…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...

push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...