android fwk模块之Sensor架构
本文基于Android 12源码整理,包含如下内容:
- 通信架构
- 应用层实现
- 使用方式
- SensorManager抽象接口具体实现
- fwk层的实现
- native中的SensorManager的初始化流程
- native中的消息队列初始化与数据读取
- sensorservice实现
- HAL层的实现
通信架构
应用层实现
涉及代码:
framework/base/core/java/android/hardware/SensorManager.java
framework/base/core/java/android/hardware/SystemSensorManager.java
framework/base/core/java/android/hardware/SensorEvent.java
使用方式
应用层主要使用fwk提供的SensorManager 来监听获取指定传感器的数据,主要实现如下,
回调的参数SensorEvent是一个包含传感器参数与当前值的数据结构,不同的传感器数据均从其values的float数组中获取值,如光感传感器的值只有一个就是values[0], 陀螺仪有三个值就是values[0],values[1],values[2]等。
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManagerval sm = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sm.registerListener(object : SensorEventListener{override fun onSensorChanged(event: SensorEvent?) {val x = event?.values?.get(0)val y = event?.values?.get(1)val z = event?.values?.get(2)}override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {//传感器精度回调}
}, sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL)
SensorManager抽象接口具体实现
SensorManager是一个抽象函数,部分抽象函数的具体的功能实现是在SystemSensorManager类中, 其构造函数主要做的工作依次如下:
- 初始化jni接口
- 创建一个SensorManager的native层实例
- 检查当前进程是否具备高速传输传感器数据的权限
- 初始化本机的sensor设备列表
public SystemSensorManager(Context context, Looper mainLooper) {synchronized (sLock) {if (!sNativeClassInited) {sNativeClassInited = true;//初始化jni接口 nativeClassInit();}}mMainLooper = mainLooper;ApplicationInfo appInfo = context.getApplicationInfo();mTargetSdkLevel = appInfo.targetSdkVersion;mContext = context;//创建一个SensorManager的native层实例mNativeInstance = nativeCreate(context.getOpPackageName());mIsPackageDebuggable = (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));PackageManager packageManager = context.getPackageManager();//检查当前进程是否具备高速传输传感器数据的权限 mHasHighSamplingRateSensorsPermission =(PERMISSION_GRANTED == packageManager.checkPermission(HIGH_SAMPLING_RATE_SENSORS_PERMISSION,appInfo.packageName));// initialize the sensor listfor (int index = 0;; ++index) {Sensor sensor = new Sensor();//初始化本机的sensor设备列表if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;mFullSensorsList.add(sensor);mHandleToSensor.put(sensor.getHandle(), sensor);}}
每个app在通过getSystemService获取class类型为SensorManager的对象时,获取的都是SystemSensorManager对象,实例的初始化在SystemServiceRegistry的静态域中初始化,代码如下:
registerService(Context.SENSOR_SERVICE, SensorManager.class,new CachedServiceFetcher<SensorManager>() {@Overridepublic SensorManager createService(ContextImpl ctx) {Return new SystemSensorManager(ctx.getOuterContext(),ctx.mMainThread.getHandler().getLooper());
}});
再跟一下监听函数registerListener,最终会走到SystemSensorManager的registerListenerImpl函数中,实现功能如下:
- 检查注册参数是否符合要求
- 获取监听器实例的所有事件队列-> 如果队列为空 1.创建新的队列实例2.队列实例中加入当前监听的Sensor对象3.将事件队列加入到监听器的HashMap中对应保存-> 如果队列不为空1.队列实例中加入当前监听的Sensor对象
实现代码如下:
// Invariants to preserve:
// - one Looper per SensorEventListener
// - one Looper per SensorEventQueue
// We map SensorEventListener to a SensorEventQueue, which holds the looper
synchronized (mSensorListeners) {SensorEventQueue queue = mSensorListeners.get(listener);if (queue == null) {Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;final String fullClassName =listener.getClass().getEnclosingClass() != null? listener.getClass().getEnclosingClass().getName(): listener.getClass().getName();queue = new SensorEventQueue(listener, looper, this, fullClassName);if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs)) {queue.dispose();return false;}mSensorListeners.put(listener, queue);return true;} else {return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs);}
}
SensorEventQueue 是定义在SystemSensorManager.java文件中的一个静态内部类,其父类类型为BaseEventQueue,BaseEventQueue中定义了enableSensor, 在app注册了监听器后,BaseEventQueue会调用nativeEnableSensor去激活该Sensor. SensorEventQueue中还实现了dispatchSensorEvent函数,当底层上报数据的时候,JNI层会回调该函数,将数据传给listener实例。
protected void dispatchSensorEvent(int handle, float[] values, int inAccuracy,long timestamp) {final Sensor sensor = mManager.mHandleToSensor.get(handle);if (sensor == null) {// sensor disconnectedreturn;}SensorEvent t = null;synchronized (mSensorsEvents) {t = mSensorsEvents.get(handle);}if (t == null) {// This may happen if the client has unregistered and there are pending events in// the queue waiting to be delivered. Ignore.return;}// Copy from the values array.System.arraycopy(values, 0, t.values, 0, t.values.length);t.timestamp = timestamp;t.accuracy = inAccuracy;t.sensor = sensor;// call onAccuracyChanged() only if the value changesfinal int accuracy = mSensorAccuracies.get(handle);if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {mSensorAccuracies.put(handle, t.accuracy);mListener.onAccuracyChanged(t.sensor, t.accuracy);}mListener.onSensorChanged(t);}
fwk层的实现
涉及代码如下:
frameworks/base/core/jni/android_hardware_SensorManager.cpp
frameworks/native/libs/sensor/*
frameworks/native/services/sensorservice/*
SensorManager的jni实现,定义在android_hardware_SensorManager.cpp这个文件中,这里不全部介绍每个本地函数的功能,只挑几个重要的
native中的SensorManager的初始化流程
android_hardware_SensorManager.cpp//初始化native层SensorManager
static jlong
nativeCreate(JNIEnv *env, jclass clazz, jstring opPackageName)
{ScopedUtfChars opPackageNameUtf(env, opPackageName);return (jlong) &SensorManager::getInstanceForPackage(String16(opPackageNameUtf.c_str()));
}
SensorManager.cpp文件路径在frameworks/native/libs/sensor文件夹下,该库编译后生成libsensor.so给到libandroid_server.so依赖,该库是一个通信中间件,实现了Sensor数据的同一进程内unix域通信,以及通过binder通信来实现部分Sensor的设置项实现。Sensor的jni类就是靠引入该库的头文件来实现与Sensor Service通信的。
获取SensorManager实例
frameworks/native/libs/sensor/SensorManager.cppSensorManager& SensorManager::getInstanceForPackage(const String16& packageName) {waitForSensorService(nullptr);Mutex::Autolock _l(sLock);SensorManager* sensorManager;//sPackageInstances是一个map数据结构,key是const String16,value是SensorManager指针auto iterator = sPackageInstances.find(packageName);//如果这个包已经获取过SensorManager,直接把返回该实例的指针if (iterator != sPackageInstances.end()) {sensorManager = iterator->second;} else {String16 opPackageName = packageName;//权限检测,判断当前调用的包名是不是有权限访问sensorif (opPackageName.size() <= 0) {sp<IBinder> binder = defaultServiceManager()->getService(String16("permission"));if (binder != nullptr) {const uid_t uid = IPCThreadState::self()->getCallingUid();Vector<String16> packages;interface_cast<IPermissionController>(binder)->getPackagesForUid(uid, packages);if (!packages.isEmpty()) {opPackageName = packages[0];} else {ALOGE("No packages for calling UID");}} else {ALOGE("Cannot get permission service");}}//创建一个新的SensorManager对象sensorManager = new SensorManager(opPackageName);// If we had no package name, we looked it up from the UID and the sensor// manager instance we created should also be mapped to the empty package// name, to avoid looking up the packages for a UID and get the same result.if (packageName.size() <= 0) {sPackageInstances.insert(std::make_pair(String16(), sensorManager));}// Stash the per package sensor manager.sPackageInstances.insert(std::make_pair(opPackageName, sensorManager));}return *sensorManager;
}
native中的消息队列初始化与数据读取
上图包括了一个native中消息队列创建的完整流程,在之前讲过registerListener的数据主要来自SensorEventQueue的dispatchSensorEvent, 这个java层的方法回调是由JNI实现中的Receiver函数来触发的,Receiver是LooperCallback的子类,实际可以看成Handler的Native样式,这个函数的dispatchEvent中会一直循环从native层的SensorEventQueue中去read数据,read函数的实现在libsensor.so库中的BitTube.cpp实现,因为sensorservice与sensor jni均运行在system_server进程内,故这里数据的读写使用unix同进程内的域通信来实现的。具体的通信实现可以看BitTube.cpp这个文件。
sensorservice实现
上面一节分析完了,Sensor数据是在jni中的Receiver中通过JNI反射java层函数,调用Java层的SensorEventQueue中的dispatchSensorEvent函数发给各个app的, Receiver通过libsensor.so不断从server端去read数据,这里Receiver可以看作是一个客户端,那么服务端的实现就是sensorservice, 其实现在frameworks/native/services/sensorservice下,这个服务的作用就是起到承上启下的作用,对上作为aidl的BnBinder端供BpBinder调用,作为Socket的Server端,往Client端写数据;对下,则是作为一个hidl的client端,通过调用hal层的sensor服务接口,来联通上下层。其主要流程如下:
- threadLoop函数中循环通过SensorDevice去poll数据,并通过SensorEventConnect的sendEvents发送到jni函数的Receiver中- SensorDevice.cpp 顾名思义就是传感器设备,该类实现了HIDL的接口,并通过hidl与hal层实现数据的poll以及设置的接口调用,列表的初始化等。- SensorEventConnection.cpp是一个事件处理通道,保持了与client进行unix域通信的双句柄,通过调用SensorEventQueue的write函数实现将sensor数据发送到client端(JNI中的Receiver类)。
threadLoop函数poll数据的实现:
SensorService.cppbool SensorService::threadLoop() {ALOGD("nuSensorService thread starting...");......//SensorDevice是单例模式的SensorDevice& device(SensorDevice::getInstance());//获取hal层版本const int halVersion = device.getHalDeviceVersion();do {//poll数据ssize_t count = device.poll(mSensorEventBuffer, numEventMax);if (count < 0) {if(count == DEAD_OBJECT && device.isReconnecting()) {device.reconnect();continue;} else {ALOGE("sensor poll failed (%s)", strerror(-count));break;}}// Reset sensors_event_t.flags to zero for all events in the buffer.for (int i = 0; i < count; i++) {mSensorEventBuffer[i].flags = 0;}//省略了很多代码//将数据发送到所有通道for (const sp<SensorEventConnection>& connection : activeConnections) {//发送数据connection->sendEvents(mSensorEventBuffer, count, mSensorEventScratch,mMapFlushEventsToConnections);needsWakeLock |= connection->needsWakeLock();// If the connection has one-shot sensors, it may be cleaned up after first trigger.// Early check for one-shot sensors.if (connection->hasOneShotSensors()) {cleanupAutoDisabledSensorLocked(connection, mSensorEventBuffer, count);}}}while (!Thread::exitPending());
}
SensorDevice的poll功能实现
ssize_t SensorDevice::poll(sensors_event_t* buffer, size_t count) {if (mSensors == nullptr) return NO_INIT;ssize_t eventsRead = 0;if (mSensors->supportsMessageQueues()) {eventsRead = pollFmq(buffer, count);//从mSensors的消息队列读值} else if (mSensors->supportsPolling()) {eventsRead = pollHal(buffer, count); //调用mSensors的poll函数取值} else {ALOGE("Must support polling or FMQ");eventsRead = -1;}return eventsRead;
}
mSensors变量类型定义如下:
sp<::android::hardware::sensors::V2_1::implementation::ISensorsWrapperBase> mSensors;
这个类型是由hidl生成的,定义在hardware/interfaces/sensors/2.1/default下,厂商自定义实现该功能。
SensorEventConnection sendEvents的实现
SensorEventConnection.cppstatus_t SensorService::SensorEventConnection::sendEvents(sensors_event_t const* buffer, size_t numEvents,sensors_event_t* scratch,wp<const SensorEventConnection> const * mapFlushEventsToConnections) //... 省略很多代码ssize_t size = SensorEventQueue::write(mChannel,reinterpret_cast<ASensorEvent const*>(scratch), count);//... 省略很多代码
}SensorEventQueue实现在libsensor.so里面,write函数就是调用了内部的网络通信封装的类BitTube.cpp来实现写入。
HAL层的实现
hal层的实现基本上每个厂商的实现都不一样,这里只跟了一下原生的,涉及到的源码如下:
hardware/libhardware/include/hardware/sensors.h
hardware/libhardware/include/hardware/sensors-base.h
hardware/interfaces/sensors/*
sensors.h定义了sensor的各类数据结构,如sensor的数据格式,sensor对应hal层的module,sensor device格式,以及实现了动态管理客制化的sensor hal实现的库或者驱动的接口。
/** convenience API for opening and closing a device */
static inline int sensors_open(const struct hw_module_t* module,struct sensors_poll_device_t** device) {return module->methods->open(module,SENSORS_HARDWARE_POLL, TO_HW_DEVICE_T_OPEN(device));
}static inline int sensors_close(struct sensors_poll_device_t* device) {return device->common.close(&device->common);
}static inline int sensors_open_1(const struct hw_module_t* module,sensors_poll_device_1_t** device) {return module->methods->open(module,SENSORS_HARDWARE_POLL, TO_HW_DEVICE_T_OPEN(device));
}static inline int sensors_close_1(sensors_poll_device_1_t* device) {return device->common.close(&device->common);
}
sensors-base.h则定义了SENSOR设备类型的值。
hardware/interfaces/sensors/1.0/default/Sensors.cpp中实现了对sensor module的load,以及设备的加载。
Sensors::Sensors(): mInitCheck(NO_INIT),mSensorModule(nullptr),mSensorDevice(nullptr) {status_t err = OK;if (UseMultiHal()) {mSensorModule = ::get_multi_hal_module_info();} else {//hal层动态加载module的so库, hw_get_module的实现在hardware/libhardware/hardware.c中,是要是动过dlopen动态加载so库。err = hw_get_module(SENSORS_HARDWARE_MODULE_ID,(hw_module_t const **)&mSensorModule);}if (mSensorModule == NULL) {err = UNKNOWN_ERROR;}if (err != OK) {LOG(ERROR) << "Couldn't load "<< SENSORS_HARDWARE_MODULE_ID<< " module ("<< strerror(-err)<< ")";mInitCheck = err;return;}//从module涨获取到sensor设备,后续的操作都需要mSensorDevice才能与真实的硬件设备通讯err = sensors_open_1(&mSensorModule->common, &mSensorDevice);
相关文章:
android fwk模块之Sensor架构
本文基于Android 12源码整理,包含如下内容: 通信架构应用层实现使用方式SensorManager抽象接口具体实现fwk层的实现native中的SensorManager的初始化流程native中的消息队列初始化与数据读取sensorservice实现HAL层的实现通信架构 应用层实现 涉及代码&…...
安装less-loader5出现webpack版本不兼容
今天遇到一个问题: 安装less-loader5之后其它包提示peerDependencies WARNING,意思是包版本不兼容。 【难题】 虽然NPM已经很自动化了,但依赖问题真的是一个难题,无法自动解决,需要人工干预调整。 【解决办法】 去查…...
Java 网络编程
1.UDP和TCPUDP和TCP是传输层协议中最核心的两种协议他们的特点分别是UDP: 无连接,不可靠传输,面向数据报,全双工TCP: 有连接,是可靠传输,面向字节流,全双工有无连接有连接:就好比两个人打电话,打电话的一方发出连接请求,被打电话的一方选择确认连接,此时双方才能进行通话无连接…...
BEV学习记录
近期可能要经常性的开展BEV工作,打算把自己觉着不错的网站拿出来记录一下。 首先贴上来我还没有细读的一篇觉着不错的文章。 自动驾驶感知新范式——BEV感知经典论文总结和对比(上)_苹果姐的博客-CSDN博客_bev视角 开山之作--LSS ECCV 202…...
Webrtc Native C++切换音频输入源
modules/audio_device/audio_device_impl.cc #include “api/audio_options.h” #include “modules/audio_device/include/factory.h” // 创建一个 AudioDeviceModule 对象 auto audio_device_module = webrtc::AudioDeviceModule::Create( webrtc::AudioDeviceModule::kPl…...
裸辞5个月,面试了37家公司,终于找到理想工作了
上半年裁员,下半年裸辞,有不少人高呼裸辞后躺平真的好快乐!但也有很多人,裸辞后的生活五味杂陈。 面试37次终于找到心仪工作 因为工作压力大、领导PUA等各种原因,今年2月下旬我从一家互联网小厂裸辞,没想…...
Mybatis-plus@DS实现动态切换数据源应用
目录1 DS实现动态切换数据源原理2 不可在事务中切换数据库分析解决3 原因解析1 DS实现动态切换数据源原理 首先mybatis-plus使用com.baomidou.dynamic.datasource.AbstractRoutingDataSource继承 AbstractDataSource接管数据源;具体实现类为com.baomidou.dynamic.d…...
SpringBoot的创建和使用
SpringBoot是什么?SpringBoot诞生的目的就是为了简化Spring开发,而相对于Spring,SpringBoot算是一个很大的升级,就如同汽车手动挡变成了自动挡。Spring:SpringBoot:SpringBoot的优点SpringBoot让Spring开发…...
居家电话客服宝典
客服分类从销售的流程来分,客服分为售前和售后。售前一般都带有销售性质,工资主要靠提成,售后一般是解答问题,工资主要看服务质量和差评量。从工作模式来分,客服分为在线客服和热线客服。在线客服以打字聊天为主&#…...
开发方案设计
1、开发流程产品需求设计-->需求粗评-->做设计方案-->粗估时-->需求细评-->排期-->开发-->提测、修bug-->code review-->上线设计方案主要是写实现思路、模块划分code review:完善代码,发现未考虑到的边界问题2、具体实现方案…...
文件路径模块pathlib
文件路径模块pathlib 文章目录文件路径模块pathlib1.概述2.创建路径2.1.创建非windos平台路径2.2.动态拼接路径joinpath2.3.替换文件名称 with_name2.4.创建固定目录2.5.创建文件夹和文件1.创建多级目录mkdir2.创建空文件3.路径解析3.1.根据路径分隔符解析路径parts3.2.获取父级…...
spring cloud篇——什么是服务熔断?服务降级?服务限流?spring cloud有什么优势?
文章目录一、spring cloud 有什么优势二、服务熔断2.1、雪崩效应2.2、DubboHystrixCommand三、服务降级四、服务限流4.1、限流算法4.2、应用级限流4.3、池化技术4.4、分布式限流4.5、基于Redis 功能的实现限流4.6、基于令牌桶算法的实现4.6.1 、Java实现一、spring cloud 有什么…...
Tomcat构建
软件架构C/S:Client/Server.需要安装才能使用。B/S:Brower/Server。有浏览器就可以。资源分类动态资源:每个用户访问相同的资源后,得到的结果可能不一样,称为动态资源。动态资源被访问后,先转换为静态资源,再被浏览器解…...
入门深度学习——基于全连接神经网络的手写数字识别案例(python代码实现)
入门深度学习——基于全连接神经网络的手写数字识别案例(python代码实现) 一、网络构建 1.1 问题导入 如图所示,数字五的图片作为输入,layer01层为输入层,layer02层为隐藏层,找出每列最大值对应索引为输…...
预算砍砍砍,IT运维如何降本增效
疫情短暂过去,一个乐观的共识正在蔓延:2023年的互联网,绝对不会比2022年更差。 “降本”是过去一年许多公司的核心策略,营销大幅缩水、亏损业务大量撤裁,以及层出不穷的裁员消息。而2023年在可预期的经济复苏下&#…...
10.Jenkins用tags的方式自动发布java应用
Jenkins用tags的方式自动发布java应用1.配置jenkins,告诉jenkins,jdk的安装目录,maven的安装目录2.构建一个maven项目指定构建参数,选择Git Paramete在源码管理中,填写我们git项目的地址,调用变量构建前执行…...
2023新华为OD机试题 - 相同数字的积木游戏 1(JavaScript)
相同数字的积木游戏 1 题目 小华和小薇一起通过玩积木游戏学习数学。 他们有很多积木,每个积木块上都有一个数字, 积木块上的数字可能相同。 小华随机拿一些积木挨着排成一排,请小薇找到这排积木中数字相同且所处位置最远的 2 块积木块,计算他们的距离。 小薇请你帮忙替她…...
重构之改善既有代码的设计(一)
1.1 何为重构,为何重构 第一个定义是名词形式: 重构(名词):对软件内部结构的一种调整,目的是在不改变「软件可察行为」前提下,提高其可理解性,降低修改成本。 「重构」的另一个用…...
Kotlin data class 数据类用法
实验数据 {"code":1,"message":"成功","data":{"name":"周杰轮","gender":1} }kotlin数据类使用方便提供如下内部Api: equals()/hashCode()对 toString() componentN()按声明顺序与属性相…...
随笔-老子不想牺牲了
18年来到这个项目组,当时只有8个人,包括经常不在的架构师和经理。当时的工位在西区1栋A座,办公桌很宽敞。随着项目的发展,入职的人越来越多,项目的工位也是几经搬迁。基本上每次搬迁时,我的工位都是挑剩下的…...
三种查找Windows10环境变量的方法
文章目录一.在设置中查看二. 在我的电脑中查看三. 在资源管理器里查看一.在设置中查看 在系统中搜索设置 打开设置,在设置功能里,点击第一项 系统 在系统功能里,左侧菜单找到关于 在关于的相关设置里可以看到高级系统设置 点击高级系…...
STM32单片机DS18B20测温程序源代码
OLED液晶屏电路接口DS18B20电路接口STM32单片机DS18B20测温程序源代码#include "sys.h"#define LED_RED PBout(12)#define LED_GREEN PBout(13)#define LED_YELLOW PBout(14)#define LED_BLUE PBout(15)#define DS18B20_IO_IN() {GPIOA->CRL&0XFFFFFFF0;GPIOA…...
java日志查看工具finder介绍
目录 一、finder介绍 二、单节点部署 1、服务器需要安装Tomcat,以2.82.16.35为例 2、进入Tomcat下目录webapps下,创建FIND目录,进入FIDN目录 3、下载findweb插件,解压缩 4、登录页面,配置 5、添加日志路径 三、…...
手写现代前端框架diff算法-前端面试进阶
前言 在前端工程上,日益复杂的今天,性能优化已经成为必不可少的环境。前端需要从每一个细节的问题去优化。那么如何更优,当然与他的如何怎么实现的有关。比如key为什么不能使用index呢?为什么不使用随机数呢?答案当然…...
【半监督医学图像分割 2022 MICCAI】CLLE 论文翻译
文章目录【半监督医学图像分割 2022 MICCAI】CLLE 论文翻译摘要1. 简介2. 方法2.1 半监督框架概述2.2 监督局部对比学习2.3 下采样和块划分3. 实验4. 结论【半监督医学图像分割 2022 MICCAI】CLLE 论文翻译 论文题目:Semi-supervised Contrastive Learning for Labe…...
vivo官网App模块化开发方案-ModularDevTool
作者:vivo 互联网客户端团队- Wang Zhenyu 本文主要讲述了Android客户端模块化开发的痛点及解决方案,详细讲解了方案的实现思路和具体实现方法。 说明:本工具基于vivo互联网客户端团队内部开源的编译管理工具开发。 一、背景 现在客户端的业…...
Python基础-数据类型之数字类型
变量中的变量值是用来存储事物状态的,事物的状态分成不同的种类(例如:人的姓名、年龄,身高、职位、工资等),因此变量值有多种不同的数据类型。 age 18 # 用整型记录年龄 salary 3.1 # 用浮点型记录…...
基于Web的6个完美3D图形WebGL库
现代前端、游戏和Web开发正是WebGL可以转化为数字杰作的东西。使用GPU绘制在浏览器屏幕上生成的矢量元素,WebGL创建交互式Web图形,从而获得用户体验。视觉元素的质量和复杂性使该工具在HTML或CSS等其他方法中脱颖而出。WebGL基础WebGL不是一个图形套件。…...
界面组件DevExpress Reporting v22.2 - 增强的Web报表组件UI
DevExpress Reporting是.NET Framework下功能完善的报表平台,它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集,包括数据透视表、图表,因此您可以构建无与伦比、信息清晰的报表。DevExpress Reporting v22.2版本已正式发布&…...
初学vector
目录 string的收尾 拷贝构造的现代写法: 浅拷贝: 拷贝构造的现代写法: swap函数: 内置类型有拷贝构造和赋值重载吗? 完善拷贝构造的现代写法: 赋值重载的现代写法: 更精简的现代写法&…...
网站建设的主要步骤有哪些/100大看免费行情的软件
点赞再看,养成习惯前言面试官:敖丙你简历上写了你会数据库调优,你都是怎么调优的?敖丙:加索引。面试官:还有么?敖丙:没了。面试官:我们公司的门你知道在哪里吧࿰…...
天津网站推广宣传/梁水才seo优化专家
Window2003服务器安全配置方案.doc...
香港疫情最新数据/西安seo学院
点击蓝字关注我?获取 高效/实用/好玩 的工具软件和教程前言输入法对我们来说无疑是最基本的必备软件了,因此选一个不流氓且无广告的输入法相当重要。谁都不想电脑右下角时不时地弹一个广告,尤其是618和双十一时,拦截软件都拦不住,…...
设计高端的国外网站/温州seo
来源:德先生(D-Technologies)FITEE“人工智能2.0”专题导读国务院近日印发《新一代人工智能发展规划》,提出了面向2030年我国新一代人工智能发展的指导思想、战略目标、重点任务和保障措施,部署构筑我国人工智能发展的…...
网站制作哪个公司好/搜索引擎竞价广告
查看软件xxx安装内容#dpkg -L xxx 查找软件#apt-cache search 正则表达式查找文件属于哪个包#dpkg -S filename apt-file search filename 查询软件xxx依赖哪些包#apt-cache depends xxx 查询软件xxx被哪些包依赖#apt-cache rdepends xxx 增加一个光盘源#sudo apt-cdrom ad…...
wordpress反应很慢/济南计算机培训机构哪个最好
方法一: 选择添加——现有项,将排除的文件添加进来。 方法二: 在解决方案资源管理器上方有一排小按钮,找到那个显示所有文件,点击后,将显示你被排除的文件,然后再在这个文件上点击右键ÿ…...