【48】Android通过libjpeg-turbo库实现图片压缩

news2025/1/11 19:58:47

(1)公司为节约图片占用服务器存储资源成本,需要对Android手机客户端所传递到云存储服务器中的图片进行压缩,在不影响图片失真程度的情况下,最大限度的压缩图片以节省图片所占用的存储空间。
(2)本文即实战了通过jni调用libjpeg-gurbo库实现图片压缩的目的。
(3)实战结果:可以将一张4.9M的图片压缩至600kb大小,而且图片并不失真。
(4)本文细节尚存疏漏之处,请各位看观多多谅解,但能搞定这件事情已经实属不易,或许可以给你一些解决问题的思路供你参考。

Android通过libjpeg-turbo库实现图片压缩

文章目录

  • Android通过libjpeg-turbo库实现图片压缩
    • 1.Android CPU基础知识
      • 1.1安卓CPU类型的说明
      • 1.2安卓CPU类型的兼容性说明
      • 1.3其他说明
      • 1.4abiFilters 'armeabi-v7a'
    • 2.使用AndroidStudio编译生成libjpeg-turbo动态链接库so文件
      • 2.1libjpeg-turbo git clone地址
      • 2.2使用AndroidStudio编译生成so动态链接库文件
    • 3.将libjpeg_turbo的so库文件拷贝到其他项目使用,以zipphoto项目为例
      • 3.1将自己编写的cpp源文件生成so动态链接库
      • 3.2在Activity中调用图片压缩的本地方法
        • 3.2.1图片压缩工具源码
        • 3.2.2Activity调用源码
        • 3.2.3Activity布局文件
    • 4.总结
      • 4.1注意:
    • 5.参考文档

本尊

1.Android CPU基础知识

1.1安卓CPU类型的说明

(1)arm64-v8a: 第8代、64位ARM处理器,目前手机大多数是此架构.
(2)armeabiv-v7a: 第七代及以上的 ARM 处理器。2011年5月以后生产的大部分安卓设备都使用它
(3)armeabi: 第5代、第6代的ARM处理器,早期的手机用的比较多,缺乏对浮点数计算的硬件支持,在须要大量计算时有性能瓶颈。
(4)x86: 平板、模拟器用得比较多。x86 架构的手机都会包含由 Intel 提供的称为 Houdini 的指令集动态转码工具,实现对arm .so 的兼容。考虑 x86不到1% 的市场占有率,x86 相关的两个 .so 也是可以忽略的。
(5)x86_64: 64位的平板
(6)mips/mips64 极少用于手机可忽略。

1.2安卓CPU类型的兼容性说明

(1)armeabi设备只兼容armeabi,不支持硬件辅助浮点运算,支持所有的 ARM* 设备;
(2)armeabi-v7a设备兼容armeabi-v7a、armeabi;
(3)arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi;
(4)x86设备兼容x86、armeabi;
(5)x86_64设备兼容x86_64、x86、armeabi;
(6)mips64设备兼容mips64、mips;
(7)mips只兼容mips;

1.3其他说明

(1)以arm64-v8a设备为例,该Android设备优先寻找libs目录下的arm64-v8a文件夹。如果有文件夹,但是没有so库,则会报错。如果没有arm64-v8a文件夹,则会去找armeabi-v7a文件夹。如果找不到armeabi-v7a文件夹,则寻找armeabi文件夹,兼容运行该文件夹下的so,
(2)从上面解释就可以大概知道下载哪种APK了。普通手机用户,建议下载arm64-v8a(第8代、64位ARM处理器)版本,能够发挥手机最佳性能(只要本型号手机支持8G运存或8G以上就是64位处理器)。如果是很老的手机,也有可能不是64位处理器,那么就选择armeabi-v7a,几乎通用所有手机,而且也兼容64位处理器。

1.4abiFilters ‘armeabi-v7a’

(1)指的是以armeabi-v7a指令环境运行
(2)默认会编译出4个平台,arm64-v8a、armeabi-v7a、x86、x86_64

2.使用AndroidStudio编译生成libjpeg-turbo动态链接库so文件

2.1libjpeg-turbo git clone地址

git clone https://github.com/libjpeg-turbo/libjpeg-turbo

2.2使用AndroidStudio编译生成so动态链接库文件

(1)新建Android Native C++项目
在这里插入图片描述

(2)将libjpeg-turbo的源代码(注意是所有文件)复制到 native c++项目的cpp目录中
在这里插入图片描述

