一.其主要框架如下图所示:

 
 
二.sensor的JNI层:android_hardware_SensorManager.cpp (frameworks\base\core\jni)
注册JNI:
    register_android_hardware_SensorManager
        jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager", gSystemSensorManagerMethods, NELEM(gSystemSensorManagerMethods));           
        jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager$BaseEventQueue", gBaseEventQueueMethods, NELEM(gBaseEventQueueMethods));
 
static JNINativeMethod gSystemSensorManagerMethods[] = {
            (void*)nativeClassInit },
                    nativeClassInit  :这函数的作用
                        jclass sensorClass = _env->FindClass("android/hardware/Sensor"); //得到Sensor.java (frameworks\base\core\java\android\hardware)的class引用
                        sensorOffsets.name        = _env->GetFieldID(sensorClass, "mName",      "Ljava/lang/String;"); //得到sensor的的引用值,这里应该是可以直接把数据传递到framework
                        sensorOffsets.vendor      = _env->GetFieldID(sensorClass, "mVendor",    "Ljava/lang/String;");
                        sensorOffsets.version     = _env->GetFieldID(sensorClass, "mVersion",   "I");
 
            (void*)nativeGetNextSensor }, 
                    size_t count = mgr.getSensorList(&sensorList); //得到sensor列表,返回sensor个数
                        *list = mSensorList; //在SensorManager.cpp (frameworks\native\libs\gui)中
                        return mSensors.size();
                    env->SetObjectField(sensor, sensorOffsets.name,      name);  //获得sensor的信息,就是nativeClassInit 中需要得到的
                    env->SetObjectField(sensor, sensorOffsets.vendor,    vendor);
};
 
static JNINativeMethod gBaseEventQueueMethods[] = {
            (void*)nativeInitSensorEventQueue },
                 sp<SensorEventQueue> queue(mgr.createEventQueue()); 
                        queue = new SensorEventQueue(connection); //构造SensorEventQueue
                               mRecBuffer = new ASensorEvent[MAX_RECEIVE_BUFFER_EVENT_COUNT]; //生成一些ASensorEvent,用了记录sensor的信息
                 android_os_MessageQueue_getMessageQueue //得到_MessageQueue


            (void*)nativeEnableSensor },
                    receiver->getSensorEventQueue()->enableSensor(handle, rate_us, maxBatchReportLatency, reservedFlags); //enable sensor
                        enableSensor  //在SensorEventQueue.cpp (frameworks\native\libs\gui)中
                            mSensorEventConnection->enableDisable(handle, true, us2ns(samplingPeriodUs), us2ns(maxBatchReportLatencyUs), reservedFlags); 
                                    
            (void*)nativeDisableSensor }, //关闭sensor
 
            (void*)nativeDestroySensorEventQueue },
 
            (void*)nativeFlushSensor },
};
 
    实现SensorManager.java中的native函数,它主要调用SenrsorManager.cpp和SensorEventQueue.cpp中的类来完成相关的工作。
   SenrsorManager.cpp与SensorService.cpp主要以binder通信
 
 

三.SensorService.cpp

  SensorService作为一个轻量级的system service,它运行于SystemServer内,即在system_init<system_init.cpp>调用SensorService::instantiate();

      SensorService主要功能如下:
          1) SensorService::instantiate创建实例对象,并增加到ServiceManager中,且创建并启动线程,并执行threadLoop
          2) threadLoop从sensor驱动获取原始数据,然后通过SensorEventConnection把事件发送给客户端
          3) BnSensorServer的成员函数负责让客户端获取sensor列表和创建SensorEventConnection

 在addService时,第一次构建sp强引用对象时,会调用onFirstRef函数 
