考研已经过去了,android驱动的学习也断了半年多了,现在重新捡起来学习,回顾一下Android驱动的大体框架。

Android系统的核心是java,其有一个David虚拟机。Android-app操作硬件也相当于是java操作硬件。

在Linux系统上操作硬件是通过open read write等来实现,也就是操作C库。如果java能直接调用C库中的函数,也就解决了app操作硬件的问题。

下面的文章是java调用C/C++库的方法。

链接:JAVA程序通过JNI调用C/C++库

1.方法1——jni调用底层驱动

在android框架中写入c/c++直接调用底层linux驱动,并向上提供jni接口给应用程序:

优点:简单易行;

缺点:主要在于驱动程序,由于在linux中需要遵循GPL协议,需要开源,而许多厂商的一些代码不希望开源。

而且像屏幕等设备可能需要多个app同时操作,这样的话将会导致各种各样的问题。

2.方法2——增加硬件抽象层

将驱动程序一分为二,一部分开源在内核中,一部分不开源在android框架中:

二、举例led android驱动:

从这里我们将看到整个应用框架层到底层驱动的走向。首先,无论是哪种方法,我们都需要实现一个linux驱动以供上层访问led资源。

同样也是通过jni来加载C库,从而通过调用open等来实现对硬件的操作。Android为了实现多个APP能操作同一个硬件,硬件不由app来直接操作,而是有SystemServer来操作。app需要把请求通过serviceManager发给SystemServer,由SystemServer最终完成对硬件的操作。

这里我们反过来思考一个app操作硬件的过程:

1、app需要申请服务和获得服务getservice。

而这个服务是有接口完成的。所以第一步我们需要建立一个aidl文件,来生成接口类。

frameworks/base/core/java/android/os/ILedService.aidl

同时修改 frameworks/base/Android.mk, 加入新建的aidl文件。

现在以实现一个利用app来通过串口对外发送数据的案例;

1. 首先创建文件  frameworks/base/core/java/android/os/IMySerialService.aidl

package android.os;

/** {@hide} */
interface IMySerialService
{
int serialOpen( String filename, int flags );
int serialSetOpt( int fd,int nSpeed,int nBits,char nEvent,int nStop );
String serialRead( int fd, int len );
int serialWrite( int fd, String txData );
void serialClose( int fd );
}

  同时需要修改Android.mk文件添加我们的aidl文件

2、自动生成ILedService.java

mmm frameworks/base/

编译自动生成

out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/ILedService.java

ILedService.java文件获得了,现在就需要新建与之对应的java文件,实现java下的对硬件操作的函数。

  2.运行:mmm frameworks/base/  进行编译,将会生成 out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/IMySerialService.java 文件。

    下一步就是要实现 ISerialService.java 中被自动创建的接口函数了

3、创建 LedService.java 实现接口函数

创建完LedService.java后将其放到frameworks/base/services/core/java/com/android/server/中,其上层Android.mk会自动包含此java文件。

  3.  新建文件  frameworks/base/services/core/java/com/android/server/SerialService.java

  

package com.android.server;

import android.os.IMySerialService;
import android.util.Slog; public class MySerialService extends IMySerialService.Stub {
private static final String TAG = "MySerialService"; public native static int nativeSerialOpen(String filename, int flags ) ;
public native static int nativeSerialSetOpt(int fd, int nSpeed, int nBits, char nEvent, int nStop) ;
public native static java.lang.String nativeSerialRead(int fd, int len ) ;
public native static int nativeSerialWrite( int fd, java.lang.String txData ) ;
public native static void nativeSerialClose(int fd ); public int serialOpen(java.lang.String filename, int flags) throws android.os.RemoteException {
return nativeSerialOpen(filename,flags);
}
public int serialSetOpt(int fd, int nSpeed, int nBits, char nEvent, int nStop) throws android.os.RemoteException {
return nativeSerialSetOpt( fd, nSpeed, nBits, nEvent, nStop );
}
public java.lang.String serialRead(int fd, int len) throws android.os.RemoteException {
return nativeSerialRead( fd, len );
}
public int serialWrite(int fd, java.lang.String txData) throws android.os.RemoteException {
return nativeSerialWrite( fd, txData);
}
public void serialClose(int fd) throws android.os.RemoteException {
nativeSerialClose(fd);
} }

    public int serialOpen(java.lang.String filename, int flags) throws android.os.RemoteException

    这些函数的定义在自动生成的IMySerialService.java的最下面,直接拷贝过来实现即可。