(3)配置app目录下的build.gradle文件,主要检查两个配置项是否已经配置了

a.android的defaultConfig配置中是否存在ndk关于CPU类型的配置

ndk{
	abiFilters 'arm64-v8a'
}

b.android下是否存在externalNativeBuild的配置,即

externalNativeBuild {
    cmake {
        path file('src/main/cpp/CMakeLists.txt')
        version '3.22.1'
    }
}

(4)运行项目生成so库
(1)生成CPU类型为arm64-v8a与armeabiv-v7a的so库需要在Android真机上运行项目,项目运行完之后,到项目app目录下的.cxx目录里面去找生成的so库文件。
在这里插入图片描述

(2)生成CPU类型为x86与x86_64的so库需要在Android模拟器上运行项目如雷电或夜神或其他Android模拟器上运行,项目运行完之后,同样是在app目录下的.cxx目录里面去找生成的so库文件。

(3)以下是多次设置CPU类型运行项目后生成so库文件的列表

在这里插入图片描述

3.将libjpeg_turbo的so库文件拷贝到其他项目使用,以zipphoto项目为例

(1)新建Android项目,取名zipphoto。
(2)新建jni目录
(3)把libjpeg-turbo关于图片压缩相关的头文件拷贝到jni目录。
注意:到libjpeg-turbo的源代码中去搜索头文件所在的目录,然后拷贝过来,主要是以下几个头文件。
在这里插入图片描述

(4)编写图片压缩zipimg.cpp源文件

#include <jni.h>
#include <string>
#include <android/bitmap.h>
#include <android/log.h>
#include <malloc.h>
#define TAG "image "
#define LOGE(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
/**
 * 导入jpeg的头文件
 */
extern "C"{
    #include "jpeglib.h"
}

typedef uint8_t BYTE;

/*
 * (1)extern "C":声明下面的代码,采用C的编译方式。
 * (2)JNIEnv *env:是整个JNI的所有API的一个桥梁,只需要将这个里面的所有函数学完了,那么JNI就学完了。即所有的操作都需要通过它来。
 * (3)JNIEXPORT:JNI重要标记关键字,不能少(VS编译能通过,运行会报错)或(AS运行不会报错),规则(标记为该方法可以被外部调用),Windows内部规则。
 * (4)JNICALL:也是一个关键字,(可以少的) jni call(约束函数入栈顺序和堆栈内存清理的规则)。
 * (5)jclass clazz:如果java本地方法声明成static native,jclass就是指的这个类,如果java本地方法未声明成static,就会变成jobject thiz,即指的是这个类的实例
 * (6)如果当前是native-lib.c,(*env)->xxx函数,如果是C语言,JNIEnv *env是二级指针,C是没有对象的,想持有env环境,就必须传递进去,(*env).AllocObject(clazz);
 * (7)如果当前是native-lib.cpp,env->xxx函数,如果是C++语言,JNIEnv *env是一级指针,evn可以直接调用一级指针下的函数,C++是有对象的,本身就会持有this,所以不需要传,env->AllocObject(clazz);
 */
/*
extern "C"
JNIEXPORT jstring JNICALL
Java_com_gdc_imagezip_ImageZipUtil_compressBitmap(JNIEnv *env, jclass clazz, jobject bitmap, jint width, jint height, jint quality, jbyteArray file_name, jboolean optimize) {

}*/

/**
 * 1.写数据,将b、g、r通过jpeg的方式写入进去
 * 2.APP需要通过jpeg_compress_struct去调用
 * @param data
 * @param path
 * @param w
 * @param h
 */