void SensorService::onFirstRef()  
       SensorDevice& dev(SensorDevice::getInstance()); // 获取SensorDevice实例, 也就是调用下面的SensorDevice::SensorDevice()。    
      ssize_t count = dev.getSensorList(&list);  调用SensorDevice.getSensorList获取sensor_t列表 
     //虚拟的sensor,也就是没有硬件,通过其他硬件计算出来的。
        uint32_t virtualSensorsNeeds = (1<<SENSOR_TYPE_GRAVITY) | (1<<SENSOR_TYPE_LINEAR_ACCELERATION) | (1<<SENSOR_TYPE_ROTATION_VECTOR); 
        registerSensor( new HardwareSensor(list[i]) ); //根据硬件sensor_t创建HardwareSensor,然后加入mSensorList(Sensor),和mSensorMap(HardwareSensor)中 
        mUserSensorList = mSensorList; // build the sensor list returned to users
        /* 这里加入的前提是hasGyro,也就是需要陀螺仪 ,才能加入虚拟sensor*/
         aSensor = registerVirtualSensor( new RotationVectorSensor() );   //根据硬件sensor_t创建对应的senosr(如GravitySensor), 根据硬件sensor_t创建对应的senosr(如GravitySensor), 
         if (virtualSensorsNeeds & (1<<SENSOR_TYPE_ROTATION_VECTOR)) {
                    mUserSensorList.add(aSensor);     //加入UserSensorList,这里RotationVectorSensor,GravitySensor,LinearAccelerationSensor, OrientationSensor, CorrectedGyroSensor
           }
        / *后面有一些关于batching的sensor的设置,batching sensor也就是支持批处理的sensor,有fifo */
         Looper = new Looper(false);  //消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper
        mSensorEventBuffer = new sensors_event_t[minBufferSize]; //sensors_event用于藏消息
        mSensorEventScratch = new sensors_event_t[minBufferSize];
        run("SensorService", PRIORITY_URGENT_DISPLAY); // run("SensorService", PRIORITY_URGENT_DISPLAY);运行线程,并执行threadLoop
 
 
bool SensorService::threadLoop()
    ssize_t count = device.poll(mSensorEventBuffer, numEventMax);  //调用SensorDevice.poll获取sensors_event_t事件 
  fusion.process(event[i]);对每一个事件,执行SensorFusion.process 
   activeConnections[i]->sendEvents(mSensorEventBuffer, count, mSensorEventScratch, mMapFlushEventsToConnections); 调用SensorService::SensorEventConnection::sendEvents,把事件发送给所有的listener
 
status_t SensorService::SensorEventConnection::enableDisable
    err = mService->enable(this, handle, samplingPeriodNs, maxBatchReportLatencyNs,  reservedFlags);
        SensorService::enable
            err = sensor->activate(connection.get(), true); //使能sensor
 
 
 
 
四.sensor上层调用中间层的入口
SensorDevice.cpp (frameworks\native\services\sensorservice)
 

SensorDevice::SensorDevice()

    1.hw_get_module(SENSORS_HARDWARE_MODULE_ID, (hw_module_t const**)&mSensorModule); //这个在Sensors.c (vendor\mediatek\proprietary\hardware\sensor)
  
  2.err = sensors_open_1(&mSensorModule->common, &mSensorDevice); 打开sensor
   