4、将服务注册到Service Manager当中

修改frameworks/base/services/java/com/android/server/SystemServer.java

这样的话,app就能获得服务,从而实现对SystemServer的通信,从而实现对硬件的操作。现在就是需要对SystemServer进行修改了。

SystemServer对硬件的操作也是jni,所以其也需要加载C/C++库。在Android系统中已经把所有对硬件操作的jni文件打包为一个.so库,所以我们需要做的是在so库中添加我们对硬件的支持。

  4. 将下面的代码加入到 SystemServer.java 中,这样的话就把服务加入到了ServiceManager

            Slog.i(TAG, "Serial Service");
ServiceManager.addService("serial", new SerialService());

5、实现com_android_server_LedService.cpp   供 LedService.java使用

将jni文件放到frameworks/base/services/core/jni/目录中。还要注册native接口,在frameworks/base/services/core/jni/onload.cpp中修改。

并修改Android.mk文件,加入com_android_server_LedService.cpp的编译。

同时还要修改onload.cpp,添加 register_android_server_MySerialService(env);  和 函数声明。

#define LOG_TAG "MySerialService"

#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h" #include <utils/misc.h>
#include <utils/Log.h> #include <stdio.h> namespace android
{
#if 1
static jstring charTojstring(JNIEnv* env, const char* pat) {
//定义java String类 strClass
jclass strClass = (env)->FindClass("Ljava/lang/String;");
//获取String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
jmethodID ctorID = (env)->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
//建立byte数组
jbyteArray bytes = (env)->NewByteArray(strlen(pat));
//将char* 转换为byte数组
(env)->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*) pat);
// 设置String, 保存语言类型,用于byte数组转换至String时的参数
jstring encoding = (env)->NewStringUTF("GB2312");
//将byte数组转换为java String,并输出
return (jstring) (env)->NewObject(strClass, ctorID, bytes, encoding);
} char* jstringToChar(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("GB2312");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
rtn = (char*) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
#endif jint jniSerialOpen( JNIEnv *env, jobject clazz, jstring filename, jint flags )
{
char *arrChars = jstringToChar(env, filename );
ALOGI("jniSerialOpen:%s,%d\n",arrChars,flags); return flags+1;
}
jint jniSerialSetOpt( JNIEnv *env, jobject clazz, jint fd, jint nSpeed, jint nBits, char nEvent, jint nStop)
{
ALOGI("jniSerialSetOpt:%d,%c\n",fd,nEvent);
return fd+1;
}
jstring jniSerialRead( JNIEnv *env, jobject clazz, jint fd, jint len )
{
char arrTx[] = "This is a chars";
ALOGI("jniSerialRead:%d,%c\n",fd,len);
/* CStr2Jstring */ return charTojstring(env, arrTx);
}
jint jniSerialWrite( JNIEnv *env, jobject clazz, jint fd, jstring txData )
{
char *arrChars = jstringToChar(env, txData );
ALOGI("jniSerialWrite:%d,%s\n",fd,arrChars); return 0;
}
void jniSerialClose( JNIEnv *env, jobject clazz, jint fd )
{
ALOGI("jniSerialClose:%d\n",fd);
} static JNINativeMethod method_table[] = {
{ "nativeSerialOpen", "(Ljava/lang/String;I)I", (void*)jniSerialOpen },
{ "nativeSerialSetOpt", "(IIICI)I", (void*)jniSerialSetOpt },
{ "nativeSerialRead", "(II)Ljava/lang/String;", (void*)jniSerialRead },
{ "nativeSerialWrite", "(ILjava/lang/String;)I", (void*)jniSerialWrite },
{ "nativeSerialClose", "(I)V", (void*)jniSerialClose }, }; int register_android_server_MySerialService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/MySerialService",
method_table, NELEM(method_table));
} };