void writeJpg(BYTE *data, const char *path, int w, int h) {
    struct jpeg_compress_struct jpeg_struct;
    //1.初始化
    //()设置错误信息
    struct jpeg_error_mgr jerr;
    //()错误信息初始化,设置错误的处理信息
    jpeg_struct.err = jpeg_std_error(&jerr);
    //()设置缓冲区,创建开始压缩任务
    jpeg_create_compress(&jpeg_struct);
    //()因为我们要输出,所以打开一个文件,即输出到文件中去,
    FILE *file = fopen(path,"wb");
    //()设置输出路径
    jpeg_stdio_dest(&jpeg_struct,file);
    //()设置图片宽高
    jpeg_struct.image_width = w;
    jpeg_struct.image_height = h;
    //()设置使用哈夫曼算法进行压缩,为false的时候采用哈夫曼算法进行压缩
    jpeg_struct.arith_code = FALSE;
    //()设置对结构进行优化
    jpeg_struct.optimize_coding = TRUE;
    //()初始化位深
    jpeg_struct.in_color_space = JCS_RGB;
    //()初始化组成,R、G、B三个为一组,所以组成为3.
    jpeg_struct.input_components = 3;
    //()设置其他的参数函数,设置成默认的
    jpeg_set_defaults(&jpeg_struct);
    //()设置压缩质量,范围是0~100,一般20优化比是最好的。
    jpeg_set_quality(&jpeg_struct,20,true);


    //2.开始压缩
    jpeg_start_compress(&jpeg_struct,TRUE);

    //()写入数据
    JSAMPROW row_pointer[1];

    //()行的rgb
    int row_stride = w*3;
    while(jpeg_struct.next_scanline < h){
        row_pointer[0] = reinterpret_cast<JSAMPROW>(&data[jpeg_struct.next_scanline * w * 3]);
        jpeg_write_scanlines(&jpeg_struct,row_pointer,1);//让next_scanline++自动加1
    }

    //()结束压缩
    //()释放结构体
    jpeg_finish_compress(&jpeg_struct);
    //()
    jpeg_destroy_compress(&jpeg_struct);
    //()关闭文件
    fclose(file);
}