3.err = pressure_sensors_open(&mSensorDevicePressure); //这里我们加入的温度气压传感器
  
  4. mSensorModule->get_sensors_list(mSensorModule, &list); //得到sensor列表,Sensors.c (vendor\mediatek\proprietary\hardware\sensor)
  
  5.for (i=0; i<size_t(count-PRES_TEMP_SENSORS_CNT); i++) {
                 mActivationCount.add(list[i].handle, model); //把sensor加入到一个vector,handle是sensor唯一的标志 , Hwmsen_chip_info.c (vendor\mediatek\proprietary\hardware\sensor)
                mSensorDevice->activate(reinterpret_cast<struct sensors_poll_device_t *>(mSensorDevice), list[i].handle, 0);  //激活sensor
            }
    6.for (; i<size_t(count); i++) {
          mActivationCount.add(list[i].handle, model);
          mSensorDevicePressure->activate(mSensorDevicePressure, list[i].handle, 0);
 
 
sensor的重要结构体
        Sensor设备结构体sensors_poll_device_t,对标准硬件设备hw_device_t结构体的扩展,主要完成读取底层数据,并将数据存储在struct sensors_poll_device_t结构体中,poll函数用来获取底层数据,调用时将被阻塞定义如下:

struct sensors_poll_device_t {

struct hw_device_t common;

//Activate/deactivate one sensor

    int (*activate)(struct sensors_poll_device_t *dev,

            int handle, int enabled);

    //Set the delay between sensor events in nanoseconds for a given sensor.

    int (*setDelay)(struct sensors_poll_device_t *dev,

            int handle, int64_t ns);

    //获取数据

    int (*poll)(struct sensors_poll_device_t *dev,

            sensors_event_t* data, int count);

};

 

  1. typedef struct hw_device_t {  
  2.     /** tag must be initialized to HARDWARE_DEVICE_TAG */  
  3.     uint32_t tag;  
  4.   
  5.     /** version number for hw_device_t */  
  6.     uint32_t version;  
  7.   
  8.     /** reference to the module this device belongs to */  
  9.     struct hw_module_t* module;  
  10.   
  11.     /** padding reserved for future use */  
  12.     uint32_t reserved[12];  
  13.   
  14.     /** Close this device */  
  15.     int (*close)(struct hw_device_t* device);  
  16.   
  17. } hw_device_t;  
     

     

        
 
  1. typedef struct sensors_event_t {  
  2.     /* must be sizeof(struct sensors_event_t) */  
  3.     int32_t version;  
  4.   
  5.     /* sensor identifier */  
  6.     int32_t sensor;  
  7.   
  8.     /* sensor type */  
  9.     int32_t type;  
  10.   
  11.     /* reserved */  
  12.     int32_t reserved0;  
  13.   
  14.     /* time is in nanosecond */  
  15.     int64_t timestamp;  
  16.   
  17.     union {  
  18.         float           data[16];  
  19.   
  20.         /* acceleration values are in meter per second per second (m/s^2) */  
  21.         sensors_vec_t   acceleration;  
  22.   
  23.         /* magnetic vector values are in micro-Tesla (uT) */  
  24.         sensors_vec_t   magnetic;  
  25.   
  26.         /* orientation values are in degrees */  
  27.         sensors_vec_t   orientation;  
  28.   
  29.         /* gyroscope values are in rad/s */  
  30.         sensors_vec_t   gyro;  
  31.   
  32.         /* temperature is in degrees centigrade (Celsius) */  
  33.         float           temperature;  
  34.   
  35.         /* distance in centimeters */  
  36.         float           distance;  
  37.   
  38.         /* light in SI lux units */  
  39.         float           light;  
  40.   
  41.         /* pressure in hectopascal (hPa) */  
  42.         float           pressure;  
  43.   
  44.         /* relative humidity in percent */  
  45.         float           relative_humidity;  
  46.     };  
  47.     uint32_t        reserved1[4];  
  48. } sensors_event_t;  
    来源: 

     

 
 
struct pollfd {
int fd;        /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
};

 
 
 
五.详解四中每一步
    第1步
.hw_get_module(SENSORS_HARDWARE_MODULE_ID, (hw_module_t const**)&mSensorModule); 
        根据SENSORS_HARDWARE_MODULE_ID得到Sensors.c (vendor\mediatek\proprietary\hardware\sensor)中的HAL层代码
            struct sensors_module_t HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,  
        .version_major = 1,
        .version_minor = 0,
        .id = SENSORS_HARDWARE_MODULE_ID,
        .name = "MTK SENSORS Module",
        .author = "Mediatek",
        .methods = &sensors_module_methods,
    },
    .get_sensors_list = sensors__get_sensors_list,
};
 

