当前位置: 首页 > news >正文

[AOSP] [JNI] [Android] AOSP中使用JNI

一. 简要

在Android中,JNI主要用于实现一些性能较高的功能,如图像处理、音频处理、视频处理等。同时,JNI也可以用于实现一些特殊的功能,如与硬件交互、与系统服务交互等。

二. Java层

在某个对象中添加如下代码,例如我在/frameworks/base/services/core/java/com/android/server/keepalive/KeepAliveManagerService.java中去使用native方法

public class KeepAliveManagerService extends IKeepAliveManager.Stub {private static final String TAG = "KeepAliveManagerService leilei";private final Context mContext;private final ActivityManagerService mActivityManagerService;private WindowManagerService mWindowManagerService;private boolean mSystemReady = false;**public static native int resumeNative(int v);****public static native int pauseNative(int v);****public static native String stopNative(String v);**// 应用保活方法,简单的调用了三个native方法:resumeNative,pauseNative,stopNative@Overridepublic boolean keepAliveApplicationByPackage(String packageName) throws RemoteException {Log.d(TAG, "keepAliveApplicationByPackage.packageName:"+packageName);if (TextUtils.isEmpty(packageName) || mActivityManagerService == null|| mContext == null || !mSystemReady){return false;}int s = resumeNative(1);int s1 = pauseNative(2);String s2 = stopNative("leilei");Log.d(TAG, "leilei keepAliveApplicationByPackage: onResumeNative:"+s);Log.d(TAG, "leilei keepAliveApplicationByPackage: onPauseNative:"+s1);Log.d(TAG, "leilei keepAliveApplicationByPackage: stopNative:"+s2);int curCallingUid = Binder.getCallingUid();return keepAliveApplicationByPackage(packageName,curCallingUid);}
}

至此java层的代码就写好了