这里其实已经差不多实现了对硬件的操作,因为我们实现了对cpp文件的调用并打包进系统,cpp文件就能直接加载C库,调用open等函数实现对硬件的操作。

但是这样的话有一个问题,就是如果驱动有点儿问题,我们就需要修改com_android_server_LedService.cpp,并重新编译系统、烧写系统。这样的话不是很方便。我们可以再引入硬件抽象层(HAL),这样更有利于搭建整个系统。

注意:编译的时候使用:

mmm frameworks/base/services/
mmm frameworks/base/
make snod

只执行  mmm frameworks/base/  将不会 frameworks/base/services/ 下修改的文件进行编译。 这个要注意。

JNI 向上提供本地函数,向下加载HAL文件并调用HAL的函数

HAL负责访问驱动程序执行硬件操作(dlopen)。

这里编译系统,运行app  即可打印出信息(logcat | grep "MySerialSerice")

APP代码: 需要添加jar包。在 out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar

package com.lmissw.serialdevicetest;

import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.IMySerialService;
import android.util.Log;
import android.view.View;
import android.widget.Button; import static android.os.ServiceManager.getService; public class MainActivity extends AppCompatActivity {
Button iButton=null;
private IMySerialService iSerialService = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); iButton = findViewById(R.id.button);
iButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
iSerialService = IMySerialService.Stub.asInterface(getService("myserial"));
try {
int fd = iSerialService.serialOpen("/dev/s3c2410_serial2",0);
iSerialService.serialSetOpt(2,3,4,'t',6);
Log.i("serialTest","App serialSetOpt"+fd);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}); }
}

6、实现Hal层程序

JNI 向上提供本地函数,向下加载HAL文件并调用HAL的函数。

HAL负责访问驱动程序并执行硬件操作。而HAL层代码被编程一个.so动态库。

所以JNI加载HAL就是加载动态库,而在linux系统中加载动态库使用的是dlopen。

在Android系统中,不直接使用dlopen,是因为Android对dlopen做了一层封装,使用的是hw_get_module("模块名");

而hw_get_module最终还是调用dlopen,那么就需用从模块名拿到文件名了。

  模块名 >> 文件名

    在模块名转化为名时,使用的是hw_module_by_class(模块名,NULL),在其中将name=“模块名”;

      然后使用property_get 获得属性值(价值对),先查询ro.hardware.模块名、ro.hardware、ro.product.board、ro.board.platform、ro.arch。上面的每个属性都对应一个subname。

      然后使用hw_module_exists 在 HAL_LIBRARY_PATH 环境变量、/vendor/lib/hw、/system/lib/hw 三个文件夹中 判断 "name"."subname".so 文件是否存在。如果都没有则查找 "name".default.so。

      如果上述的模块存在,则调用load 加载,在load中使用了dlopen。再使用dlsym("HMI")从so获得名为hw_module_t结构体。然后判断结构体中的名字和name是否一致,一致则表明找到了模块。

JNI怎么使用HAL

  使用hw_get_module获得一个hw_module_t结构体。调用module中的open函数来获得一个hw_device_t结构体。并且把hw_device_t结构体转换为设备自定义的结构体。自定义的结构体第一个成员是 hw_device_t结构体。

在 hardware/libhardware/include/hardware/ 下创建hal层的头文件,在hardware/libhardware/modules/ 目录下创建一个新的目录用来 存放hal的C文件,并创建一个Android.mk文件。

编译的话用 mmm  hardware/libhardware/modules/+创建的目录名。

/* filename: Android.mk */
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := myserial.default LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_C_INCLUDES := hardware/libhardware
LOCAL_SRC_FILES := myserial.c
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE_TAGS := eng include $(BUILD_SHARED_LIBRARY)