这里主要函数是:
    sensors__get_sensors_list

        *list = sSensorList; //得到sensorlist
               struct sensor_t sSensorList[MAX_NUM_SENSOR] ={};   在Hwmsen_chip_info.c (vendor\mediatek\proprietary\hardware\sensor)中,我推测是apk判断是否支持某种sensor的依据 
               #ifdef CUSTOM_KERNEL_ACCELEROMETER   //这个与ProjectConfig.mk这个相对应
     {
        .name       = ACCELEROMETER,
        .vendor     = ACCELEROMETER_VENDER,
        .version    = 3,
.handle     = ID_ACCELEROMETER+ID_OFFSET,  //这个在Hwmsensor.h (kernel-3.10\include\linux)中定义,是标示各种sensor的唯一值,调用的时候使用
 
        .type       = SENSOR_TYPE_ACCELEROMETER,  //这个在Hwmsensor.h (kernel-3.10\include\linux)中定义,标示sensor的类型
        .maxRange   = ACCELEROMETER_RANGE,//32.0f,  
        .resolution = ACCELEROMETER_RESOLUTION,//4.0f/1024.0f ,上报的精度在4.0f---1024f
        .power      = ACCELEROMETER_POWER,//130.0f/1000.0f,  //估计上电用的时间
        .minDelay   = 10000, //最少的上报时间=10ms
.maxDelay   = 1000000, //1s,最大上报时间
        .reserved   = {}
     },
   #endif
 
 

第2步:

        
 
第3步:
    pressure_sensors_open(&mSensorDevicePressure); //这里我们加入的温度气压传感器
          sensors__open_sensors((struct hw_device_t**)device); //打开sensor

        sensors_poll_context_t *dev = new sensors_poll_context_t(); 

              mSensors[pre] = new PresSensor();

             

                   PresSensor::PresSensor() //进入构造函数

                        SensorBase(NULL, "lps331ap_prs") //在PresSensor.cpp (frameworks\native\services\sensorservice)

                               data_fd = openInput(data_name); //data_name = lps331ap_prs

                                       dir = opendir(dirname);  //dirname=/dev/input

                                       readdir(dir) //循环读取dir下每一个文件,找到lps331ap_prs这个文件,pressure_presence_flag = 1

                         

                    mEnabled(1),

                    mEnabledP(1),

                    mEnabledT(1),

                    currHzPollFreq(10.0f),

                    mInputReader(4)

                             mPendingEventP //设置mPendingEventP结构体,他一个sensors_event_t变量   

                   property_get("ro.hardware.accsensor", buffer, INPUT_SYSFS_PATH_PRES); //得到INPUT_SYSFS_PATH_PRES "/sys/class/input/input5/device/"

                   getCompenseParam(); //得到补偿参数

                        LPS331AP_InitializeLibrary(calvalues); //算法相关的

        dev->device.activate        = poll__activate; //填充结构体

        dev->device.setDelay        = poll__setDelay; 

        dev->device.poll            = poll__poll;

第4步:
    mSensorModule->get_sensors_list(mSensorModule, &list); //得到sensor列表,Sensors.c (vendor\mediatek\proprietary\hardware\sensor)
    
 