/*
 *1.jpeg压缩
 * (1)条件:压缩程度比较低,采用的是哈夫曼的算法进行压缩,所有的压缩数据必须是元数据,即不能够被分割的数据。
 * (2)bitmap不是元数据,像素也不是元数据,因为像素还可以分为a r g b。
 * (3)一个像素由多少位去表示呢?由4个字节表示。
 * 一个像素是一个int类型,一个像素由高8位,第二个8位,第三个8位,第四个8位,每个8位分别表示A、R、G、B.
 * 4个字节就由4个8位组成。
 * (4)A、R、G、B整张力片取出来之后,放到一个数组里面去。
 * (5)将一张bitmap取出来之后,我肯定要取出它的像素数据,怎么取出像素数据呢?
 *
 * 2.取出Bitmap的像素数据
 * int AndroidBitmap_lockPixels(_JNIEnv *env, jobject jbitmap, void **addrPtr)
 * (1)_JNIEnv *env:结构体
 * (2)jobject jbitmap:bitmap
 * (3)void **addrPtr:入参与出参对象,它是一个数组,将这个数组传进来之后,经过这个方法,它就将bitmap像素数据全部转化到数组里面去.
 *
 * 3.在NDK里面去取出图片的宽和高
 * static int AndroidBitmap_getInfo(_JNIEnv *env, jobject jbitmap, struct__anonymous *info)
 * (1)_JNIEnv *env:结构体
 * (2)jobject jbitmap:bitmap
 * (3)struct__anonymous *info:入参与出参结构体
 *
 * 4.整体需求就是
 * (1)将一张图片全部解压成元数据,并且将它放到一个datas数组里面去。
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_gdc_zipphoto_util_ImageZipUtil_compressBitmap(JNIEnv *env, jobject thiz, jobject bitmap, jstring path_) {
    //()获取图片存储路径
    const char *path = env->GetStringUTFChars(path_, 0);
    LOGE("==================1.进入方法==================");
    //()定义入参出参对象AndroidBitmapInfo bitmapInfo,在AndroidBitmapInfo结构体中就有了图片的宽、高属性。
    AndroidBitmapInfo bitmapInfo;
    //()在NDK里面去取出图片的宽和高,调用此函数之后,bitmapInfo中会有bitmap的具体宽高值。
    AndroidBitmap_getInfo(env,bitmap,&bitmapInfo);

    //()取出Bitmap的像素数据,放到pixels数组里面去
    BYTE *pixels;
    //()将bitmap中的数据转换到pixels里面
    AndroidBitmap_lockPixels(env,bitmap,reinterpret_cast<void **>(&pixels));
    //()遍历pixels里面的内容,它的内容是4个字节的int数据。遍历图片的宽、高。
    int h = bitmapInfo.height;
    int w = bitmapInfo.width;
    //()定义像素数据,用于存放取出的像素数据
    int color;
    //()定义R、G、B分别存放红、绿、蓝三种颜色
    BYTE r,g,b;
    //()定义新的数组用于存放取出来的元数据,tmpDatas是防止丢失的数组。
    BYTE *datas, *tmpDatas;
    //()这个数组有多大呢?它应该是w*h的3倍
    datas = static_cast<BYTE *>(malloc(w * h * 3));
    tmpDatas = datas;

    for(int i = 0; i < h ; ++i){
        for(int j = 0 ; j < w ; ++j){
            //()将数组元素像素数据取出来
            color = *((int*)pixels);
            //()取出A、R、G、B,但本例中只取R、G、B,要取R、G、B就需要用到左移与右移
            r = ((color & 0x00FF0000) >> 16);
            g = ((color & 0x0000FF00) >> 8);
            b = (color & 0x000000FF);
            //()将元数据放到一个新的数组里面去,如何放呢?是按b、g、r、a的方式放的,即是倒着放的,
            *datas = b;
            *(datas+1) = g;
            *(datas+2) = r;
            datas += 3;
            //()取下一个像素数据需要+4,因为每个像素是由4个字节的32位int数据表示的。
            pixels += 4;
        }
    }

    LOGE("==================2.压缩完毕==================");

    //()释放图片所占用的资源
    AndroidBitmap_unlockPixels(env,bitmap);

    LOGE("==================3.写入SD卡==================");
    //()写数据,将b、g、r通过jpeg的方式写入进去
    writeJpg(tmpDatas,path,w,h);


    env->ReleaseStringUTFChars(path_,path);
}

3.1将自己编写的cpp源文件生成so动态链接库

(1)在jni目录新建Android.mk文件与Application.mk文件
(2)Android.mk文件主要用于说明我自己的so库需要哪些so动态库的支持,有哪些cpp源文件,本例的内容如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := libjpeg
LOCAL_SRC_FILES := libjpeg.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)

LOCAL_MODULE    := zipimg
LOCAL_SRC_FILES := zipimg.cpp
LOCAL_SHARED_LIBRARIES := libjpeg
LOCAL_LDLIBS := -ljnigraphics -llog
LOCAL_C_INCLUDES := $(LOCAL_PATH)

include $(BUILD_SHARED_LIBRARY)

(3)Application.mk文件主要用于说明我自己的so库支持哪些CPU类型 ,以及最高支持到哪一个Android平台使用,内容如下:

APP_ABI :=arm64-v8a
APP_PLATFORM := android-33

(3)为zipphoto配置ndk编译环境
File----- > Project Structure----->设置自己下载的ndk路径
在这里插入图片描述

(4)配置项目app目录下的build.gradle文件
a.在android节点下,添加sourceSets节点配置,用于指定生成自己的so动态链接库成功后,存放输出so库到哪个目录,本例是放到libs目录下

    sourceSets{
        main{
            jni.srcDirs = []//设置禁止gradle生成Android.mk
            jniLibs.srcDirs = ['libs']
        }
    }

b.在android节点下,添加一个task构建任务,配置内容如下

task ndkBuild(type:Exec){
        commandLine "D:\\android-ndk-r27c\\ndk-build.cmd",
                'NDK_PROJECT_PATH=build/intermediates/ndk',
                'NDK_LIBS_OUT=libs',
                'APP_BUILD_SCRIPT=jni/Android.mk',
                'NDK_APPLICATION_MK=jni/Application.mk'
    }

(5)通过ndk构建生成so动态链接库
我所采用的是通过命令行的方式生成动态链接库。
a.将自己下载的ndk设置到windows系统环境变量中。过程略(自己网上查)
b.打开命令提示符
win+R cmd 回车
c.进入到zipphoto的jni目录,即执行命令:
cd F:\cxzworkspace\zipphoto\app\jni
d.执行ndk-build命令
在这里插入图片描述

通过以上步骤就已经完成了自己编写的c++程序的so动态链接库的生成。

3.2在Activity中调用图片压缩的本地方法

3.2.1图片压缩工具源码
public class ImageZipUtil {
	//1.引入动态链接库
    static {
    	//(1)zipimg动态链接库用于调用图片压缩jpeg.so动态链接库中所提供的api函数实现图片的压缩
        System.loadLibrary("zipimg");
        //(2)引入图片压缩jpeg.so动态链接库
        System.loadLibrary("jpeg");
    }
    
    //2.图片压缩本地方法,在zipphoto.cpp的c++源文件中具体实现
    public native void compressBitmap(Bitmap bitmap, String path);
}
3.2.2Activity调用源码
package com.gdc.zipphoto;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import com.gdc.zipphoto.permission.PermissionsUtil;
import com.gdc.zipphoto.util.FilePathManager;
import com.gdc.zipphoto.util.ImageZipUtil;

import java.io.File;

public class MainActivity extends Activity implements View.OnClickListener , PermissionsUtil.IPermissionsResult {
    private Button mCompressBtn;
    private ImageView mImage;
    /**
     * 图片存放根目录
     */
    private final String mImageRootDir = FilePathManager.getFileDirectory().getAbsolutePath();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //(1)批量申请多个权限
        PermissionsUtil.getInstance().checkPermissions(MainActivity.this,PermissionsUtil.getInstance().getPermissionList(),this);

        //(2)压缩后保存临时文件目录
        File tempFile = new File(mImageRootDir);
        if (!tempFile.exists()) {
            tempFile.mkdirs();
        }

        //(3)
        mCompressBtn = (Button) findViewById(R.id.compress_btn);
        mImage = (ImageView) findViewById(R.id.image);

        //(4)
        mCompressBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if(v == mCompressBtn){
            new Thread(new Runnable() {

                @Override
                public void run() {
                    //(1)压缩后的图片存储路径
                    final File afterCompressImgFile = new File(mImageRootDir + "/temp.jpg");

                    //(2)需要压缩的图片路径
                    String tempCompressImgPath = mImageRootDir + File.separator + "temp.jpg";

                    //(3)直接使用jni libjpeg压缩
                    Bitmap bitmap = BitmapFactory.decodeFile(tempCompressImgPath);
                    new ImageZipUtil().compressBitmap(bitmap,tempCompressImgPath);

                    MainActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mImage.setImageBitmap(BitmapFactory.decodeFile(afterCompressImgFile.getPath()));
                        }
                    });
                }
            }).start();
        }
    }

    @Override
    public void permitPermissions() {

    }

    @Override
    public void refusePermissions() {

    }
}
3.2.3Activity布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/compress_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:layout_marginLeft="15dp"
        android:text="终极压缩" />

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="15dp"
        android:layout_marginLeft="15dp"
        android:contentDescription="@null"
        android:scaleType="centerInside" />