myserial.c

#define LOG_TAG "MySerialHal"

#include <hardware/myserial.h>
#include <hardware/hardware.h>
#include <cutils/log.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h> int halSerialOpen( char* filename, int flags )
{
ALOGI("halSerialOpen:%s,%d\n",filename,flags);
return 0;
}
int halSerialSetOpt( int fd, int nSpeed, int nBits, char nEvent, int nStop)
{
ALOGI("halSerialSetOpt:%d,%c\n",fd,nEvent);
return 0;
}
char* halSerialRead( int fd, int len )
{
ALOGI("halSerialRead:%d,%c\n",fd,len);
return 0;
}
int halSerialWrite( int fd, char* txData )
{
ALOGI("halSerialWrite:%d,%s\n",fd,txData);
return 0;
}
void halSerialClose( int fd )
{
ALOGI("halSerialClose:%d\n",fd);
} struct myserial_device_t myserial_device = {
.common = {
.tag = HARDWARE_DEVICE_TAG,
},
.serial_open = halSerialOpen,
.serial_setOpt = halSerialSetOpt,
.serial_read = halSerialRead,
.serial_write = halSerialWrite,
.serial_close = halSerialClose,
}; int serial_open(const struct hw_module_t* module, const char* id,
struct hw_device_t** device) {
*device = &myserial_device;
return 0;
} static struct hw_module_methods_t myserial_module_methods = {
.open = serial_open,
}; struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.name = "myserial Device HAL",
.id = "myserial", //必填
.methods = &myserial_module_methods, //必填
};

myserial.h

#ifndef _HARDWARE_MYSERIAL_H
#define _HARDWARE_MYSERIAL_H #include <hardware/hardware.h> __BEGIN_DECLS struct myserial_device_t {
struct hw_device_t common;
int (*serial_open)( char* filename, int flags ) ;
int (*serial_setOpt)( int fd, int nSpeed, int nBits, char nEvent, int nStop) ;
char* (*serial_read)( int fd, int len );
int (*serial_write)( int fd, char* txData ) ;
void (*serial_close)( int fd );
} ; __END_DECLS #endif // _HARDWARE_MYSERIAL_H
/* filename com_android_server_MySerialService.cpp */
#define LOG_TAG "MySerialService" #include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h" #include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/myserial.h> #include <stdio.h> namespace android
{ struct hw_module_t *module;
struct hw_device_t* device;
struct myserial_device_t* serial_dev; #if 1
static jstring charTojstring(JNIEnv* env, const char* pat) {
//定义java String类 strClass
jclass strClass = (env)->FindClass("Ljava/lang/String;");
//获取String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
jmethodID ctorID = (env)->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
//建立byte数组
jbyteArray bytes = (env)->NewByteArray(strlen(pat));
//将char* 转换为byte数组
(env)->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*) pat);
// 设置String, 保存语言类型,用于byte数组转换至String时的参数
jstring encoding = (env)->NewStringUTF("GB2312");
//将byte数组转换为java String,并输出
return (jstring) (env)->NewObject(strClass, ctorID, bytes, encoding);
} static char* jstringToChar(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("GB2312");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
rtn = (char*) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
#endif jint jniSerialOpen( JNIEnv *env, jobject clazz, jstring filename, jint flags )
{
jint err;
char *arrChars = jstringToChar(env, filename ); err = hw_get_module("myserial",(hw_module_t const**)&module);
if (err==0)
{
err = module->methods->open( module, NULL, &device );
if (err==0)
{
serial_dev = (struct myserial_device_t*) device; ALOGI("jniSerialOpen:%s,%d\n",arrChars,flags); return serial_dev->serial_open(arrChars,flags);
} else {
ALOGI("open:error\n");
}
} else {
ALOGI("hw_get_module:error\n");
}
return -1; }
jint jniSerialSetOpt( JNIEnv *env, jobject clazz, jint fd, jint nSpeed, jint nBits, char nEvent, jint nStop)
{
ALOGI("jniSerialSetOpt:%d,%c\n",fd,nEvent);
return serial_dev->serial_setOpt(fd,nSpeed,nBits,nEvent,nStop);
}
jstring jniSerialRead( JNIEnv *env, jobject clazz, jint fd, jint len )
{
char* arrTx= serial_dev->serial_read(fd,len);
ALOGI("jniSerialRead:%d,%c\n",fd,len); return charTojstring(env, arrTx);
}
jint jniSerialWrite( JNIEnv *env, jobject clazz, jint fd, jstring txData )
{
char *arrChars = jstringToChar(env, txData );
ALOGI("jniSerialWrite:%d,%s\n",fd,arrChars); return serial_dev->serial_write(fd,arrChars);
}
void jniSerialClose( JNIEnv *env, jobject clazz, jint fd )
{
ALOGI("jniSerialClose:%d\n",fd);
serial_dev->serial_close(fd);;
} static JNINativeMethod method_table[] = {
{ "nativeSerialOpen", "(Ljava/lang/String;I)I", (void*)jniSerialOpen },
{ "nativeSerialSetOpt", "(IIICI)I", (void*)jniSerialSetOpt },
{ "nativeSerialRead", "(II)Ljava/lang/String;", (void*)jniSerialRead },
{ "nativeSerialWrite", "(ILjava/lang/String;)I", (void*)jniSerialWrite },
{ "nativeSerialClose", "(I)V", (void*)jniSerialClose }, }; int register_android_server_MySerialService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/MySerialService",
method_table, NELEM(method_table));
} };