第5步:
 
 
第六步:
      6.for (; i<size_t(count); i++) {
          mActivationCount.add(list[i].handle, model); //把sensor加入到一个vector,handle是sensor唯一的标志 , Hwmsen_chip_info.c 
          mSensorDevicePressure->activate(mSensorDevicePressure, list[i].handle, 0);  

    dev->device.activate        = poll__activate; //在pressure_sensors_open里面赋值了这个函数指针

                    poll__activate //在Sensors.cpp (frameworks\native\services\sensorservice)中
                        ctx->activate(handle, enabled); //进行enable                   
                            sensors_poll_context_t::activate;在Sensors.cpp (frameworks\native\services\sensorservice)
                        index = handleToDriver(handle);       //这里返回0
                        err =  mSensors[index]->enable(handle, enabled);                        
                             PresSensor::enable      //在PresSensor.cpp (frameworks\native\services\sensorservice)中             

                                 fd = open(input_sysfs_path, O_RDWR); //打开/得到INPUT_SYSFS_PATH_PRES "/sys/class/input/input5/device/enable"

                                 err = write(fd, buf, sizeof(buf)); // 写入1,打开sensor

                         write(mWritePipeFd, &wakeMessage, 1);//写一个W到pipe,pipe在open的时候创建

    

创建pipe:Sensors.cpp (frameworks\native\services\sensorservice)的sensors_poll_context_t::sensors_poll_context_t()

    int wakeFds[2];

    int result = pipe(wakeFds);  //创建pipe
    ALOGE_IF(result<0, "error creating wake pipe (%s)", strerror(errno));
    fcntl(wakeFds[0], F_SETFL, O_NONBLOCK); //设置文件的flags为O_NONBLOCK
    fcntl(wakeFds[1], F_SETFL, O_NONBLOCK);
    mWritePipeFd = wakeFds[1]; //写管道
    mPollFds[wake].fd = wakeFds[0]; //读管道
    mPollFds[wake].events = POLLIN;
    mPollFds[wake].revents = 0;
 
 
 

6.sensor数据的读取

    

bool SensorService::threadLoop()  //

SensorService.cpp (frameworks\native\services\sensorservice)

            ssize_t count = device.poll(mSensorEventBuffer, numEventMax);
                    SensorDevice::poll(sensors_event_t* buffer, size_t count) //SensorDevice.cpp (frameworks\native\services\sensorservice)
                        c_pressure = mSensorDevicePressure->poll(mSensorDevicePressure, buffer, PRES_TEMP_SENSORS_CNT);
        //ALOGE( "SensorDevice::poll, mSensorDevicePressure return = %d\n", c_pressure);
        c = mSensorDevice->poll(reinterpret_cast<struct sensors_poll_device_t *> (mSensorDevice), buffer+PRES_TEMP_SENSORS_CNT, count-PRES_TEMP_SENSORS_CNT);
                                return ctx->pollEvents(data, count);  //Nusensors.cpp (vendor\mediatek\proprietary\hardware\sensor)
                                        int nb = sensor->readEvents(data, count);  //Sensors.cpp (frameworks\native\services\sensorservice)
                                                 int Hwmsen::readEvents(sensors_event_t* data, int count) //Hwmsen.cpp (vendor\mediatek\proprietary\hardware\sensor)
                                                        n = mInputReader.fill(mdata_fd);  //后面有些细节没有深究了
                                         n = poll(mPollFds, numFds, nbEvents ? 0 : -1);  //poll,等待事件
 
 
 
 
7.虚拟sensor的读取过程
注册过程
SensorService::onFirstRef
     ssize_t count = dev.getSensorList(&list); //得到sensorlist
    if (hasGyro) { //注册各种sensor
         aSensor = registerVirtualSensor( new RotationVectorSensor() );
                if (virtualSensorsNeeds & (1<<SENSOR_TYPE_ROTATION_VECTOR)) {
                    mUserSensorList.add(aSensor);
                }
 
                aSensor = registerVirtualSensor( new GravitySensor(list, count) );
                if (virtualSensorsNeeds & (1<<SENSOR_TYPE_GRAVITY)) {
                    mUserSensorList.add(aSensor);
                }
 
                aSensor = registerVirtualSensor( new LinearAccelerationSensor(list, count) );
                if (virtualSensorsNeeds & (1<<SENSOR_TYPE_LINEAR_ACCELERATION)) {
                    mUserSensorList.add(aSensor);
                }
 
                aSensor = registerVirtualSensor( new OrientationSensor() );
                if (virtualSensorsNeeds & (1<<SENSOR_TYPE_ROTATION_VECTOR)) {
                    // if we are doing our own rotation-vector, also add
                    // the orientation sensor and remove the HAL provided one.
                    mUserSensorList.replaceAt(aSensor, orientationIndex);
                }
 
                // virtual debugging sensors are not added to mUserSensorList
                registerVirtualSensor( new CorrectedGyroSensor(list, count) );
                registerVirtualSensor( new GyroDriftSensor() );       
}
 
enable过程
    //SensorService.cpp (frameworks\native\services\sensorservice)
    SensorService::enable
        SensorInterface* sensor = mSensorMap.valueFor(handle); //得到sensor的接口,虚拟sensor的interface
        mActiveVirtualSensors.add(handle, sensor);  //加入到mActiveVirtualSensors链
        err = sensor->activate(connection.get(), true); //调用active
        SensorFusion::activate(void* ident, bool enabled) //调用SensorFusion.cpp (frameworks\native\services\sensorservice)
             mSensorDevice.activate(ident, mAcc.getHandle(), enabled); //使能加速度传感器
            mSensorDevice.activate(ident, mMag.getHandle(), enabled); //使能磁传感器
            mSensorDevice.activate(ident, mGyro.getHandle(), enabled); //使能陀螺仪
                 SensorDevice::activate //SensorDevice.cpp (frameworks\native\services\sensorservice) //这里就是开始使能实体sensor
 
 
获取数据过程
    SensorService::threadLoop() //
        count = device.poll(mSensorEventBuffer, numEventMax); //这里获取实体sensor的数据
             // handle virtual sensors

            if (count && vcount) { //如果是虚拟sensor
                SensorFusion& fusion(SensorFusion::getInstance());
                fusion.isEnabled() //如果使能

                fusion.process(event[i]); //调用process
                  SensorFusion::process //调用SensorFusion.cpp (frameworks\native\services\sensorservice),这里传入的是实体sensor获取来的数据

                     mFusion.handleGyro(vec3_t(event.data), dT); //处理各自的数据

             mFusion.handleMag(mag);

                    mFusion.handleAcc(acc); 

    SensorInterface* si = mActiveVirtualSensors.valueAt(j); //得到虚拟sensor的接口
    if (si->process(&out, event[i])) { //获取计算好的值
           mSensorEventBuffer[count + k] = out;}

         // record the last synthesized values

          recordLastValueLocked(&mSensorEventBuffer[count], k);
          // sort the buffer by time-stamps
          sortEventBuffer(mSensorEventBuffer, count); //给这些event排序
  activeConnections[i]->sendEvents(mSensorEventBuffer, count, mSensorEventScratch,),通过activeConnections发送到上层
 

注意:

如果加速度,磁传感器,陀螺仪的数据不正常可能导致虚拟传感器没有数据!!
 
 
 
 
 
 

Android sensor架构分析的更多相关文章

  1. android sensor架构

    Android Sensor 架构深入剖析 作者:倪键树,华清远见嵌入式学院讲师. 1.Android sensor架构 Android4.0系统内置对传感器的支持达13种,它们分别是:加速度传感器 ...

  2. Android Sensor 架构深入剖析【转】

    本文转载自: 1.Android sensor架构 Android4.0系统内置对传感器的支持达13种,它们分别是:加速度传感器 (accelerometer).磁力传感器(magnetic fiel ...

  3. Qualcomm Android display架构分析

    Android display架构分析(一) http://blog.csdn.net/BonderWu/archive/2010/08/12/5805961.aspx http://hi.baidu ...

  4. 高通Android display架构分析

    目录(?)[-] Kernel Space Display架构介绍 函数和数据结构介绍 函数和数据结构介绍 函数和数据结构介绍 数据流分析 初始化过程分析 User Space display接口 K ...

  5. Android MVP架构分析

    App架构在Android开发者中一直是讨论比较多的一个话题,目前讨论较多的有MVP.MVVM.Clean这三种.google官方对于架构的态度一直是非常开放的,让开发者自主选择组织和架构app的方式 ...

  6. (转)App工程结构搭建:几种常见Android代码架构分析

    关于Android架构,因为手机的限制,目前我觉得也确实没什么大谈特谈的,但是从开发的角度,看到整齐的代码,优美的分层总是一种舒服的享受的. 从艺术的角度看,其实我们是在追求一种美. 本文先分析几个当 ...

  7. Android应用架构分析

    一.res目录: 1.属性:Android必需: 2.作用:存放Android项目的各种资源文件.这些资源会自动生成R.java. 2.1.layout:存放界面布局文件. 2.2.strings.x ...

  8. Android Sensor详解(1)简介与架构【转】

    本文转载自:https://blog.csdn.net/u013983194/article/details/53244686 最近在学习有关如何porting sensor的东西,仅借此机会写博客来 ...

  9. Android架构分析之Android消息处理机制(二)

    作者:刘昊昱 博客:http://blog.csdn.net/liuhaoyutz Android版本号:4.4.2 在上一篇文章中我们看了一个使用Handler处理Message消息的样例,本文我们 ...

随机推荐

  1. Pytest学习(三) - setup和teardown的使用

    一.前言 从文章标题可以看出,就是初始化和释放的操作,根据我的java习惯来学习pytest,个人感觉没差太多,理解上也不是很难. 哦,对了,差点跑题了,这个框架是基于Python语言的,在学习的时候 ...

  2. Git版本管理器操作步骤

    组长建立管理器: 第一步: 管理器网址:https://gitee.com/ 第二步:注册个人信息 第三步(添加项目) 第四步(兴建文件)注意:必须双层文件夹 第五步:打开VS开发工具 第六步:去把刚 ...

  3. 使用ModelForm校验数据唯一性

    在设计模型类的时候,将指定字段设置unique=true属性,可以保证该字段在数据库中的唯一性. 使用ModelForm可以将指定模型类快速生成表单元素.在提交数据后,使用is_valid()校验时, ...

  4. 使用Node.js给图片加水印的方法

    一.准备工作: 首先,确保你本地已经安装好了node环境. 然后,我们进行图像编辑操作需要用到一个Node.js的库:images. 这个库的地址是:https://github.com/zhangy ...

  5. 企业微信公众号告警Python脚本

    #!/usr/bin/env python # -*- coding: utf-8 -*- import time import requests import json import os impo ...

  6. Hash 哈希(上)

    Hash 哈希(上) 目录 Hash 哈希(上) 简介 Hash函数的构造 取余法 乘积取整法 其他方法 冲突的处理 挂链法 开放定址法 线性探查法 二次探查法 双哈希法 结语 简介 Hash,又称散 ...

  7. 仅用六种字符来完成Hello World,你能做到吗?

    Hello World 对于每一个开发者来说都不陌生,因为在我们学习任何一个语言或框架的时候,都会有一个Hello World的案例来帮助我们快速入门. 如果我们使用JavaScript来输出Hell ...

  8. bash中选择结构、循环结构与break、continue

    if两种选择结构 if 测试条件; then 程序块 else 程序块 fi if 测试条件1; then 程序块 elif 测试条件2; then 程序块 ... elif 程序条件n; then ...

  9. python动态规划

    动态规划: 动态规划表面上很难,其实存在很简单的套路:当求解的问题满足以下两个条件时, 就应该使用动态规划:        主问题的答案 包含了 可分解的子问题答案 (也就是说,问题可以被递归的思想求 ...

  10. css水平垂直居中 三种最常用的方式

    代码在下面,可以直接用. bb两句,个人见解: text-align 是让里面的内容水平居中 line-height 是行高,行高等于元素的高度 就能让内容垂直居中 left和top 50% 是根据h ...