三. C/C++层

  • JNI文件创建

    因为我写jni方法是需要在我的service对象里使用,所以我frameworks/base/services/core/jni/目录下创建为需要的文件,如下代码所示

    #include <string>
    #include <dlfcn.h>
    #include <pthread.h>
    #include <chrono>
    #include <thread>
    #include <jni.h>
    #include <nativehelper/JNIHelp.h>
    #include <android/binder_manager.h>
    #include <android/binder_stability.h>
    #include <android/hidl/manager/1.2/IServiceManager.h>
    #include <binder/IServiceManager.h>
    #include <hidl/HidlTransportSupport.h>
    #include <incremental_service.h>
    #include <memtrackproxy/MemtrackProxy.h>
    #include <schedulerservice/SchedulingPolicyService.h>
    #include <sensorservicehidl/SensorManager.h>
    #include <stats/StatsAidl.h>
    #include <stats/StatsHal.h>
    #include <bionic/malloc.h>
    #include <bionic/reserved_signals.h>
    #include <android-base/properties.h>
    #include <utils/Log.h>
    #include <utils/misc.h>
    #include <utils/AndroidThreads.h>
    #ifdef LOG_TAG
    #undef LOG_TAG
    #define LOG_TAG "leilei"
    #endif// jni静态注册
    /*extern "C" jstring
    Java_com_android_server_keepalive_KeepAliveManagerService_onResumeNative(JNIEnv *env, jclass thiz, jlong value) {// 进行本地处理,生成返回值std::string hello = "Hello from C++";jstring result = env->NewStringUTF(hello.c_str());return result;
    }*/// jni动态注册
    namespace android {
    static jint pauseNative(JNIEnv* env, jobject thiz, jint value){ALOGD("The leilei message is onPauseNative %d:",value);return value;
    }static jint resumeNative(JNIEnv *env, jobject thiz, jint value){ALOGD("The leilei message is onResumeNative %d:",value);return value;
    }static jstring stopNative(JNIEnv *env, jobject thiz,jstring value){const char* ptr = env->GetStringUTFChars(value, NULL);ALOGD("The leilei message is stopNative %s:",ptr);return value;
    }// 对应native的方法注册表
    static const JNINativeMethod gKeepAliveManagerMethods[] = {/* name, signature, funcPtr */{"pauseNative","(I)I",(void *)pauseNative},{"resumeNative","(I)I",(void *)resumeNative},{"stopNative","(Ljava/lang/String;)Ljava/lang/String;",(void*) stopNative},
    };// 自己实现一个跟jniRegisterNativeMethods一样的功能
    int registerNativeMethods(JNIEnv *env,std::string name,const JNINativeMethod *methods) {// 反射拿到java对象jclass klass = env->FindClass(name.c_str());if (klass == NULL) {return -1;}// 第一个参数:反射拿到的对象// 第二个参数:类中的native方法--注册表// 第三个参数:native方法对象的个数return env->RegisterNatives(klass, methods,3);
    }// onload.cpp中调用了JNI_OnLoad,然后调用了register_android_server_KeepAliveManager进行注册
    // jniRegisterNativeMethods对RegisterNatives封装了,所以可以很方便的使用,我们手动来实现一下
    // JNI_OnLoad是jni.h中的对象,只有调用JNI_OnLoad和RegisterNatives才是动态注册
    int register_android_server_KeepAliveManager(JNIEnv* env) {// return jniRegisterNativeMethods(env, "com/android/server/keepalive/KeepAliveManagerService",gKeepAliveManagerMethods, NELEM(gKeepAliveManagerMethods));return registerNativeMethods(env,"com/android/server/keepalive/KeepAliveManagerService",gKeepAliveManagerMethods);
    }
    };
    

    文件名称必须规范:com.android.server.keepalive.KeepAliveManagerService.cpp,由包名+类名组成,然后实现对应上层的native方法即可,方法名称最好相同(也可以不同,只要在注册函数的第一个参数中对应起来就行),例如下方法,三个参数中,前两个参数必须有,而且不能变—>JNIEnv和jobject:

    static jint pauseNative(JNIEnv* env, jobject thiz, jint value){...
    }
    

    然后就是开始动态注册jni方法,如下代码所示,由于aosp已经封装好了jniRegisterNativeMethods方法可以直接使用来注册jni方法,但是为了更深刻的理解,我们手动来实现registerNativeMethods

    // native方法注册表
    static const JNINativeMethod gKeepAliveManagerMethods[] = {/* name, signature, funcPtr */{"pauseNative","(I)I",(void *)pauseNative},{"resumeNative","(I)I",(void *)resumeNative},{"stopNative","(Ljava/lang/String;)Ljava/lang/String;",(void*) stopNative},
    };// 自己实现一个跟register_android_server_KeepAliveManager一样的功能
    int registerNativeMethods(JNIEnv *env,std::string name,const JNINativeMethod *methods) {jclass klass = env->FindClass(name.c_str());if (klass == NULL) {return -1;}// 第一个参数:反射拿到的对象// 第二个参数:类中的native方法--注册表// 第三个参数:native方法对象的个数return env->RegisterNatives(klass, methods,3);
    }int register_android_server_KeepAliveManager(JNIEnv* env) {// return jniRegisterNativeMethods(env, "com/android/server/keepalive/KeepAliveManagerService",gKeepAliveManagerMethods, NELEM(gKeepAliveManagerMethods));return registerNativeMethods(env,"com/android/server/keepalive/KeepAliveManagerService",gKeepAliveManagerMethods);
    }
    

    先实现一个native方法注册表,代表需要对应java层native方法,返回的对象是JNINativeMethod,该对象属于jni.h里的,结构如下

    typedef struct {const char* name;const char* signature;void*       fnPtr;
    } JNINativeMethod;
    

    第一个参数对应了java native方法的名称,第二个参数代表native方法里面的参数和返回值,第三个参数代表jni方法

    回到registerNativeMethods方法,主要就是通过env的FindClass反射获取Java对象,然后通过RegisterNatives(klass, methods,3);进行注册即可,第二个参数就是native方法gKeepAliveManagerMethods注册表

    **思考一下:**既然需要注册jni,那么调用register_android_server_KeepAliveManager函数的注册的入口又在哪里?下文分析

  • JNI文件引入和注册流程

    • android.bp引入编译

      需要让我们的jni文件参与编译,需要在frameworks/base/services/core/jni/Android.bp中添加该文件,如下所示

      cc_library_static {name: "libservices.core",defaults: ["libservices.core-libs"],cpp_std: "c++2a",cflags: ["-Wall","-Werror","-Wno-unused-parameter","-Wthread-safety","-DEGL_EGLEXT_PROTOTYPES","-DGL_GLEXT_PROTOTYPES",],srcs: ["BroadcastRadio/JavaRef.cpp","BroadcastRadio/NativeCallbackThread.cpp","BroadcastRadio/BroadcastRadioService.cpp","BroadcastRadio/Tuner.cpp","BroadcastRadio/TunerCallback.cpp","BroadcastRadio/convert.cpp","BroadcastRadio/regions.cpp","gnss/GnssConfiguration.cpp","gnss/GnssMeasurement.cpp","gnss/GnssMeasurementCallback.cpp","gnss/Utils.cpp","stats/SurfaceFlingerPuller.cpp",**"com.android.server.keepalive.KeepAliveManagerService.cpp",**include_dirs: ["frameworks/base/libs","frameworks/native/services","system/gatekeeper/include","system/memory/libmeminfo/include",],header_libs: ["bionic_libc_platform_headers",],
      }
      

      在此模块添加**“com.android.server.keepalive.KeepAliveManagerService.cpp”**,即可,模块名为libservices.core,会生成对应的so库

    • JNI注册入口声明

      在frameworks中,上文分析了如何调用jni注册native方法,但是调用注册的入口在哪里?就是通过frameworks/base/services/core/jni/onload.cpp文件进行调用的,需要在此文件中声明我们的注册入口,如下代码所示

      namespace android {
      int register_android_server_BatteryStatsService(JNIEnv* env);
      int register_android_server_ConsumerIrService(JNIEnv *env);
      int register_android_server_InputManager(JNIEnv* env);
      **int register_android_server_KeepAliveManager(JNIEnv* env);**
      int register_android_server_LightsService(JNIEnv* env);
      int register_android_server_PowerManagerService(JNIEnv* env);
      int register_android_server_PowerStatsService(JNIEnv* env);
      int register_android_server_HintManagerService(JNIEnv* env);
      int register_android_server_storage_AppFuse(JNIEnv* env);
      }
      extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
      {JNIEnv* env = NULL;jint result = -1;if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {ALOGE("GetEnv failed!");return result;}ALOG_ASSERT(env, "Could not retrieve the env!");register_android_server_broadcastradio_BroadcastRadioService(env);register_android_server_broadcastradio_Tuner(vm, env);register_android_server_PowerManagerService(env);register_android_server_PowerStatsService(env);register_android_server_HintManagerService(env);register_android_server_SerialService(env);register_android_server_InputManager(env);**register_android_server_KeepAliveManager(env);}**
      }
      

      只需要在namespace android中声明注册入口函数**register_android_server_KeepAliveManager**,此函数在我们创建的jni文件中会实现。

      然后在JNI_OnLoad函数中添加**register_android_server_KeepAliveManager(env);**,目的是为了方法可以被正确调用,以及传递了env对象(jni里的东西),再来看一遍我创建的jni文件,frameworks/base/services/core/jni/com.android.server.keepalive.KeepAliveManagerService.cpp

      ...
      int **register_android_server_KeepAliveManager**(JNIEnv* env) {// return jniRegisterNativeMethods(env, "com/android/server/keepalive/KeepAliveManagerService",gKeepAliveManagerMethods, NELEM(gKeepAliveManagerMethods));return registerNativeMethods(env,"com/android/server/keepalive/KeepAliveManagerService",gKeepAliveManagerMethods);
      }
      };
      

      **思考一下:**为什么需要在extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)方法中调用jni注册逻辑呢?这涉及到jni动态注册原理了,后面再分析,jni分为静态注册和动态注册

    • JNI注册表分析

      Andoird 中使用了一种不同传统Java JNI的方式来定义其native的函数。其中很重要区别是Andorid使用了一种Java 和 C 函数的映射表数组,并在其中描述了函数的参数和返回值。这个数组的类型是JNINativeMethod——该结构体位于jni.h中,结构如下

      typedef struct {const char* name;const char* signature;void*       fnPtr;
      } JNINativeMethod;
      

      三个参数代表着:native方法名称,签名—用字符串是描述了Java中函数的参数和返回值,jni函数对象-指向了java的native方法

      具体用法如下

      static const JNINativeMethod gKeepAliveManagerMethods[] = {/* name, signature, funcPtr */{"pauseNative","(I)I",(void *)pauseNative},{"resumeNative","(I)I",(void *)resumeNative},{"stopNative","(Ljava/lang/String;)Ljava/lang/String;",(void*) stopNative},
      };
      

      第三个参数前面必须带有(void *),这里主要分析第二个参数,()代表native方法的参数,()外面部分代表着返回值,I代表着java的int,jni的jint,具体如下

      字符c/c++类型Java类型
      Vvoidvoid
      Zjbooleanboolean
      Ijintint
      Jjlonglong
      Djdoubledouble
      Fjfloatfloat
      Bjbytebyte
      Cjcharchar
      Sjshortshort

      以上都是基本数据类,如果是数组,则用[代表,如整型数值 [I来表示,具体如下

      名称c/c++类型Java类型
      [IjintArrayint[]
      [FjfloatArrayfloat[]
      [BjbyteArraybyte[]
      [CjcharArraychar[]
      [SjshortArrayshort[]
      [DjdoubleArraydouble[]
      [JjlongArraylong[]
      [ZjbooleanArrayboolean[]

      那如果native参数中是对象呢,需要用如下方法表示—参数解释:

      // 参数解释
      "()" 中的字符表示参数,小括号后面的则代表返回值。
      "()V" 就表示native void Fun();
      "(II)V" 表示native void Func(int a, int b);参数是俩个整型。
      "(Ljava/lang/String;)Ljava/lang/String;" 就表示native Sting Func(String value);
      

      所以如果要用对象作为参数或者返回值,需要在前面加个”L”,中间是用”/" 隔开,后面跟包名和类名,以及分号即可。如果是对象数组,则在前面加个[即可

相关文章:

[AOSP] [JNI] [Android] AOSP中使用JNI

一. 简要 &#x1f34e; JNI是Java Native Interface的缩写&#xff0c;它提供了若干的API实现了Java和其他语言的通信&#xff08;主要是C&C&#xff09;。从Java1.1开始&#xff0c;JNI标准成为java平台的一部分&#xff0c;它允许Java代码和其他语言写的代码进行交互。J…...

GEE案例——如何使用长时序影像实现多波段图像加载(不同层土壤湿度)

简介: 在GEE中实现时序图像的加载主要的目的是查看影像波段或者指数的变化,这里我们使用的主要是加载常规的4个波段,然后添加一个复合波段,复合波段主要的是求4个波段的平均值,然后再次加入到原有的4个波段的时序图中。这里面主要的技术难点一个是图表的设定,另外一个就…...

Cloudflare进阶技巧:缓存利用最大化

1. 引言 cloudflare我想你应该知道是什么&#xff0c;一家真正意义上免费无限量的CDN&#xff0c;至今未曾有哥们喷它的。当然&#xff0c;在国内的速度确实比较一般&#xff0c;不过这也不能怪它。 CDN最大的特色&#xff0c;我想就是它的缓存功能&#xff0c;达到防攻击&am…...

想要精通算法和SQL的成长之路 - 二叉树的判断问题(子树判断 | 对称性 | 一致性判断)

想要精通算法和SQL的成长之路 - 二叉树的判断问题 前言一. 相同的树二. 对称二叉树三. 判断子树 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 相同的树 原题链接 这题目典型的递归题&#xff1a; 如果两个节点都是null&#xff0c;我们返回true。如果两个节点一个nul…...

(零)如何做机器视觉项目

文章目录 1 项目的前期准备1.1 从5个方面初步分析客户需求1.2 方案评估与验证1.3 签订合同 2 项目规划2.1 定义客户端的详细需求2.2 制定项目管理计划2.3 方案评审 3 详细设计3.1 硬件设备的选择与环境搭建3.2 软件开发平台与开发工具的选择3.3 机器视觉系统的整体框架与开发流…...

【Leetcode】滑动窗口合集

这里写目录标题 209.长度最小的子数组题目思路代码 3. 无重复字符的最长子串&#xff08;medium&#xff09;题目思路 11. 最大连续 1 的个数 III题目思路 1658. 将 x 减到 0 的最⼩操作数题目思路代码 904. 水果成篮题目思路代码 438.找到字符串中所有字母的异位词题目思路代码…...

【C++】STL详解(九)—— set、map、multiset、multimap的介绍及使用

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C学习 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C】STL…...

计组—— I/O系统

&#x1f4d5;&#xff1a;参考王道课件 目录 一、I/O系统的基本概念 1.什么是“I/O”&#xff1f; ​编辑2.主机如何和I/O设备进行交互&#xff1f; 3.I/O控制方式 &#xff08;1&#xff09;程序查询方式 &#xff08;2&#xff09;程序中断方式 &#xff08;3&#x…...

基于vc6+sdk51开发简易文字识别转语音的程序

系统&#xff1a;window7 软件&#xff1a;vc6.0 目的&#xff1a;简易文字转语音真人发声 利用2023国庆小长假&#xff0c;研究如何将文言转语音&#xff0c;之前在网上查询相关知识&#xff0c;大致了解微信语音转换&#xff0c;翻译官之类软件的原理&#xff0c;但要加入神…...

DevOps:自动化部署和持续集成/持续交付(CI/CD)

DevOps&#xff1a;自动化部署和持续集成/持续交付&#xff08;CI/CD&#xff09; 在现代软件开发领域&#xff0c;DevOps&#xff08;Development和Operations的组合&#xff09;已经成为一个不可或缺的概念。它代表了一种将软件开发和运维&#xff08;Operations&#xff09…...

专业图标制作软件 Image2icon 最新中文 for mac

Image2Icon是一款用于Mac操作系统的图标转换工具。它允许用户将常见的图像文件&#xff08;如PNG、JPEG、GIF等&#xff09;转换为图标文件&#xff08;.ico格式&#xff09;&#xff0c;以便在Mac上用作应用程序、文件夹或驱动器的自定义图标。 以下是Image2Icon的一些主要功…...

数据结构:顺序表

SeqList.h #pragma once #include<stdio.h> #include<assert.h> #include<stdlib.h>typedef int SLDataType; //#define NULL 0typedef struct SeqList {SLDataType* a;int size;//顺序表中存储的有效元素的个数int capacity;//空间的大小 }SL;void SLInit(…...

僵尸进程的产生与处理

僵尸进程&#xff08;Zombie Process&#xff09;是指在操作系统中已经完成了执行&#xff0c;但其父进程尚未调用wait()或waitpid()来获取其终止状态的子进程。当一个进程结束时&#xff0c;操作系统会保留该进程的一些基本信息&#xff0c;包括进程ID&#xff08;PID&#xf…...

TouchEffects - Android View点击特效

官网 GitHub - likaiyuan559/TouchEffects: Android View点击特效TouchEffects,几行代码为所有控件添加点击效果 项目简介 Android View点击特效TouchEffects,几行代码为所有控件添加点击效果 TouchEffects能够帮助你更快速方便的增加点击时候的效果&#xff0c;TouchEffect…...

从ContinuousEventTimeTrigger/ContinuousProcessingTimeTrigger代码看如何实现一个自定义的触发器

背景 当我们想要实现提前触发计算的触发器时&#xff0c;我们可以使用ContinuousEventTimeTrigger/ContinuousProcessingTimeTrigger作为触发器达到比如几分钟触发一次计算并发送计算结果的类&#xff0c;我们本文就从代码角度解析下实现自定义触发器的一些注意事项 Continuo…...

Linux 5种网络模型

[参考]&#xff1a;《黑马程序员Redis》https://www.bilibili.com/video/BV1cr4y1671t/?p166&share_sourcecopy_web&vd_source9e65300ccca322aeb367bb1eb677b0fc [参考]&#xff1a;《操作系统》 [参考]&#xff1a;《UNIX网络编程》 为了避免用户应用导致冲突甚至内…...

10.1 调试事件读取寄存器

当读者需要获取到特定进程内的寄存器信息时&#xff0c;则需要在上述代码中进行完善&#xff0c;首先需要编写CREATE_PROCESS_DEBUG_EVENT事件&#xff0c;程序被首次加载进入内存时会被触发此事件&#xff0c;在该事件内首先我们通过lpStartAddress属性获取到当前程序的入口地…...

Linux系统常用指令篇---(一)

Linux系统常用指令篇—(一) 1.cd指令 Linux系统中&#xff0c;磁盘上的文件和目录被组成一棵目录树&#xff0c;每个节点都是目录或文件。 语法:cd 目录名 功能&#xff1a;改变工作目录。将当前工作目录改变到指定的目录下。 (简单理解为进入指定目录下) 举例: cd .. : 返…...

【初识Linux】:常见指令(1)

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux的基础知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数…...

STM32复习笔记(四):看门狗

目录 &#xff08;一&#xff09;简介 &#xff08;二&#xff09;IWDG IWDG的CUBEMX工程配置 IWDG相关函数&#xff08;非常少&#xff0c;所以直接贴上来&#xff09;&#xff1a; &#xff08;三&#xff09;WWDG &#xff08;一&#xff09;简介 看门狗分为独立看门…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

毫米波雷达基础理论(3D+4D)

3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文&#xff1a; 一文入门汽车毫米波雷达基本原理 &#xff1a;https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)

目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 &#xff08;1&#xff09;输入单引号 &#xff08;2&#xff09;万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...

uniapp 实现腾讯云IM群文件上传下载功能

UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中&#xff0c;群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS&#xff0c;在uniapp中实现&#xff1a; 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)

引言 在嵌入式系统中&#xff0c;用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例&#xff0c;介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单&#xff0c;执行相应操作&#xff0c;并提供平滑的滚动动画效果。 本文设计了一个…...

《Offer来了:Java面试核心知识点精讲》大纲

文章目录 一、《Offer来了:Java面试核心知识点精讲》的典型大纲框架Java基础并发编程JVM原理数据库与缓存分布式架构系统设计二、《Offer来了:Java面试核心知识点精讲(原理篇)》技术文章大纲核心主题:Java基础原理与面试高频考点Java虚拟机(JVM)原理Java并发编程原理Jav…...

解析“道作为序位生成器”的核心原理

解析“道作为序位生成器”的核心原理 以下完整展开道函数的零点调控机制&#xff0c;重点解析"道作为序位生成器"的核心原理与实现框架&#xff1a; 一、道函数的零点调控机制 1. 道作为序位生成器 道在认知坐标系$(x_{\text{物}}, y_{\text{意}}, z_{\text{文}}…...