首先 实现一个 hw_module_t结构体,实现一个methods和id,methods中实现open函数,open函数根据传入的名字返回设备自定义的结构体。实现一个设备自定义的结构体,并在设备自定义的结构体中加入需要的函数指针,并在.c文件中实现这些函数。

编译:

mmm frameworks/base/services/
mmm  hardware/libhardware/modules/myserial/
make snod

Android驱动学习-app调用内核驱动过程(驱动框架回顾)的更多相关文章

  1. linux 驱动学习笔记01--Linux 内核的编译

    由于用的学习材料是<linux设备驱动开发详解(第二版)>,所以linux驱动学习笔记大部分文字描述来自于这本书,学习笔记系列用于自己学习理解的一种查阅和复习方式. #make confi ...

  2. Linux内核驱动学习(四)Platform设备驱动模型

    Linux platform设备驱动模型 文章目录 Linux platform设备驱动模型 前言 框架 设备与驱动的分离 设备(device) 驱动(driver) 匹配(match) 参考 前言 ...

  3. 3.移植驱动到3.4内核-移植DM9000C驱动

    在上章-使内核支持烧写yaffs2,裁剪内核并制作补丁了 本章,便开始移植以前2.6内核的驱动到3.4新内核 1.介绍 首先内核更新,有可能会重新定义新的宏,去除以前的宏,以前更改函数名等 所以移植驱 ...

  4. Linux摄像头驱动学习之:(一)V4L2_框架分析

    这段时间开始搞安卓camera底层驱动了,把以前的的Linux视频驱动回顾一下,本篇主要概述一下vfl2(video for linux 2). 一. V4L2框架: video for linux ...

  5. Linux 网卡驱动学习(二)(网络驱动接口小结)

    [摘要]前文我们分析了一个虚拟硬件的网络驱动例子,从中我们看到了网络设备的一些接口,其实网络设备驱动和块设备驱动的功能比较类似,都是发送和接收数据包(数据请求).当然它们实际是有很多不同的. 1.引言 ...

  6. Android新技术学习——阿里巴巴免Root无侵入AOP框架Dexposed

    阿里巴巴无线事业部近期开源的Android平台下的无侵入运行期AOP框架Dexposed,该框架基于AOP思想,支持经典的AOP使用场景.可应用于日志记录,性能统计,安全控制.事务处理.异常处理等方面 ...

  7. Android驱动学习-APP操作新硬件的两种方法(支持添加的驱动)

    在给Android添加新的驱动后,app要如何使用呢? 正常的使用一个设备,需要getService.但是像LED等我们自己添加的硬件驱动,Android源代码根本没有我们自己添加的服务. 第一种: ...

  8. linux 驱动学习笔记03--Linux 内核的引导

    如图所示为 X86 PC 上从上电/复位到运行 Linux 用户空间初始进程的流程.在进入与 Linux相关代码之间,会经历如下阶段. ( 1 ) 当系统上电或复位时, CPU 会将 PC 指针赋值为 ...

  9. 嵌入式Linux驱动学习之路(十五)按键驱动-定时器防抖

    在之前的定时器驱动程序中,我们发现在连续按下按键的时候,正常情况下应该是一次按下对应一次松开.而程序有时候会显示是两次按下,一次松开.这个问题是因为在按下的时候,因为是机械按键,所以电压信号会产生一定 ...