</LinearLayout>

4.总结

(1)libjpeg-turbo库的so库的编译生成是通过AndroidStudio的native c++项目生成
(2)目前用的是cmake方式,即借助libjpeg-turbo本身存在的CMakeLists.txt文件去生成。
(3)只需要在app目录下的build.gradle文件中去指定CMakeLists.txt文件所在位置即可
(4)运行项目在生成so动态链接库的时候,会去读取CMakeLists.txt中的文件信息生成libjpeg-turbo的so库文件
(5)我们自己的项目需要用到libjpeg-turbo的so库时,只需要将其添加到jni目录,并且复制图片压缩相关的.h头文件,并在自己编写的扩展名为.cpp的文件中去引入这些头文件,以达到调用libjpeg-turbo函数库实现图片压缩的目的。
(6)自己写的.cpp文件(C或C++)函数希望能在Android Activity或其他java程序中调用,需要将其生成so动态链接库,然后通过JNI去调用。
(7)生成so动态链接库的过程,可以ndk来实现,主要是在JNI目录中添加Android.mk与Application.mk文件中去写清楚编译so库需要的依赖库有哪些,比如图片压缩so库,即前述生成的jpeg.so库,还要写清楚编译的c++源文件是什么,so库最高支持到Android哪个版本,等等,也就是要告诉ndk,编译的内容有哪些,编译成哪种CPU类型的指令集。
(8)要运行Android.mk和Application.mk文件需要用到ndk-build命令。
前提是ndk必须配置系统环境变量。
首先是要打开命令提示符,并进入Android.mk文件所处的目录,然后再执行ndk-build命令。

4.1注意:

(1)在执行ndk-build命令之前,需要将不同CPU型号指令库,即jpeg.so库复制到jni目录下,去生成对应的CPU型号的so库,不然可能在生成so库的过程中会出问题。
(2)这句话的意思就是
arm-v8a的jpeg.so库复制到jni目录生成arm-v8a的zipimg.so库。
armeabi-v7a的jpeg.so库复制到jni目录生成armeabi-v7a的zipimg.so库。
x86的jpeg.so库复制到jni目录生成x86的zipimg.so库。
x86_64的jpeg.so库复制到jni目录生成x86_64的zipimg.so库。
(3)主要目的就是为了避免不同CPU生成的指令不一样,导致程序无法运行的问题。因此编译之前一定要在Application.mk中设置编译的CPU类型名称,arm64-v8a、armeabi-v7a、x86、x86_64,可以一个一个的设置,然后编译。

