• android期末复习


    403-复习提纲

    嵌入式安卓底层开发

    第一章概述

    • 安卓软件的架构分层

      • 英文中文
        Application应用层
        Application Framework应用框架层
        Android Runtime&Libraries运行时库和本地库
        Linux Kernel内核层
        Android HAL硬件抽象层
      • 应用层

        • 用户安装应用程序及系统自带应用程序层
        • 用来与用户进行交互
        • 如Home指Android手机的桌面
      • Application Framework应用框架层

        • 系统框架层,封装了大量应用程序所使用的类,达到组件重用的目的,它主要向上提供API
      • 运行时库和本地库层

        • Runtime使安卓的运行环境,该层有安卓虚拟机(DVM)的实现,DVM中运行着Java程序
      • 内核层

        • 基于Linux系统的,和linux的内核没什么很大区别
      • 硬件抽象层

        • 就是对Linux内核驱动程序的封装,向上提供接口,屏蔽低层的实现细节
        • 也就是说,把对硬件的支持分成了两层,一层放在用户空间(User Space),一层放在内核空间(Kernel Space),其中,硬件抽象层运行在用户空间,而Linux内核驱动程序运行在内核空间。
        • 硬件抽象层运行在用户空间
        • Linux内核驱动程序运行在内核空间
    • 子系统的介绍

      • 系统名介绍
        Android RIL子系统子系统即无线电接口系统用于管理用户的电话、短信、数据通信等相关功能,它是每个移动通信设备必备的系统。
        Android Input子系统Input子系统用来处理所有来自用户的输入数据,如触摸屏、声音控制物理按键等。
        Android GUI子系统GUI即图形用户接口,也就是图形界面,它用来负责显示系统图形化界面,使用户与系统及信息交互更加便利。 Android的GUI系统和其他各子系统关系密切相关,是 Android中最重要的子系统之一,如绘制一个2D图形、通过 OpenGL库处理3D游戏、通过 Surface Flinger来重叠几个图形界面。
        Android Audio子系统Android的音频处理子系统,主要用于音频方面的数据流传输和控制功能,也负责音频设备的管理。 Android的 Audio系统和多媒体处理紧密相连,如视频的音频处理和播放、电话通信及录音等。
        Android Media子系统Android的多媒体子系统,它是 Android系统中最庞大的子系统,与硬件编解码、 Open Core多媒体框架、 Android多媒体框架等相关,如音频播放器、视频播放器、 Camera摄像预览等。
        Android Connectivity子系统Android连接子系统是智能设备的重要组成部分,它除了一般网络连接,如以太网、Wi-Fi外,还包含蓝牙连接、GPS定位连接、NFC等。
        Android Sensor子系统Android连接子系统是智能设备的重要组成部分,它除了一般网络连接,如以太网、Wi-Fi外,还包含蓝牙连接、GPS定位连接、NFC等。
    • Android 应用程序开发过程(2种形式的开发,开发过程)

      • Android SDK开发(基于Android SDK的Android应用层开发) 特点:最快捷
      • Android源码开发(基于Android源码的底层开发,可以自定义操作系统) 特点:最大程度上体现了开源的优势
      • 源码开发过程:
        • 搭建开发环境
        • 下载Android源码
        • 编译Android源码
        • 配置开发环境安装

    第二章 源码开发环境搭建

    • Android 4.0开发环境搭建及源码编译

      • 安装VMware Workstation pro

      • 安装ubuntu-12.04

      • gcc、g++降版本

        • sudo apt-get install gcc-4.4 g++-4.4 
          gcc降级:
            sudo rm -rf /usr/bin/gcc
            sudo ln -s /usr/bin/gcc-4.4 /usr/bin/gcc 
            gcc -v
           g++降级
            sudo rm -rf /usr/bin/g++ 
            sudo ln -s /usr/bin/g++-4.4 /usr/bin/g++ 
            g++ -v
          
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
      • 把源码aosp源码拷贝到目录下

      • 安装JDK1.6,修改 /etc/profile并且追加JDK的配置信息到文件的最后,通过 source /etc/profile使得更改生效

      • 安装编译依赖工具包

      • 解压源码、内核、sdk、eclipse压缩包

      • 进入内核目录运行脚本build_kernel.sh进行编译linux内核

      • 初始化编译环境 source build/envsetup.sh。

      • 选择编译选项 lunch之后,选择目标编译项,应该选择ARM版本。

      • 编译源码 make -j4,-j之后的数字是编译使用的线程数,一般最大可以是内核数的两倍。

      • 源码编译完成,在out/target/product/generic/目录里出现三个文件:system.img、ramdisk.img、userdata.img说明编译成功。

      • 创建模拟器

      • 运行run_emulator.sh脚本

    • 版本变量名由以下几个部分组成

      • eng:工程版本
      • user:最终用户版本
      • userdebug:调试版本
      • tests:测试版本
      • 版本作用:
        • 区分目标系统的所有的应用程序、库、测试程序等
        • 根据不同的版本,系统会有不同的设置。
    • 源码编译生成:

      • system.img
        • 文件系统,包含了android系统的应用程序,系统用到的各种库和资源,配置文件(etc目录),系统命令(bin,usr/bin,xbin)
      • ramdisk.img
        • 用户数据镜像,里面包含有程序安装信息
      • userdata.img
        • 内存磁盘映像,ramdisk.img是一个最小化的根文件系统,加载到内存中作为android的根文件系统
    • 搭建android sdk开发环境

      • 下载和安装eclipse,前面已经将其复制到目录下
      • 安装ADT插件
      • 下载配置Android SDK工具包,搭建Android SDK平台。
      • 在Android源码目录下通过android create avd -n avd -t 1或者直接通过Android SDK Manager创建模拟器,定制Android模拟器。
      • 运行模拟器,加载编译好的linux内核和android源码镜像文件,如果成功启动说明搭建完成,可以进行仿真。
    • 编译命令

      • make
        • 编译andorid文件系统
      • make ramdisk
        • 单独编译ramdisk.img
      • make snod
        • 打包system文件系统
      • mmm 模块目录(带有Android.mk文件)
      • mkall
        • 完全编译bootloader、kernel、android文件系统并且生成整体烧写镜像
      • mksnod
        • 快速system文件系统

    第三章 系统的启动

    • Android系统的启动过程

      • 开机上电后,启动BootLoader初始化堆栈和内存空间,为加载内核提供条件。
      • BootLoader把Linux内核加载到内存,挂载根文件系统
      • 启动init进程,init进程完成解析init.rc等初始化脚本文件,启动linux本地服务,启动Android本地守护进程。
      • init进程解析init.rc启动zygote进程,zygote进程启动DVM,DVM中运行第一个java程序zygoteInit。zygote进程fork出System Server进程,在System Server进程启动和初始化时启动Android系统服务。
      • Android系统服务启动完毕后,System Server进程通知开启Android桌面应用程序。
      • 启动完成之后可以创建Android应用程序进程,每一个应用进程都是一个独立的DVM实例,其都是客户端通过Binder机制与System Server进程进行通信,然后System Server进程再通过socket与zygote进程通信,最后由zygote进程fork出来的。这些DVM实例通过共享zygote进程预加载的一部分资源来加快应用启动速度。
    • 简答版本

      • Android系统启动有4个阶段;
      • 硬件BOOT、加载Linux内核并挂载Rootfs
      • init进程启动及Native服务启动
      • System Server及Android服务启动
      • Home桌面启动
    • init进程启动

      • 解析init.rc及init.{hardware}.rc初始化脚本文件
        • 解释:init.rc只是语法文件,并不是程序,真正的入口时init.c
        • 解释:/system/core/rootdit/正常启动使用的
        • 解释:/bootab/recorecry/etc/刷机用到的
      • 监听keychord组合按键事件
        • 当几个组合按键被按下时,代表一个标准键盘输入
      • 监听属性服务
        • 属性服务好比window下的注册表,是系统定义的一系列键值对,这些属性影响系统的工作机制和功能
      • 监听并处理子进程死亡事件
        • 子进程的死亡由其父进程收尸,子进程的父进程提前结束就变成孤儿进程,由init接管
    • Android初始化语言四种类型的声明

      • Actions(行为)

        • 其实就是一个Commands命令序列,每个Actions都有一个trigger(触发器),决定了触发行为的条件。
      • Commands(命令)

        • 后面是一系列命令

        • 常用的Commands

        • 命令说明
          exec []* 创建和执行一个程序( )。在程序完全执行前,init将会阻塞
          export在全局环境变量中设置环境变量为
          ifup启动网络接口
          hostname设置主机名
          chmod 更改文件访问权限
          chown 更改文件的所有者和组
          class_start启动所有指定服务类别下的未运行的服务
          class_stop停止指定服务类下的所有已运行的服务
          insmod 加载 中的模块
          mkdir [mode] [owner] [group] 创建一个目录 ,可以指定mode、owner、group。没有指定,默认的权限为755,属于root用户和root组
          mount [ ]* 在目录挂载指定的设备
          setprop 设置系统属性name为value
          start/stop 启动/停止指定服务
      • services(服务)

        • 就是一个程序,一个本地守护进程,它被init进程启动,并在退出时可选择让其重启
      • options(选项)

        • 是一个服务的属性,决定了service的运行机制、状态和功能,他们影响service在何时,并以何种方式运行

        • options说明
          critical对于设备关键的服务,如果其4min内退出大于四次,系统将会重启进入recovery模式
          disabled这个服务不会与同一个触发器下的服务自动启动
          setenv 在进程启动时将环境变量<name<设置为
          socket [ [ ] ]创建套接字,并传递它的文件描述符给已经启动的进程。
    • Android中的本地守护进程

      • Service 对应程序
        ueventd /sbin/ueventd
        console /system/bin/sh
        adbd /sbin/adbd
        servicemanager /system/bin/servicemanager
        vold /system/bin/vold
        netd /system/bin/netd
        debugged /system/bin/debugged
        ril-daemon /system/bin/rild
        zygote /system/bin/app_process -Xzygote
        /system/bin --zygote–start-system-server
        media /system/bin/mediaserver
      • uevented进程
        • 实现设备的管理
        • 冷插拔:以预先定义的设备信息为基础,当init进程被启动时,统一创建设备节点文件
        • 热插拔:系统运行中,有设备插入USB端口时,init进程就会接收到这一事件,动态的为新插入设备创建设备节点文件
      • adbd进程
        • 是adb调试桥的服务器端
      • servicemanager进程
        • 服务管理器,整个android系统里服务的大总管
      • vold进程
        • 负责系统的cdrom、usb大容量存储设备、mmc卡等拓展存储器的插拔等
      • ril-daemon进程
      • surfaceflinger进程
        • 安卓系统的显示核心处理单元
    • Home桌面的启动

      • 显示Intent启动,在Intent对象中指定Intent对象的接收者,是点对点的启动方式
      • 隐式Intent启动,类似于广播机制,在发送的Intent中通过Action和Category来匹配接收者

    第四章 编译系统与系统定制

    • Android.mk必须完成的操作

      • 指定当前模块的目录
      • 清除所有的local_xx变量
      • 指定源码文件
      • 指定编译细节
      • 制定目标模块名
      • 指定目标模块类型
    • 安卓主要预定义编译变量

      • 编译变量功能
        BUILD_SHARED_LIBRARY将模块编译成共享库
        BUILD_STATIC_LIBRARY将模块编译成静态库
        BUILD_EXECUTABLE将模块编译成可执行文件
        BUILD_JAVA_LIBRARY将模块编译成Java类库
        BUILD_PACKAGE将模块编译成Android应用程序包
    • 主要编译变量

      • 编译变量功能
        local_path编译路径
        local_module编译模块名
        local_src_files编译源码列表
        local_shared_libraries使用的C/C++共享库列表
        local_static_libraries使用的C/C++静态库列表
        local_static_java_libraries使用的Java库列表
        local_cflags编译器参数
        local_c_includesC/C++头文件路径
        local_package_nameAndroid应用程序名
        local_certificate签名认证
        local_java_librariesJava库列表
    • 编译可执行程序

      • LOCAL_PATH:=$(my-dir)//获取当前目录
        # Test Exe
        include $(CLEAR_VARS)//清除许多local_XXX变量
        LOCAL_MODULE_TAGS:=eng//编译版本
        LOCAL_SRC_FILES:=\ //源文件
        main.c
        LOCAL_MODULE=test_exe //生成可执行程序
        LOCAL_C_INCLUDES:=  //包含的头文件
        LOCAL_STATIC_LIBARIES:= //需要的静态库
        LOCAL_SHARED_LIBARIES:=libc //需要的动态库
        include $(BUILD_EXECUTABLE)//编译后生成文件路径,指定编译方式,编译成可执行程序
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
    • 编译静态库的模板

      • # Test Static Lib
        LOCAL_PATH:=$(call my-dir)//获取当前目录
        include$(CLEAR_VARS):=/
        		helloworld.c
        LOCAL_MODULE:=libtest_static
        #LOCAL_C_INCLUDES :=//包含的头文件
        #LOCAL_STATIC_LIBRARIES :=//需要的静态库
        #LOCAL_SHARED_LIBRARIES :=//需要的动态库
        include $(BUILD_STATIC_LIBRARY)
        一般的和上面相似,BUILD_STATIC_LIBRARY表示编译一个静态库。
        编译生成的是以“lib<module_name>.so”的文件,这个就是共享库了,这是系统会自动加上“头”和“尾”。 libtest_static.a
        
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
    • 编译动态库的模板

      • #Test Shared Lib
        LOCAL_PATH:=$(call my-dir)//获取当前目录
        include $(CLEAR_VARS)//清除许多local_XX变量
        LOCAL_SRC_FILES:=/
        		helloworld.c
        LOCAL_MODULE:=libtest_shared
        TARGET_PRELINK_MODULES:=false
        #LOCAL_C_INCLUDES :=//包含的头文件
        #LOCAL_STATIC_LIBRARIES :=//需要的静态库
        #LOCAL_SHARED_LIBRARIES :=//需要的动态库
        include $(BUILD_SHARED_LIBRARY)
        LOCAL_PRELINK_MODULE := false 
        Prelink利用事先链接代替运行时链接的方法来加速共享库的加载,它不仅可以加快起动速度,还可以减少部分内存开销。 
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
    • 编译一个简单的APK

      • LOCAL_PATH:=$(call my-dir)
        include $(CLEAR_VARS)
        LOCAL_SRC_FILES:=$(call all-subdir-java-files)//指定编译源码列表
        LOCAL_PACKAGE_NAME:=LocalPackage//指定android应用程序名
        include $(BUILD_PACKAGE)
        BUILD_PACKAGE表示编译一个apk程序,而call all-subdir-java-files表示自动查找当前目录下的所有java文件进行编译。
        在源码中编译和SDK环境下编译有所不同,涉及的目录主要有两个。
        Out/target/common/obj/apps—通用java字节码目录
        out/target/product/<TARGET_PRODUCT>/apps---android应用包目录
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
    • 编译Helloworld应用程序的步骤

      • 打开eclipse开发环境,新建一个Android应用程序:HelloWorld

        • cd /home/fyj/aosp/eclipse
          ./eclipse
          
          • 1
          • 2
      • 将新创建的HelloWorld工程复制到源码目录下的packages/apps目录下

        • cp -rf HelloWorld /home/fyj/aosp/aosp/packages/apps
          
          • 1
      • 编译HelloWorld工程的Android.mk文件,仿照Android自带应用程序的Android.mk文件,例如 Camera工程中的Android.mk,因此将其复制到HelloWorld中

        • cp ../Camera/Android.mk ./
          
          • 1
        • Android.mk

          • LOCAL_PATH:= $(call my-dir)
            include $(CLEAR_VARS)
            LOCAL_MODULE_TAGS := optional
            LOCAL_SRC_FILES := $(call all-java-files-under, src)
            LOCAL_PACKAGE_NAME := HelloWorld
            #LOCAL_SDK_VERSION := current
            include $(BUILD_PACKAGE)
            
            • 1
            • 2
            • 3
            • 4
            • 5
            • 6
            • 7
          • •eng: 工程机,user:最终用户机 tests:测试机 optional:指该模块在所有版本下都编译

      • 编译Helloworld工程

        • 切换到Android源码目录下

        • cd /home/fyj/aosp/aosp
          
          • 1
        • 加载编译函数

        • source build/envsetup.sh
          
          • 1
        • 选择编译目标项

        • lunch 1
          
          • 1
        • 通过mmm命令编译HelloWorld工程

        • mmm packages/apps/HelloWorld/
          
          • 1
        • 编译生成模拟映像system.img

        • make snod
          
          • 1
    • 定制开机界面

      • 生成图片数组

        • 使用Image2Lcd,它可以将一个图片转换成指定位色得C语言数组类型,我们只需要将这个图 片数据数组写入,即可在屏幕上显示这个图片了
      • 设置logo.jpg输出文件属性

        • 打开Image2Lcd选择实训xx目录中的test.bmp文件,设置test.bmp输出文件得属性。
        • 由于Android模拟器得屏幕大小为宽320,高480,模拟器支持16位色,故设置为上述要求
      • 保存图片数据为c语言头文件test.h

      • 将test.h图片数据文件拷贝到Linux内核framebuffer驱动目录下

        • cp test.h /home/fyj/aosp/goldfish_2.6.29/drivers/video
          
          • 1
      • 修改framebuffer驱动文件goldfishfb.c

        • 首先引入test.h,#include “test.h”
      • 然后修改初始化工作,在prob函数返回之前,将图片数据写入到Framebuffer显存中。

        • memcpy(fb->fb.screen_base,gImage_test,320*480*2);
          return 0;
          
          • 1
          • 2
      • 重新编译Linux内核

        • cd /home/fyj/aosp/goldfish_2.6.29
          ./build_kenel.sh
          
          • 1
          • 2
      • 启动Android模拟器运行实训结果

    • 开机动画【蒙版图片】

      • 使用Photoshop 等图像处理软件制作一张背景为黑色,中间镂空的 PNG:格式的图片,命名为:android-logo-mask.png

      • 将android-logo-mask.png 复制到 frameworks/base/core/res/assets/images/目录下替换Android 默认的图片,为了防止源码不编译图片资源,将图片时间戳更新一下。

        • 运行命令把时间戳进行更新:

        • touch /home/fyj/aosp/aosp/frameworks/base/core/res/assets/images/android-logo-mask.png
          
          • 1
      • 重新编译Android的系统资源包framework-res.apk,以及运行make snod并且生成新的system.img

      • source build/envsetup.sh
        lunch 1
        mmm frameworks/base/core/res/
        make snod
        
        • 1
        • 2
        • 3
        • 4
      • 运行脚本查看

    • 开机动画【逐帧动画】

      • 在/data/local/或/system/media/目录创建 bootanimation.zip 文件

      • bootanimation.zip文件打包前的结构如表所示。

        • 文件说明
          desc.txt动画属性描述文件
          part0/第一阶段动画图片的目录
          part1/第二阶段动画图片的目录
        • 理解:

          320 480 5
          p 1 0 part0
          p 0 0 part1
          
          • 1
          • 2
          • 3
        • 图片属性320(图片宽)320(图片高)15(每秒显示帧数)
          第一阶段动画属性P(默认标志符)1(循环次数为1)0(进入该时间段的间隔时间)part0(该阶段图片存放目录)
          第二阶段动画属性P(默认标志符)0(无限循环)0(进入该时间段的间隔时间)part1(该阶段图片存放目录)
      • 如果bootanimation.zip放到/system/media/目录下,则重新编译生成system.img

        • source build/envsetup.sh
          lunch 1
          make snod
          
          • 1
          • 2
          • 3
        • 启动模拟器

    第五章:JNI机制

    • 使用JNI通常的场景

      • 对处理速度有要求
      • 硬件控制
      • 复用本地代码
    • JNIEnv是当前Java线程的执行环境,一个JVM对应一个JavaVM结构,而一个JVM中可能创建多个Java线程,每个线程对应一个JNIEnv结构,他们保存在线程本地存储中。因此,不同的线程的JNIEnv是不同,也不能相互共享调用。

    • JNI的数据类型和Java数据类型以及数据类型签名

      • JAVA类型JNI类型签名
        booleanjbooleanZ
        bytejbyteB
        charjcharC
        shortjshortS
        intjintI
        longjlongJ
        floatjfloatF
        doublejdoubleD
        引用类型jobjectL
        voidV
        数组类型jarray[
        二维数组类型[[
    • JNI方法签名相关

      • JAVA方法JNI方法签名
        boolean isLedOn();()Z或者(V)Z
        double Div(int x,int y);(II)D
        int GetNameBy(int ModubleID);(I)I
        int GetArrayElementNumber(long[] num);([J)I
        double Sub(double x,double y)(DD)D
        void setLedOn(int ledNo);(I)V
        String substr(String s,int idx,int count)(Ljava/lang/String;II)Ljava/lang/String
        char fun(int n,String s,int[] count)(I;Ljava/lang/String;[I)C
        boolean showMsg(android.View v, String msg);(Landroid/lang/String;Ljava/lang/String)Z
        int GetLedCount();()I或者(V)I
        String GetMoudleNameBy(int ModuleID);(I)Ljava/lang/String
        long GetArrayElement(long[] num , int index);([JI)J
        float Add(float x , float y);(FF)F
        boolean isSameName(String mName);(Ljava/lang/String)Z
        void function();()V或者(V)V
    • JNI引用类型

      • JNI引用类型Java引用类型
        jobjectjava.lang.Object类型
        jclassjava.lang.Class类型
        jstringjava.lang.String类型
        jarray数组类型
        jobjectArray对象数组类型
        jbooleanArray布尔数组类型
        jbyteArray字节数组类型
        jcharArray字符数组类型
        jshortArray短整型数组类型
        jintArray整形数组类型
        jlongArray长整型数组类型
        jfloatArray浮点数组类型
        jdoubleArray双精度浮点数组类型
        jthrowablejava.lang.Throwable类型
    • Java访问本地方法

      • 编写Java代码,在Java代码中加载本地代码库

      • 在Java中声明本地native方法

      • 调用本地native方法

      • public class HelloJNI{
        	static {
        		System.loadLibrary("hellojni");
        	}
        	private static native String getJNIHello();
        	public static void main(String args[]){
        		System.out.println(HelloJNI.getJNIHello());
        	}
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
    • JNI访问Java成员

      • jclass FindClass(const char *name);
        
        • 1
    • 在JNI中Java对象一般都是作为参数传递给本地方法的

      • class MyClass{
        	private int mNumber;
        	private static Sring mName;
        	public MyClass(){
        	
        	}
        	public void printNum(){
        		System.out.println("Number:"+mNumber);
        	}
        	public static void printNm(){
        		System.out.println("Number"+mNumber);
        	}
        }
        class PassJavaObj{
        	static{
        		System.loadLibrary("native_method");
        	}
        	private native static void passObj(String str);
        	public static void main(){
        		passObj("Hello World");
        	}
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
      • 本地代码

      • void Java_com_test_exam1_PassJavaObj_passObj
        (JNIEnv *env,jclassthiz,jobject str)
        {
        ...
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
      • env:当前Java代码的运行环境

        thiz:表示调用当前本地方法的对象

        str:就是我们传递过来的字符串

    • 获得Java属性ID和方法ID

      • jfieldIDjmethodID 用来标识java对象的属性和方法。需要获得ID才能对属性和方法进行操作。而由于java中的重载机制,所以需 要使用签名确定属性和方法。
      • jfieldID GetFieldID(jclass clazz, const char* name, const char* sig);	// 获取属性ID
        jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig);	// 获取静态属性ID
        jmethodID GetMethodID(jclass clazz, const char* name, const char* sig);	// 获取方法ID
        jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig);	// 获取静态方法ID
        
        • 1
        • 2
        • 3
        • 4
      • jclass clazz:要取得成员对应的类

        const char *name:代表要取得的方法名和属性名

        const char *sig:代表要取得的方法或属性的签名

      • 案例

        • class MyClass{
          	private int mNumber;
          	private static Sring mName="Michael";
          	public MyClass(){
          		mNumber=1000;
          	}
          	public void printNum(){
          		System.out.println("Number:"+mNumber);
          	}
          	public static void printNm(){
          		System.out.println("Number"+mNumber);
          	}
          }
          class NativeCallJava{
          	static{
          		System.loadLibrary("native_callback");
          	}
          	private native static void callNative(MyClass cls);
          	public static void main(){
          		callNative(new MyClass());
          	}
          }
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
        • void Java_com_test_exam2_NativeCallJava_callNative(JNIEnv * env,jobject obj)
          {
          	jclass myCls = env->GetObjectClass(obj);
          	jfieldID mNumFieldID=env->GetFieldID(myCls,"Number","I");
          	jfieldID mNameFieldID=env->GetStaticFieldID(myCls,"mName","java/lang/String");
          	jmethodID printNumMethodID =env->GetMethodID(myCls,"printNum"."(V)V");
          	jmethodID printNumMethodID =env->GetStaticMethodID(myCls,"printNm"."(V)V");
          }
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
    • JNI操作Java属性和方法

      • // 获取java属性
        j<类型> Get<类型>Field(jobject obj, jfieldID fieldID);	// 通过fieldID从一个object中获取属性
        j<类型> Get Static<类型>Field(jobject obj, jfieldID fieldID);	// 通过fieldID从一个object中获取静态属性
        //设置java属性
        void Set<类型>Field(jobject obj, jfieldID fieldID, j<类型> val);	// 向一个对象的一个属性设置值
        void GetStatic<类型>Field(jobject obj, jfieldID fieldID, j<类型> val);	// 向一个对象的一个静态属性设置值
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
      • 例如

      • jint GetIntField(jobejct obj,jfield fieldID);
        //获得obj对象中,整形属性ID为fieldID的值
        
        void SetObjectField(jobjectobj,jfieldID fielded,jobejct val);
        //设置obj对象中,属性ID为fieldID,属性值为val
        
        void SetStaticCharField(jclass clazz,jfieldID fieldID,jchar value)
        //设置clazz类中,静态属性ID为fieldID的属性值为value
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
      • 例子

      • //对上一节本地的代码进行修改
        void Java_com_test_exam2_NativeCallJava_callNative(JNIEnv * env,jclassthiz,jobject obj)
        {
            jclass myCls = env->GetObjectClass(obj);
        	jfieldID mNumFieldID=env->GetFieldID(myCls,"Number","I");
        	jfieldID mNameFieldID=env->GetStaticFieldID(myCls,"mName","java/lang/String");
            //获得、设置Java成员的属性值
            jint mNum=env->GetIntField(obj,mNumFieldID);
            env->SetIntField(obj,mNumFieldID,mNum+100);
            //获得、设置静态属性的值
            jstring mNum=(jstring)(env->GetStaticObjectField(myCls,mNameFieldID));
            printf("%s\n",mNm);
            jstring newStr=env->NewStringUTF("hello from Native");
            env->SetStaticObejctField(myCls,mNumFieldID,newStr);
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
    • JNI调用Java中的方法

      • // 调用Java方法 <类型>为方法返回值的类型
        // 调用Java成员方法
        Call<类型>Method(jobject obj, jmethodID methodID, ...);	// 第三个参数以参数列表形式传参
        Call<类型>MethodV(jobject obj, jmethodID methodID, va_listargs);	// 第三个参数以向量表形式传参
        Call<类型>MethodA(jobject obj, jmethodID methodID, const jvalue* args);	// 第三个参数以jvalue数组形式传参
        // 调用Java静态方法
        CallStatic<类型>Method(jobject obj, jmethodID methodID, ...);
        CallStatic<类型>MethodV(jobject obj, jmethodID methodID, va_list args);
        CallStatic<类型>MethodA(jobject obj, jmethodID methodID, const jvalue* args);
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
      • 例子

      • //对上一节本地的代码进行修改
        void Java_com_test_exam2_NativeCallJava_callNative(JNIEnv * env,jclassthiz,jobject obj)
        {
            jclass myCls = env->GetObjectClass(obj);
        	jfieldID mNumFieldID=env->GetFieldID(myCls,"Number","I");
        	jfieldID mNameFieldID=env->GetStaticFieldID(myCls,"mName","java/lang/String");
            //获得、设置Java成员的属性值
            jint mNum=env->GetIntField(obj,mNumFieldID);
            env->SetIntField(obj,mNumFieldID,mNum+100);
            
            //获得、设置静态属性的值
            jstring mNum=(jstring)(env->GetStaticObjectField(myCls,mNameFieldID));
            printf("%s\n",mNm);
            jstring newStr=env->NewStringUTF("hello from Native");
            env->SetStaticObejctField(myCls,mNumFieldID,newStr);
            
            //取得Java方法ID
            jmethodID printNumMethodID=env->GetMethodID(myCls,"printNum","(V)V");
            jmethodID printMmMethodID=env->GetStaticMethodID(myCls,"printNm","(V)V");
            
            //调用Myclass对象中的printNm方法
            CallStaticVoidMethod(myCls,printNmMethodID)
                
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
    • 在本地代码中创建Java对象

      • // 创建对象的函数和调用方法类似,只不过是调用的是类的构造方法,第二个参数是类对应构造方法的ID,第三个参数是传递构造方法的参数
        jobject NewObject(jclass clazz, jmethodID methodID, ...);
        jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args);
        jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue* args);
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
      • clazz:要创建的对象的类

        jmethodID:创建对象对应的构造方法ID

        参数列表:,表示是变长参数,以V结束的方法名表示向量表表示参数列表,以A结束的方法名表示以jvalue数组提供参数列表

      • 在本地代码中创建Java对象

      • void Java_com_test_exam2_NativeCallJava_callNative(JNIEnv * env,jclassthiz,jobject obj)
        {
        jclass myCls=env->GetObjectClass(obj);
        //当然可以像下面这样
        //jclass myCls = env->FindClass("com/test/exam2/MyClass");
        //取得MyClass的构造方法ID
        jmethodID myClassMethodID=env->GetMethodID(myCls,"MyClass","(V)V");
        //创建MyClass新对象
        jobject newObj = NewObject(myCls,myClassMethodID);
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
    • 本地代码操作Java String对象

      • jstring NewString(const jchar* unicode, jsize len);	// 根据传入的宽字符串创建一个String对象
        jstring NewStringUTF(const char* utf);	// 根据传入的UTF-8字符串创建一个String对象
        
        // isCopy的值可以为NULL、JNI_TRUE、JNI_FALSE
        // 如果是 JNI_TRUE 的话表示将String复制到新开辟的内存地址中,返回该地址的指针
        // 如果是 JNI_FALSE 的话表示将直接返回String的内存指针,这个时候不要修改内存中的内容,否则破坏了java中字符串不可变的规定
        // 如果是 NULL 的话表示不关心是否复制
        const jchar* GetStringChars(jstring str, jboolean* isCopy);	// 将一个jstring对象转换为宽字符串(UTF-16编码)
        const char* GetStringUTFChars(jstring str, jboolean* isCopy);	// 将一个jstring对象转换为字符串(UTF-8编码)
        
        // 当获取到的字符不再使用的时候要使用以下两个函数对应的释放内存
        ReleaseStringChars(jstring jstr, const jchar* str);	// 第一个参数为本地字符串的资源,第二个参数为本地字符串
        ReleaseStringUTFChars(jstring jstr, const char* str);
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
    • 本地代码操作Java数组

      • // 对象类型数组的操作
        jsize GetArrayLength(jarray array);	// 获取数组长度
        jobjectArray NewObjectArray(jsize len, jclass clazz, jobject init);	// 创建一个新的数组对象
        jobject GetObjectArrayElement(jobjectArray array, jsize index);	// 获取数组中的元素
        void SetObjectArrayElement(jobjectArray array, jsize index, jobject val);	// 设置数组的元素
        
        // 基本类型数组的操作
        // isCopy的值可以为NULL、JNI_TRUE、JNI_FALSE,同String对象的处理
        j<类型>* Get<类型>ArrayElements(j<类型>Array array, jboolean* isCopy);
        // 该函数用于释放数组,第三个参数mode可以取到以下值
        // 0:对Java数组进行更新并释放C/C++数组
        // JNI_COMMIT:对Java的数组进行更新但是不释放C/C++的数组
        // JNI_ABORT:对Java的数组不进行更新,释放C/C++的数组
        void Release<类型>ArrayElements(j<类型>Array array, j<类型>* elems, jint mode);
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
      • len:新创建对象数组长度

        clazz:对象数组元素类型

        init:对象数组元素的初始值

        array:要操作的数组

        index:要操作数组元素的下标索引

        val:要设置的数组元素的值

      • 例子

      • class ArrayTest{
        	static{
        	System.loadLibrary("native_array");
        	}
        	privateint [] arrays=new int[]{1,2,3,4,5};
        	publicnative void show();
        publicstatic void main(String[] args){
        	new ArrayTest().show();
        }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
      • 本地代码的编写:

      • void Java_com_test_exam4_5_ArrayTest_ show(JNIEnv * env,jobject obj){
        	jfieldID id_arrsys=env->GetFieldID(env->GetObjectClass(obj),"arrays","[I");
        	jintArrayarr=(jintArray)(env->GetObjectField(obj,id_arrays));
            jint * int_arr=env->GetIntArrayElements(arr,NULL);
            jsizelen = env->GetArrayLength(arr);
            for(int i=0;i<len;i++)
                cout<<int_arr[i]<<endl;
            env->ReleaseIntArrayElements(arr,int_arr,JNI_ABORT);
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
    • 局部引用与全局引用

    • 局部引用:

      (1):只在上层Java调用本地代码的函数内有效,当本地代码返回时,局部引用自动回收。

      (2)局部引用只在创建他们的线程里有效,本地代码不能将局部引用在多线程之间传递,使用时不能在一个线程调用另一个线程创建的局部引用,不能将一个局部引用保存在全局变量中在其他线程使用。(一定不能使用)

      (3)默认传递给本地代码的引用都是局部引用,所有JNI函数的返回值都是局部引用。

      全局引用:

      (1)只有显式通知VM时,全局引用才会被回收,否则一直有效,Java的GC不会释放该引用的对象。

      (2)全局引用可以跨多个线程使用,可以将全局引用保存在全局变量中或者使用另一个线程访问。(不过不建议保存在全局变量)

      (3)全局引用在程序员手动释放之前一直有效,使用全局引用必须手动创建销毁,可以使用局部引用创建全局引用,不使用全局引 用时必须手动释放。

      • jobect NewLocalRef(jobject ref);	// 创建指向某个对象的局部引用,失败返回NULL
        void DeleteLocalRef(jobject obj);	// 删除局部引用
        jobject NewGlobalRef(jobject lobj);	// 创建指向某个对象的全局引用,失败返回NULL,全局引用只能由此函数创建
        void DeleteGlobalRef(jobject gref);	// 删除全局引用,全局引用不再访问之后必须要调用此函数销毁,若调用失败JVM不会回收该对象
        
        • 1
        • 2
        • 3
        • 4
    • 局部引用需要手动释放的情况

      • 本地代码访问一个很大的java对象,使用完对象之后本地代码去执行耗时操作,这时本地代码还没有返回,需要手动释放引用对象
      • 本地代码创建了大量的局部引用,导致JNI局部引用表溢出,这个时候需要及时的删除不被使用的局部引用
      • 不返回的本地函数,如果函数中持续执行耗时操作不返回(比如无限循环),需要避免因无限创造局部引用导致内存泄漏手动释放局部引用。
    • 全局引用

      • 本地方法被多次调用时,可以使用一个全局引用跨越他们,一个全局引用可以跨越多个线程,并且在程序员手动释放之前,一直有效。
      
      stringClass=env->NewGlobalRef(localRefCls);
      //创建全局引用并指向局部引用
      //删除局部应用
      env->DeleteLocalRef(localRefCls);
      //判断全局引用是否创建成功
      if(stringClass==NULL)
      return NULL;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • 在Java环境中保存JNI对象

      • 最好不要使用全局引用的方式在本地保存JNI的对象,一般可以在Java域中定义一个int类型的属性,在本地层将本地对象的指针转换为int类型,然后设置到Java域的属性中,然后需要的时候从Java域获取该int值,再转换为对应的指针类型,然后就可以通过指针获取值了。(不过需要注意的是需要创建的对象一定是在堆上创建的,要不然对象在函数结束之后就会被回收)

      • // 保存JNI对象
        static void java_native_setup(JNIEnv* env, jobject thiz)
        {
            JNIExcampleContext* context = new JNIExampleContext(env);	// 在堆上创建对象,如果在栈上创建函数结束后会被回收
            jclass clazz = env->GetObjectClass(thiz);	// 获取jclass对象
            jfieldID fieldId = env->GetFieldID(clazz, "mNativeContext", "I");
            env->SetIntField(thiz, fieldId, (int)context);	// 将对象转换为int类型设置到属性中
        }
        // 获取JNI对象
        static void java_native_get(JNIEnv* env, jobject thiz)
        {
            jclass clazz = env->GetObjectClass(thiz);
            jfieldID fieldId = env->GetFieldID(clazz, "mNativeContext", "I");
            // 取出int类型值转换为指针类型
            JNIExcampleContext* context = (JNIExcampleContext*)(env->GetIntField(thiz, fieldId));
            // 进行后续操作
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
    • 本地方法的注册(重要)

      • 需要将本地函数与Java中的native方法关联起来,这是通过一个结构体JNINativeMethod实现的。这个结构体实现了本地函数和Java方法的映射。

        typedef struct {
            char* name;			// Java方法名
            char *signature;	// 本地签名表示字符串
            void* fnPtr;		// Java方法对应的本地函数指针
        } JNINativeMethod;
        
        • 1
        • 2
        • 3
        • 4
        • 5
      • JNI_OnLoad方法

      • 当Java代码中通过System.loadLibrary方法加载本地库的时候,本地代码库被JVM加载,JVM自动调用本地代码中的JNI_OnLoad函数。这个函数实现了本地方法注册的流程

      • jint JNI_OnLoad(JavaVM* vm, void* reserved); // 第一个参数表示了一个JVM实例,第二个参数是保留的
        
        
        • 1
        • 2
      • vm:代表了JVM实例,其实是和JVM相关的一些操作函数指针

        reserved:保留

      • JNI_OnLoad函数中需要做的工作:

        (1)调用GetEnv函数,获取到JNIEnv实例,这是Java运行环境,里面封装了许多本地操作

        (2)通过RegisterNatives函数注册本地方法

        (3)返回JNI版本号

      • jint RegisterNatives(jclass clazz, const JNINativeMethod *methods, jint nMethods); // 注册映射关系
        jint UnregisterNatives(jclass clazz);	// 删除映射关系
        
        
        • 1
        • 2
        • 3
    • JNI 实例(非常重要)

      • 在Linux操作系统中硬件通常有:open、read、write、close等相关操作接口,每个设备硬件还有一些自己的属性。

        (1)用Java编写一个Screen “屏幕”设备类

        (2)设备的初始化,设备打开,关闭,读/写都交给本地代码去实现。

        a. 当写屏幕设备时,将写入内容存放在本地代码缓冲区中

        b. 当读屏幕设备时,则将数据经过简单处理读取出来

        例如:向Screen中写人a〜z的小写字母,读出来变成A〜Z的大写。

        在Ubuntu系统中编写Java程序和本地C++代码,编写Makefile文件用来编译Java代码和本地代码库,最终正常运行Java与C++本地代码。

      • // ScreenTest.java
        package com.test.practice4_8;
        
        // 定义一个屏幕类
        class Screen {
        
            // 静态块 在类第一次被加载的时候调用
            static {
                System.loadLibrary("screen");
            }
        
            private String mDevName; // 设备名
            private int mDevNo; // 设备号
            private boolean isInit = false; // 是否被初始化
            private int mWidth; // 宽度
            private int mHeight; // 高度
        
            public Screen() {
                mDevName = null;
                mDevNo = 0;
            }
        
            // 返回初始化状态
            public boolean isInit(){
                return isInit;
            }
            // 获取屏幕宽度
            public int getWidth(){
                return mWidth;
            }
            // 获取屏幕高度
            public int getHeight(){
                return mHeight;
            }
        
            // 打印设备的信息
            public void printInfo(){
                System.out.println("屏幕名:" + mDevName);
                System.out.println("设备号:" + mDevNo);
                System.out.println("屏幕宽度:" + mWidth);
                System.out.println("屏幕高度:" + mHeight);
            }
        
            public native boolean open();                   // 开启设备
            public native int read(byte[] data, int len);   // 读取设备
            public native int write(byte[] data, int len);  // 写入设备
            public native void close();                     // 关闭设备
        }
        
        // 测试效果
        public class ScreenTest {
        
            public static void main(String[] args) {
                Screen dev = new Screen();
                // 开启设备
                if (!dev.open()){
                    System.out.println("屏幕开启错误");
                    return;
                }
                // 如果开启成功打印信息
                dev.printInfo();
        
                // 往屏幕里写数据
                byte[] data = new byte[26];
                for (int i = 0; i < data.length; i++){
                    data[i] = (byte)(97 + i);
                }
                System.out.println("向屏幕写入 a-z:");
                dev.write(data, data.length);
                // 从屏幕里读数据
                byte[] buf = new byte[64];
                int size = dev.read(buf, buf.length);   // size为实际读出的字节个数
                if (size < 0) {
                    System.out.println("从屏幕读取失败");
                    return;
                }
                System.out.println("从屏幕读取成功 A-Z");
        	System.out.print("大写字母:");
                for (int i = 0; i < 26; i++) {
                    System.out.print((char) buf[i] + " ");
                }
                System.out.println();
                // 关闭设备
                dev.close();
            }
        }
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
        • 47
        • 48
        • 49
        • 50
        • 51
        • 52
        • 53
        • 54
        • 55
        • 56
        • 57
        • 58
        • 59
        • 60
        • 61
        • 62
        • 63
        • 64
        • 65
        • 66
        • 67
        • 68
        • 69
        • 70
        • 71
        • 72
        • 73
        • 74
        • 75
        • 76
        • 77
        • 78
        • 79
        • 80
        • 81
        • 82
        • 83
        • 84
        • 85
        • 86
        • 87
      • 本地实现代码

      • // com_test_practice4_8_ScreenTest.cpp
        #include <unistd.h>
        #include <stdlib.h>
        #include <malloc.h>
        #include <jni.h>
        #include <jni_md.h>
        
        typedef struct _screen {
            jclass clazz;
            jfieldID id_dev_name;
            jfieldID id_dev_no;
            jfieldID id_is_init;
            jfieldID id_width;
            jfieldID id_height;
        } screen, *screen_ptr;
        
        screen_ptr gfieldID;
        // 读写缓冲区
        char _data[64];
        
        // 初始化屏幕的相关ID信息,起到缓存的作用
        static int native_id_init(JNIEnv *env) {
        
            gfieldID = (screen_ptr)malloc(sizeof(screen));
            jclass _clazz = env->FindClass("com/test/practice4_8/Screen");
            gfieldID->clazz = _clazz;
            gfieldID->id_dev_name = env->GetFieldID(_clazz, "mDevName", "Ljava/lang/String;");
            gfieldID->id_dev_no = env->GetFieldID(_clazz, "mDevNo", "I");
            gfieldID->id_is_init = env->GetFieldID(_clazz, "isInit", "Z");
            gfieldID->id_width = env->GetFieldID(_clazz, "mWidth", "I");
            gfieldID->id_height = env->GetFieldID(_clazz, "mHeight", "I");
            if(gfieldID == NULL){
                return -1;
            }
            return 0;
        }
        
        static jboolean native_open(JNIEnv *env, jobject thiz){
            if (native_id_init(env) != 0){
                return JNI_FALSE;
            }
            jstring dev_nm = env->NewStringUTF("NAPO LCD Device");
            if (dev_nm == NULL){
                return JNI_FALSE;
            }
            env->SetObjectField(thiz, gfieldID->id_dev_name, dev_nm);
            env->SetIntField(thiz, gfieldID->id_dev_no, 0x1024);
            env->SetBooleanField(thiz, gfieldID->id_is_init, JNI_TRUE);
            env->SetIntField(thiz, gfieldID->id_width, 2048);
            env->SetIntField(thiz, gfieldID->id_height, 800);
            return JNI_TRUE;
        }
        
        static jint native_read(JNIEnv *env, jobject thiz, jbyteArray arr, jint len){
            if (len < 0) {
                return len;
            }
            // arr数组 的 内存地址就是java中的那一个byte数组的内存地址
            // 获得java层定义的数组
            jbyte* byte_arr = env->GetByteArrayElements(arr, NULL);
            int i = 0;
            for ( ; i < len; i++) {
                if (_data[i] - 32 < 0) break;
                byte_arr[i] = _data[i] - 32;
            }
            env->ReleaseByteArrayElements(arr, byte_arr, 0);
            return i;
        }
        
        static jint native_write(JNIEnv *env, jobject thiz, jbyteArray arr, jint len){
            if (len > sizeof(_data) || len <= 0){
                return len;
            }
            // 获得java层定义的数组
            jbyte *byte_arr = env->GetByteArrayElements(arr, NULL);
            int i = 0;
            printf("小写字母:");
            for (; i < len; i++) {
                _data[i] = byte_arr[i];
                printf("%c ", _data[i]);
            }
            printf("\n");
            env->ReleaseByteArrayElements(arr, byte_arr, JNI_ABORT);
            return i;
        }
        
        static void native_close(JNIEnv *env, jobject thiz){
            env->SetBooleanField(thiz, gfieldID->id_is_init, JNI_FALSE);
            free(gfieldID);
            gfieldID = NULL;
        }
        
        // String fun(String s, int[] is, byte b)  的签名  (Ljava/lang/String;[IB)Ljava/lang/String;
        // 该数组描述了一个 java方法 -> 本地函数的映射关系
        static const JNINativeMethod gMethods[] = {
                {(char*)"open", (char*)"()Z", (void*)native_open},
                {(char*)"read", (char*)"([BI)I", (void*)native_read},
                {(char*)"write", (char*)"([BI)I", (void*)native_write},
                {(char*)"close", (char*)"()V", (void*)native_close},
        };
        
        // 将映射关系数组注册到JVM中 java virtual machine
        static int registerMethods(JNIEnv* env){
            static const char* className = "com/test/practice4_8/Screen";
            jclass clazz = env->FindClass(className);
            if(clazz == NULL){
                printf("没发现这个类\n");
                return -1;
            }
            if(env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK) return -1;
            return 0;
        }
        
        // 相当于本地的入口
        jint JNI_OnLoad(JavaVM* vm, void* reserved){
            JNIEnv *env = NULL;
            jint result = -1;
            if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK){
                printf("获取ENV失败\n");
                goto fail;
            }
            if(env == NULL){
                goto fail;
            }
            if(registerMethods(env) != 0){
                printf("注册方法失败\n");
                goto fail;
            }
            result = JNI_VERSION_1_4;
            fail:
            	return result;
        }
        // java类 Screen 工作:static块中 使用System.loadLibrary(类全名);加载本地库   声明native方法  定义了一些描述这个类的属性
        // 本地库  声明一个结构体描述Screen类 jclass 各种属性的id       四个native方法的实现
        // 方法的注册
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
        • 47
        • 48
        • 49
        • 50
        • 51
        • 52
        • 53
        • 54
        • 55
        • 56
        • 57
        • 58
        • 59
        • 60
        • 61
        • 62
        • 63
        • 64
        • 65
        • 66
        • 67
        • 68
        • 69
        • 70
        • 71
        • 72
        • 73
        • 74
        • 75
        • 76
        • 77
        • 78
        • 79
        • 80
        • 81
        • 82
        • 83
        • 84
        • 85
        • 86
        • 87
        • 88
        • 89
        • 90
        • 91
        • 92
        • 93
        • 94
        • 95
        • 96
        • 97
        • 98
        • 99
        • 100
        • 101
        • 102
        • 103
        • 104
        • 105
        • 106
        • 107
        • 108
        • 109
        • 110
        • 111
        • 112
        • 113
        • 114
        • 115
        • 116
        • 117
        • 118
        • 119
        • 120
        • 121
        • 122
        • 123
        • 124
        • 125
        • 126
        • 127
        • 128
        • 129
        • 130
        • 131
        • 132
        • 133
        • 134
        • 135
      • # Makefile编写
        libscreen.so: com_test_practice4_8_ScreenTest.cpp ScreenTest.class
        	g++ -I ~/aosp/jdk1.6.0_29/include -I ~/aosp/jdk1.6.0_29/include/linux/ $< -fPIC -shared -o $@
        ScreenTest.class: ScreenTest.java
        	javac -d ./ $<
        clean:
        	$(RM) ScreenTest.class Screen.class libscreen.so
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
      • # 启动脚本
        #!/bin/bash
        java -Djava.library.path='.' com/test/practice4_8/ScreenTest
        
        
        • 1
        • 2
        • 3
        • 4

    第六章:Android的对象管理

    Android的对象管理采用了智能指针,其中LightRefBase类是轻量级指针类,RefBase类是重量级指针类,RefBase 类中不仅仅定义了 mStrong 强引用计数,而且还有一个 mWeak 的弱引用计数,强引用计数主要被 sp 对象管理,弱引用计数主要被 wp 对象管理。

    • 智能指针

    • 智能指针

      • C++代码中创建对象的两种方式:创建栈对象创建堆对象

      • class A {
            public:
            	A();
            	~A();
            private:
            	int mVar1;
            	int mVar2;
        }
        int main(int argc, char* argv){
            A a();	// 创建栈对象
            A* a_ptr = new A();	// 创建堆对象
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
      • 在栈上创建对象是局部的,出了作用域就会被销毁,在堆上创建对象管理非常的复杂,必须要手动的进行销毁。实现了智能指针就如同java中的引用管理一样,能够通过引用计数智能的管理对象和引用,智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。智能指针的引用管理sp和wp使用了模板template实现了泛型,只要继承了智能指针的基类LightRefBase或RefBase就能被管理,体现了多态的思想。而且智能指针类中使用了重载运算符,重载了operator->和operator*来返回原始对象指针,能够让智能指针使用起来就像原始对象指针一样。

    • 智能指针的概念

      • 当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享
    • 轻量级指针

      • 当使用智能指针时,只要继承LightRefBase类,那么子类对象就具有智能管理功能了。
      • 类中定义一个mCount变量,初值为0;两个成员函数 incStrong和decStrong 来维护引用计数器的值。
      • 需要注意的是,在desStrong函数中,如果当前引用计数值为1,那么当减1后就会变成0,于是就会delete这个对象。
      • 轻量级指针实现有以下规定

      • (1)LightRefBase类的对象可以通过智能指针sp进行管理

        (2)当使用智能指针sp指向、赋值、初始化LightRefBase对象时,该对象引用计数加1

        (3)当sp指针使用完后,其指向的对象引用计数自动减1

        (4)当LightRefBase对象的引用计数为0时,该对象会被删除

    • 强引用和弱引用

      • 强引用指针sp:表示一个对象的强引用关系,可以直接访问目标成员对象,并且直接管理着目标对象的销毁
      • 弱引用指针wp:表示对一个对象的弱引用关系,不能像sp一样可以直接访问目标对象成员,如果要访问的话,必须调用promote函数由弱引用升级为强引用
      • 强引用增加时,强弱引用计数都增加;强引用减少时,强弱引用计数都减少
      • 弱引用增加时,只有弱引用计数增加;弱引用减少时,只有弱引用计数减少

    第七章:Binder通信

    Binder框架定义了四个角色,它们分别是:Server、Client、ServerManager (SMgr) 、Binder驱动 。其中前三者运行在用户空间,第四者运行于内核空间。

    传统的Linux系统IPC通信机制:管道、消息队列、共享内存、套接字、信号量、信号

    • Binder的小知识

      • 原本是一个进程间的通信工具,在android binder中它用于实现RPC,使得当前进程调用另外一个进程的函数时就像调用自己进程空间函数一样简单
    • Android进程空间与Binder机制

      • 每个进程都运行在自己独立的虚拟地址空间(0-3GB),对于32位机而言,4GB的地址空间的高1GB为内核空间
      • 不同进程之间通信必须要借助于共享的内核空间
      • linux系统的ipc通信机制分为两大类:
        • System V进程间通信和BSD进程间通信
        • System V通信方式:信号、管道、信号量、共享内存、消息队列
        • 当安卓的应用程序试图与另外一个进程进行IPC通信时,先将自己的通信请求交给各进程共享的Binder Driver,然后Binder Driver将通信请求转交给目标进程,目标进程将通信结果以相同的方式发送给Binder Driver,Binder Driver再将结果返回给通信发出者,显然,Binder Driver在整个通信中起到了媒介的作用
    • Binder通信和传统的Linux进程间通信方式相比的优点

      • 传输性能高
        • 传统的IPC(套接字、管道、消息队列)需要拷贝两次内存(数据先从发送方缓冲区复制到内核开辟的缓冲区中,然后再从内核缓冲区复制到接收方缓冲区,至少有两次复制过程)
        • 共享内存无须复制但是控制复杂,难以使用
        • Binder只需拷贝一次内存
      • 安全性
        • Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志。
        • 传统IPC的接收方无法获得对方进程可靠的UID/PID(用户ID/进程ID),从而无法鉴别对方身份。
        • 使用传统IPC只能由用户在数据包里填入UID/PID,但这样不可靠,容易被恶意程序利用,故可靠的身份标记只有由IPC机制本身在内核中添加。
        • 其次传统IPC访问接入点是开放的,无法建立私有通道。比如命名管道的名称,system V的键值,socket的ip地址或文件名都是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接
      • 使用简单
        • 采用了C/S架构和面向对象的调用方式,屏蔽了实现的细节,在使用Binder时,就跟调用一个本地对象实例一样
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ufwhJa39-1656159576809)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220619182020614.png)]
    • Binder的操作步骤

      • 在这里插入图片描述

      • Server注册服务

        • Server进程向Binder驱动发起注册服务请求
        • Binder驱动将注册请求转发给Service Manager进程
        • Sevice Manager进程添加该Server服务,Service Manager中保留了该Server的信息和其引用,并且Binder驱动在内核空间保存了这个Server的实体
      • Client获取服务

        • Client向Binder驱动发起获取服务的请求,并传递要获取的服务名称
        • Binder驱动将该请求转发给Service Manager进程;
        • Service Manager查找到Client需要的Server对应的Binder实体的Binder引用并且返回给Binder驱动
        • Binder驱动将Binder代理对象返回给Client(这个时候Clent和Server已经建立了连接)
      • Client使用服务

        • Binder驱动为跨进程通信做准备实现了内存映射(调用了mmap系统函数);
        • Client进程将参数数据发送到Server进程
        • Server进程根据Client进程的要求迪调用目标方法
        • Server进程将目标方法的结果返回给Client进程
      • Server可以通过已经建立的实名Binder连接,将创建的Binder实体传递给Client,因为这个Binder没有向Service Manager注册信息,所以别的进程就无法通过任何方式获取这个Binder的引用,这是一个匿名Binder。

    • RPC通信机制

      • 通过open()打开Binder驱动文件描述符
      • 通过mmap在内核中开辟一块内存来保存接收到的IPC数据
      • 调用ioctl将IPC数据作为参数,传递给Binder驱动
      • Binder驱动再将数据和请求转交给作为服务器端的目标进程
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TRFngqDD-1656159576812)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220619170546664.png)]
    • IPC和RPC通信的区别

      • IPC:(Inter Process Communication )跨进程通信
        • 泛指进程之间任何形式的通信行为,是个可以拿来到处套的术语。它不仅包括各种形式的消息传递,还可以指共享资源,以及同步对象[mutex 或者其他类似的东西,即确保安全的并发访问共享资源(也就是防止两个或两个以上的对象同事对同一个数据成员进行修改,从而导致数据被破坏,或者竞争条件下同时读/写数据而导致错误的情况发生)的东西。
      • RPC😦Reomote Procedure Call) 远程过程调用
        • 特指一种隐藏了过程调用时实际通信细节的IPC方法。客户端将调用一个本地方法,而这个本地方法则是负责透明的与远程服务端进行过程间通信。这个本地方法会讲相关参数顺序打包到一个消息中,然后把这个消息发送给服务端提供的方法,服务端的方法会从消息中解出序列化发出来的参数,然后执行,最后仍以同样的方式将方法的返回值发送给客户端。
    • 匿名和实名Binder的区别与联系

    • 理解:

    • 其相互配合完成一次Binder通信的大概过程如下:

      首先在系统开机启动时,一个进程通过Binder驱动将自己注册成ServiceManager,其地址固定为0;
      Server通过驱动向ServiceManager中注册Binder(Server中的Binder实体),表明可以对外提供服务。驱动为这个Binder创建位于内核中的实体节点以及ServiceManager对实体的引用,将名字以及新建的引用打包传给ServiceManager,ServiceManger将其填入查找表;
      Client通过名字,在Binder驱动的帮助下从ServiceManager中获取到对Binder实体的引用,通过这个引用就能实现和Server进程的通信。

      • img

    第八章:硬件抽象层

    HAL:硬件设备抽象

    • linux的设备驱动:
      • Kernel中使用GPL协议开源一些非核心的接口访问代码
      • 在Kernel上的应用层使用Apache协议,主要是硬件厂商不希望开源的逻辑代码,仅提供二进制代码
    • HAL存在的原因

      • HAL的两种框架形式

        • Module框架
        • Stub代理框架
      • HAL使用方式

        • HAL的代码被编译生成动态模块库,可以动态加载到内核中运行,Android应用程序和框架通过JNI加载并调用HAL module 库代码,在HAL module 库再去访问设备驱动。
      • HAL Stub 架构简称 321架构。因为其只需要三个结构体、两个常量、一个函数

    • HAL Stub架构分析

      • 三个结构体

      • // 表示了一个硬件
        struct hw_module_t{
            uint32_t tag;				// 该值必须声明为HARDWARE_MODULE_TAG
            uint16_t version_major;		 // 主版本号
            uint16_t version_minor;		 // 次版本号
            const char* id;				// 硬件id名,唯一标识module
            const char* name;			// 硬件module名字
            const char* author;			// 作者
            struct hw_module_methods_t* methods;	// 指向封装有open函数指针的结构体
            void* dso;					// 动态共享库,指向打开的so库的指针
            uint32_t reserved[32];		// 128字节补齐
        };
        // 封装了open函数
        struct hw_module_methods_t{
            int (*open)(const struct hw_module_t* module, const char* id, struct hw_device_t** device);
        };
        // 表示了一个操作接口
        struct hw_device_t{
            uint32_t tag;								// 必须赋值为HARDWARE_DEVICE_TAG
            uint32_t version;							// 版本号
            struct hw_module_t* module;					 // 说明这个操作接口属于哪一个硬件
            uint32_t reserved[12];						 // 字节补齐
            int (*close)(struct hw_device_t* device);		//  
        }
        
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
      • 两个常量

      • #define HAL_MODULE_INFO_SYM HMI				// Stub 硬件模块对象固定的名字
        #define HAL_MODULE_INFO_SYM_AS_STR "HMI"	// Stub 硬件模块对象固定的字符串形式的名字
        
        
        • 1
        • 2
        • 3
      • 一个函数

      • 	// 第一个参数是硬件id,第二个参数是要获取的module,使用该函数初始化一个hw_module_t指针
        int hw_get_module(const char* id, const struct hw_module_t** module);
        
        • 1
        • 2
    • HAL Stub 注册

      • led_moudle_t

      • 继承led_moudle_t并且修改结构体

      • struct led_module_t {
        	struct hw_module_t common;
        	// 后面可以放置扩展的属性
        };
        
        struct led_control_device_t {
        	struct hw_device_t common;
        	// 后面是为操作接口扩展的操作函数
        	int (*getcount_led)(struct led_control_device_t *dev);
        	int (*set_on)(struct led_control_device_t *dev, int arg);
        	int (*set_off)(struct led_control_device_t *dev, int arg);
        };
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
      • 一定要注意的是common必须要做为第一个数据项。这样通过强制类型转换实现结构体的“多态”时才不会出错。

    • 实现操作接口里对硬件操作的函数

      • static int led_device_close(struct hw_device_t* device)
        {
        	struct led_control_context_t* ctx = (struct led_control_context_t*)device;
        	if (ctx) {
        		free(ctx);
        	}
        	close(fd);
        	return 0; 
        }
        static int led_getcount(struct led_control_device_t *dev)
        {
        	LOGI("led_getcount");
        	return 4;
        }
        
        static int led_set_on(struct led_control_device_t *dev, int arg)
        {    
        	LOGI("led_set_on");
        	ioctl(fd,LED_ON,arg);
        	return 0;
        } 
        
        static int led_set_off(struct led_control_device_t *dev, int arg)
        {
        	LOGI("led_set_off");
        	ioctl(fd,LED_OFF,arg);
        	return 0;
        }
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
    • led_device_t和父结构体hw_device_t的关系

      • struct led_device_t
        {
        	struct hw_device_t common;
        	int (*getcount_led)(struct led_device_t *dev);
        	int (*set_on)(struct led_device_t *dev);
        	int (*set_off)(struct led_device_t *dev);
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
    • 需要实现设备打开的函数,并将其封装到struct hw_module_methods_t结构体中去,将拓展的函数封装到struct led_control_device_t结构体中去

      • // open函数实现
        static int led_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device)
        {
        	struct led_control_device_t* context;
        	context = (struct led_control_device_t*)malloc(sizeof(*context));	// 在堆上动态开辟内存空间
        	memset(context, 0, sizeof(*context));
        	// 硬件抽象层必须要设置的初始化属性
        	context->common.tag= HARDWARE_DEVICE_TAG;
        	context->common.version = 0;
        	context->common.module = module;
        	context->common.close = led_device_close; 
        	// 初始化扩展的控制LED函数
        	context->set_on = led_set_on;
        	context->set_off = led_set_off;
        	context->getcount_led = led_getcount;
        	*device = (struct hw_device_t*)context;
        	if((fd=open("/dev/led", O_RDWR))==-1)
        	{
        		LOGI("open error\n");
        		exit(1);
        	}
            else LOGI("open ok\n");
        	return 0;
        }
        // 将open函数封装到 struct hw_module_methods_t 结构体中去
        static struct hw_module_methods_t led_module_methods = {
        	open: led_device_open  
        };
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
    • 模块变量的名字必须为HMI或者是HAL_MODULE_INFO_SYM(只有是这个名字才能从上层调用时使用hw_get_module函数找到它)

      • const struct led_module_t HAL_MODULE_INFO_SYM = {
        	common: {
        		tag: HARDWARE_MODULE_TAG,
        		version_major: 1,
        		version_minor: 0,
        		id: LED_HARDWARE_MODULE_ID,
        		name: "led HAL module",
        		author: "farsight",
        		methods: &led_module_methods,
        		}, 
        		// 后面是扩展属性的初始化
        };
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
    • 用语言描述HAL的实现(简答题)

      • 定义结构体hw_module_t 表示硬件、hw_module_methods_t 封装open函数、hw_device_t 表示硬件操作接口。
      • hw_module_t中的methods指向hw_module_methods_t,是hw_module_methods_t类型的指针。
      • hw_module_methods_t中的open是指向初始化函数的函数指针。
      • 在初始化函数初始化结构hw_device_t,返回硬件操作接口。
      • 定义名字为HMI的module对象,通过hw_get_module函数获取。

    第九章:HAL硬件抽象层进阶 Sensor HAL 实例

    • Android系统内置支持的传感器

      • 加速度传感器、磁力传感器、方向传感器、陀螺仪、环境光照传感器、压力传感器、温度传感器、距离传感器 等。
    • 编写一个传感器的应用程序的步骤

      • 通过调用 Context. getSystemService(SENSOR_ SERVICE)获得传感器服务,实现返回的是封装了 Sensorservice的 Sensor Manager对象。
      • 调用 Sensor Manager. get Default Sensor( SensorTYPE_ ORIENTATION)来获得指定类型的传感器对象,方便获得传感器的数据。
      • 通过 SensorManager registerlistener注册 SensorEvent listener监听器,监听获得的传感器对象,当传感器数据提交上来时,能被应用程序得到。
      • 实现监听器里传感器上报数据的具体操作。

    ff = led_set_off;
    context->getcount_led = led_getcount;
    device = (struct hw_device_t)context;
    if((fd=open(“/dev/led”, O_RDWR))==-1)
    {
    LOGI(“open error\n”);
    exit(1);
    }
    else LOGI(“open ok\n”);
    return 0;
    }
    // 将open函数封装到 struct hw_module_methods_t 结构体中去
    static struct hw_module_methods_t led_module_methods = {
    open: led_device_open
    };

    ```
    
    • 1
    • 模块变量的名字必须为HMI或者是HAL_MODULE_INFO_SYM(只有是这个名字才能从上层调用时使用hw_get_module函数找到它)

      • const struct led_module_t HAL_MODULE_INFO_SYM = {
        	common: {
        		tag: HARDWARE_MODULE_TAG,
        		version_major: 1,
        		version_minor: 0,
        		id: LED_HARDWARE_MODULE_ID,
        		name: "led HAL module",
        		author: "farsight",
        		methods: &led_module_methods,
        		}, 
        		// 后面是扩展属性的初始化
        };
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
    • 用语言描述HAL的实现(简答题)

      • 定义结构体hw_module_t 表示硬件、hw_module_methods_t 封装open函数、hw_device_t 表示硬件操作接口。
      • hw_module_t中的methods指向hw_module_methods_t,是hw_module_methods_t类型的指针。
      • hw_module_methods_t中的open是指向初始化函数的函数指针。
      • 在初始化函数初始化结构hw_device_t,返回硬件操作接口。
      • 定义名字为HMI的module对象,通过hw_get_module函数获取。

    第九章:HAL硬件抽象层进阶 Sensor HAL 实例

    • Android系统内置支持的传感器

      • 加速度传感器、磁力传感器、方向传感器、陀螺仪、环境光照传感器、压力传感器、温度传感器、距离传感器 等。
    • 编写一个传感器的应用程序的步骤

      • 通过调用 Context. getSystemService(SENSOR_ SERVICE)获得传感器服务,实现返回的是封装了 Sensorservice的 Sensor Manager对象。
      • 调用 Sensor Manager. get Default Sensor( SensorTYPE_ ORIENTATION)来获得指定类型的传感器对象,方便获得传感器的数据。
      • 通过 SensorManager registerlistener注册 SensorEvent listener监听器,监听获得的传感器对象,当传感器数据提交上来时,能被应用程序得到。
      • 实现监听器里传感器上报数据的具体操作。
  • 相关阅读:
    概率论中的几个重要悖论问题
    <C++> 优先级队列
    数据库安全如何保障?YashanDB有妙招(上篇)
    【精选】构建智能木材计数系统:深度学习与OpenCV完美结合(详细教程+源码)
    【Unity3D】UI Toolkit数据动态绑定
    支持JDK19虚拟线程的web框架,之二:完整开发一个支持虚拟线程的quarkus应用
    工业网关连接工业生产设备与物联网系统的关键设备
    系统升级数量超微软预期,Win10/11盗版激活被封杀
    探索Django验证码功能的实现 - DjangoStarter项目模板里的封装
    jar包打成docker镜像文件推送至服务器,服务器拉取并重启容器
  • 原文地址:https://blog.csdn.net/weixin_45822638/article/details/125463688