随机推荐

  1. 发现了一个关于 gin 1.3.0 框架的 bug

    gin 1.3.0 框架 http 响应数据错乱问题排查 问题概述 客户端同时发起多个http请求,gin接受到请求后,其中一个接口响应内容为空,另外一个接口响应内容包含接口1,接口2的响应内容,导致 ...

  2. Fiddler 4 (利用Fiddler模拟恶劣网络环境)

    1.模拟弱网环境 打开Fiddler,Rules->Performance->勾选 Simulate Modem Speeds,勾选之后访问网站会发现网络慢了很多 解决办法去掉勾选的地方网 ...

  3. 整理一下《java并发编程实战》中的知识点

    分工.同步.互斥的历史由来 分工:单道.多道.分时 同步:线程通信(组织编排任务) 互斥:因(多线程访问共享资源)果(串行化共享资源的访问) 1切都是为了提高性能 2.可见性.原子性.有序性 可见性: ...

  4. MySQL索引(二):建索引的原则

    在了解了索引的基础知识及B+树索引的原理后(如需复习请点这里),这一节我们了解一下有哪些建索引的原则,来指导我们去建索引. 建索引的原则 1. 联合索引 我们可能听一些数据库方面的专业人士说过:&qu ...

  5. Cypress系列(101)- intercept() 命令详解

    如果想从头学起Cypress,可以看下面的系列文章哦 https://www.cnblogs.com/poloyy/category/1768839.html 作用 使用该命令在网络层管理 HTTP ...

  6. 老猿学5G:融合计费场景的离线计费会话的Nchf_OfflineOnlyCharging_Release释放操作

    ☞ ░ 前往老猿Python博文目录 ░ 一.Nchf_OfflineOnlyCharging_Release消息交互流程 Nchf_OfflineOnlyCharging_Release是CHF提供 ...

  7. PyQt(Python+Qt)学习随笔:QAbstractItemView的verticalScrollMode和horizontalScrollMode属性

    老猿Python博文目录 老猿Python博客地址 一.概述 verticalScrollMode和horizontalScrollMode属性用于控制视图如何在垂直方向和水平方向滚动内容.滚动可以按 ...

  8. pytorch实战(一)hw1——李宏毅老师作业1

    任务描述:利用前9小时数据,预测第10小时的pm2.5的数值,回归任务 kaggle地址:https://www.kaggle.com/c/ml2020spring-hw1 训练集为: 12个月*20 ...

  9. setTimeout和setInterval的区别,包含内存方面的分析?

    setTimeout表示间隔一段时间之后执行一次调用,而setInterval则是每间隔一段时间循环调用,直至clearInterval结束. 内存方面,setTimeout只需要进入一次队列,不会造 ...

  10. 数位DP复习笔记

    前言 复习笔记第五篇.(由于某些原因(见下),放到了第六篇后面更新)CSP-S RP++. luogu 的难度评级完全不对,所以换了顺序,换了别的题目.有点乱,见谅.要骂就骂洛谷吧,原因在T2处 由于 ...