5.参考文档

参考文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2254752.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue.js学习(day 18)

实例&#xff1a;面经基础版 (功能)组件缓存 keep-alive 小结 main.js import Vue from vue import App from ./App.vue import router from ./routerVue.config.productionTip falsenew Vue({ //注入,将路由对象注入到new Vue实例中&#xff0c;建立关联render: h > …

Android 使用 Canvas 和 Paint 实现圆形图片

学习笔记 效果展示: 全部代码: public class YuanActivity extends AppCompatActivity {private ActivityYuanBinding binding;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 通过 DataBinding 获取布局文件binding …

鸿蒙DevEco Profiler无法识别设备

一、问题 DevEco Studio运行项目处可以识别到设备信息&#xff0c;但是Profiler工具无法识别 二、背景知识 注意 DevEco Profiler工具不支持模拟器进行调优。macOS 12及以上系统版本支持使用DevEco Profiler工具。 知识来源&#xff1a;文档中心 三、解决方案 重启DevEco …

微信小程序版小米商城的搭建流程详解!

很多初学微信小程序语法的同学&#xff0c;可能不知道如何布局和搭建一个项目&#xff0c;下面我将讲解初学者如何搭建项目和注意事项。 目录 一、 app.json的配置 二、引入vant 三、主页banner携带参数跳转 四、点击商品项跳转详情页 一、 app.json的配置 {"pages&q…

第30天:安全开发-JS 应用NodeJS 指南原型链污染Express 框架功能实现审计0

时间轴&#xff1a; 演示案例&#xff1a; 环境搭建-NodeJS-解析安装&库安装 功能实现-NodeJS-数据库&文件&执行 安全问题-NodeJS-注入&RCE&原型链 案例分析-NodeJS-CTF 题目&源码审计 开发指南-NodeJS-安全 SecGuide 项目、 环境搭建-NodeJ…

LongVU:用于长视频语言理解的空间时间自适应压缩

晚上闲暇时间看到一种用于长视频语言理解的空间时间自适应压缩机制的研究工作LongVU&#xff0c;主要内容包括&#xff1a; 背景与挑战&#xff1a;多模态大语言模型&#xff08;MLLMs&#xff09;在视频理解和分析方面取得了进展&#xff0c;但处理长视频仍受限于LLM的上下文长…

Observability:用 OpenTelemetry 自动检测 Python 应用程序

作者&#xff1a;来自 Elastic Bahubali Shetti 了解如何使用 OpenTelemetry 自动检测 Python 应用程序。使用 Docker 文件中的标准命令&#xff0c;可以快速检测应用程序&#xff0c;而无需在多个位置编写代码&#xff0c;从而实现快速更改、扩展和更轻松的管理。 更多阅读&a…

JVM, JRE 和 JDK

JRE: Java Runtime Environment, Java 运行环境. JDK: Java Development Kit, Java 开发工具包. JRE JVM 核心类库 运行工具 JDK JVM 核心类库 开发工具 JVM: Java Virtual Machine, Java 虚拟机. 核心类库: Java 已经写好的东西, 直接拿来用即可. 开发工具: 包括 …

Ubuntu 22.04.5 + kubeadm:Kubernetes v1.28.2集群部署企业实战

文章目录 Ubuntu 22.04.5 kubeadm&#xff1a;Kubernetes v1.28.2集群部署企业实战一、环境准备1.1 机器规划1.2 环境配置1.2.1 设置主机名1.2.2 安装依赖工具1.2.3 配置时间同步1.2.4 关闭swap分区1.2.5 停止和禁用防火墙1.2.6 配置内核转发及网桥过滤1.2.7 安装配置ipset及i…

Mac环境下brew安装LNMP

安装不同版本PHP 在Mac环境下同时运行多个版本的PHP&#xff0c;同Linux环境一样&#xff0c;都是将后台运行的php-fpm设置为不同的端口号&#xff0c;下面将已php7.2 和 php7.4为例 添加 tap 目的&#xff1a;homebrew仅保留最近的php版本&#xff0c;可能没有你需要的版本…

【Python网络爬虫笔记】8- (BeautifulSoup)抓取电影天堂2024年最新电影,并保存所有电影名称和链接

目录 一. BeautifulSoup的作用二. 核心方法介绍2.1 构造函数2.2 find()方法2.3 find_all()方法2.4 select()方法 三. 网络爬虫中使用BeautifulSoup四、案例爬取结果 一. BeautifulSoup的作用 解析HTML/XML文档&#xff1a;它可以将复杂的HTML或XML文本转换为易于操作的树形结构…

解决LED显示屏水波纹的方法

在拍摄LED显示屏时&#xff0c;水波纹和扫描线的出现常常让人误以为是显示屏质量问题&#xff0c;但实际上这些现象往往与拍摄角度和焦距有关。本文将探讨水波纹的成因&#xff0c;并提供一些有效的解决方法。 1. 水波纹与扫描线现象的区别 水波纹通常呈现无规则的弧形扩散状态…

【MySQL — 数据库基础】深入理解数据库服务与数据库关系、MySQL连接创建、客户端工具及架构解析

目录 1. 数据库服务&#xff06;数据库&#xff06;表之间的关系 1.1 复习 my.ini 1.2 MYSQL服务基于mysqld启动而启动 1.3 数据库服务的具体含义 1.4 数据库服务&数据库&表之间的关系 2. 客户端工具 2.1 客户端连接MySQL服务器 2.2 客…

记录关于阿里云智能媒体预览pdf文件的问题

pdf仅支持预览&#xff0c;不支持编辑&#xff0c;需要将权限设置成只读。 readonly参数一定要传&#xff0c;不能不传&#xff01;&#xff01;&#xff01;&#xff01; readonly的设置一定要用示例提供的方法&#xff01;&#xff01;&#xff01;&#xff01; 用WebofficeP…

FlyHttp 的设计思想:前端 API 自动化构建工具

FlyHttp的相关文章&#xff1a; FlyHttp 的诞生&#xff1a;从认识各种网络请求开始 FlyHttp 的设计思想&#xff1a;前端 API 自动化构建工具 FlyHttp 的使用&#xff1a;如何高效使用 FlyHttp&#xff0c;支持 JS、TS 项目 FlyHttp 的最佳实践&#xff1a;加速项目级 API…

WHLUG丨deepin、华中科技大学开放原子开源俱乐部、 RustSBI 和清华大学开源操作系统训练营共话开源新生代成长之路

2024年11月30日下午&#xff0c;由 deepin&#xff08;深度&#xff09;社区联合华中科技大学开放原子开源俱乐部、 RustSBI 开源社区和清华大学开源操作系统训练营共同举办的WHLUG&#xff08;武汉Linux用户组&#xff09;线下沙龙在华中科技大学成功举办。 本次活动聚集了50余…

K8S离线部署Nacos集群【Oracle作外部数据源】

一、前言 由于公司的要求下要使Nacos集群以Oracle作为外部数据源&#xff0c;前期咱们已经阐述了如何在本地搭建&#xff08;Nacos集群搭建【Oracle作外部数据源】&#xff09;&#xff0c;本次将带领大家在k8s上部署Nacos集群并以Oracle作为外部数据源。 二、软件包 nacos-f…

MperReduce学习笔记下

自定义InputFormat合并小文件 案例需求 无论hdfs还是mapreduce&#xff0c;对于小文件都有损效率&#xff0c;实践中&#xff0c;又难免面临处理大量小文件的场景&#xff0c;此时&#xff0c;就需要有相应解决方案。 案例分析 小文件的优化无非以下几种方式&#xff1a; …

Junit5 单元测试入门

基础知识 常用注解含义 Test&#xff1a;标记一个方法为测试方法BeforeEach&#xff1a;标记的方法会在每个测试方法执行前执行AfterEach&#xff1a;标记的方法会在每个测试方法执行后执行BeforeAll&#xff1a;标记的方法会在所有测试方法执行前执行一次AfterAll&#xff1…

【CSS in Depth 2 精译_065】第四部分:CSS 视觉增强技术 + 第 11 章 颜色与对比概述 + 11.1 通过对比进行交流

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第四部分 视觉增强技术 ✔️【第 11 章 颜色与对比】 ✔️ 11.1 通过对比进行交流 ✔️ 11.1.1 模式的建立 ✔️11.1.2 还原设计稿 ✔️ 11.2 颜色的定义 文章目录 第四部分 视觉增强技术 Visual e…