zl程序教程

您现在的位置是:首页 >  移动开发

当前栏目

我的Android进阶之旅------>使用ThumbnailUtils类获取视频的缩略图

Androidamp 获取 视频 之旅 进阶 gt ------&
2023-09-27 14:29:23 时间


今天看了一段代码,是关于获取视频的缩略图的,让我认识了一个ThumbnailUtils类,代码如下。

Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(path, Thumbnails.MINI_KIND); //用于获取视频的缩略图

BitmapDrawable background=new BitmapDrawable(bitmap);


通过察看android.media.ThumbnailUtils的源码,可以发现该类提供了三种静态方法可以直接调用获取,从而可以帮助我们获取系统视频或图片文件的缩略图。


第一个方法可以用来创建一张视频的缩略图,如果视频已损坏或者格式不支持可能返回null

 /**

 * Create a video thumbnail for a video. May return null if the video is

 * corrupt or the format is not supported.

 * @param filePath the path of video file

 * @param kind could be MINI_KIND or MICRO_KIND

 public static Bitmap createVideoThumbnail(String filePath, int kind) {

 Bitmap bitmap = null;

 MediaMetadataRetriever retriever = new MediaMetadataRetriever();

 try {

 retriever.setDataSource(filePath);

 bitmap = retriever.getFrameAtTime(-1);

 } catch (IllegalArgumentException ex) {

 // Assume this is a corrupt video file

 } catch (RuntimeException ex) {

 // Assume this is a corrupt video file.

 } finally {

 try {

 retriever.release();

 } catch (RuntimeException ex) {

 // Ignore failures while cleaning up.

 if (bitmap == null) return null;

 if (kind == Images.Thumbnails.MINI_KIND) {

 // Scale down the bitmap if its too large.

 int width = bitmap.getWidth();

 int height = bitmap.getHeight();

 int max = Math.max(width, height);

 if (max 512) {

 float scale = 512f / max;

 int w = Math.round(scale * width);

 int h = Math.round(scale * height);

 bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);

 } else if (kind == Images.Thumbnails.MICRO_KIND) {

 bitmap = extractThumbnail(bitmap,

 TARGET_SIZE_MICRO_THUMBNAIL,

 TARGET_SIZE_MICRO_THUMBNAIL,

 OPTIONS_RECYCLE_INPUT);

 return bitmap;



参数说明:


kind表示类型,可以有两个选项,分别是Images.Thumbnails.MICRO_KIND和Images.Thumbnails.MINI_KIND,其中,MINI_KIND: 512 x 384,MICRO_KIND: 96 x 96,当然读了代码你会发现,你也可以传入任意的int型数字,只是就不会对获取的bitmap进行相关的设置,我们可以自己使用extractThumbnail( Bitmap source, int width, int height)方法对返回的bitmap进行相关设置。

Bitmap source, int width, int height) { return extractThumbnail(source, width, height, OPTIONS_NONE); }
参数说明:


* @param height targeted height * @param options options used during thumbnail extraction public static Bitmap extractThumbnail( Bitmap source, int width, int height, int options) { if (source == null) { return null; float scale; if (source.getWidth() source.getHeight()) { scale = width / (float) source.getWidth(); } else { scale = height / (float) source.getHeight(); Matrix matrix = new Matrix(); matrix.setScale(scale, scale); Bitmap thumbnail = transform(matrix, source, width, height, OPTIONS_SCALE_UP | options); return thumbnail; }



参数说明:
options表示在缩略图抽取时提供的选项,如果options定义为OPTIONS_RECYCLE_INPUT,则回收@param source这个资源文件(除非缩略图等于@param source)

* 此方法有两点好处: * 1. 使用较小的内存空间,第一次获取的bitmap实际上为null,只是为了读取宽度和高度, * 第二次读取的bitmap是根据比例压缩过的图像,第三次读取的bitmap是所要的缩略图。 * 2. 缩略图对于原图像来讲没有拉伸,这里使用了2.2版本的新工具ThumbnailUtils,使 * 用这个工具生成的图像不会被拉伸。 * @param imagePath 图像的路径 * @param width 指定输出图像的宽度 * @param height 指定输出图像的高度 * @return 生成的缩略图 private Bitmap getImageThumbnail(String imagePath, int width, int height) { Bitmap bitmap = null; BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; // 获取这个图片的宽和高,注意此处的bitmap为null bitmap = BitmapFactory.decodeFile(imagePath, options); options.inJustDecodeBounds = false; // 设为 false // 计算缩放比 int h = options.outHeight; int w = options.outWidth; int beWidth = w / width; int beHeight = h / height; int be = 1; if (beWidth beHeight) { be = beWidth; } else { be = beHeight; if (be = 0) { be = 1; options.inSampleSize = be; // 重新读入图片,读取缩放后的bitmap,注意这次要把options.inJustDecodeBounds 设为 false bitmap = BitmapFactory.decodeFile(imagePath, options); // 利用ThumbnailUtils来创建缩略图,这里要指定要缩放哪个Bitmap对象 bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); return bitmap; /** * 获取视频的缩略图 * 先通过ThumbnailUtils来创建一个视频的缩略图,然后再利用ThumbnailUtils来生成指定大小的缩略图。 * 如果想要的缩略图的宽和高都小于MICRO_KIND,则类型要使用MICRO_KIND作为kind的值,这样会节省内存。 * @param videoPath 视频的路径 * @param width 指定输出视频缩略图的宽度 * @param height 指定输出视频缩略图的高度度 * @param kind 参照MediaStore.Images.Thumbnails类中的常量MINI_KIND和MICRO_KIND。 * 其中,MINI_KIND: 512 x 384,MICRO_KIND: 96 x 96 * @return 指定大小的视频缩略图 private Bitmap getVideoThumbnail(String videoPath, int width, int height, int kind) { Bitmap bitmap = null; // 获取视频的缩略图 bitmap = ThumbnailUtils.createVideoThumbnail(videoPath, kind); System.out.println("w"+bitmap.getWidth()); System.out.println("h"+bitmap.getHeight()); bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); return bitmap; }

   =====================华丽丽的分界线========================


最后附上ThumbnailUtils源码,代码如下:

/*

 * Copyright (C) 2009 The Android Open Source Project

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 * http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

package android.media;

import android.content.ContentResolver;

import android.content.ContentUris;

import android.content.ContentValues;

import android.database.Cursor;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.graphics.Canvas;

import android.graphics.Matrix;

import android.graphics.Rect;

import android.media.MediaMetadataRetriever;

import android.media.MediaFile.MediaFileType;

import android.net.Uri;

import android.os.ParcelFileDescriptor;

import android.provider.BaseColumns;

import android.provider.MediaStore.Images;

import android.provider.MediaStore.Images.Thumbnails;

import android.util.Log;

import java.io.FileInputStream;

import java.io.FileDescriptor;

import java.io.IOException;

import java.io.OutputStream;

 * Thumbnail generation routines for media provider.

public class ThumbnailUtils {

 private static final String TAG = "ThumbnailUtils";

 /* Maximum pixels size for created bitmap. */

 private static final int MAX_NUM_PIXELS_THUMBNAIL = 512 * 384;

 private static final int MAX_NUM_PIXELS_MICRO_THUMBNAIL = 128 * 128;

 private static final int UNCONSTRAINED = -1;

 /* Options used internally. */

 private static final int OPTIONS_NONE = 0x0;

 private static final int OPTIONS_SCALE_UP = 0x1;

 * Constant used to indicate we should recycle the input in

 * {@link #extractThumbnail(Bitmap, int, int, int)} unless the output is the input.

 public static final int OPTIONS_RECYCLE_INPUT = 0x2;

 * Constant used to indicate the dimension of mini thumbnail.

 * @hide Only used by media framework and media provider internally.

 public static final int TARGET_SIZE_MINI_THUMBNAIL = 320;

 * Constant used to indicate the dimension of micro thumbnail.

 * @hide Only used by media framework and media provider internally.

 public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96;

 * This method first examines if the thumbnail embedded in EXIF is bigger than our target

 * size. If not, then itll create a thumbnail from original image. Due to efficiency

 * consideration, we want to let MediaThumbRequest avoid calling this method twice for

 * both kinds, so it only requests for MICRO_KIND and set saveImage to true.

 * This method always returns a "square thumbnail" for MICRO_KIND thumbnail.

 * @param filePath the path of image file

 * @param kind could be MINI_KIND or MICRO_KIND

 * @return Bitmap, or null on failures

 * @hide This method is only used by media framework and media provider internally.

 public static Bitmap createImageThumbnail(String filePath, int kind) {

 boolean wantMini = (kind == Images.Thumbnails.MINI_KIND);

 int targetSize = wantMini

 ? TARGET_SIZE_MINI_THUMBNAIL

 : TARGET_SIZE_MICRO_THUMBNAIL;

 int maxPixels = wantMini

 ? MAX_NUM_PIXELS_THUMBNAIL

 : MAX_NUM_PIXELS_MICRO_THUMBNAIL;

 SizedThumbnailBitmap sizedThumbnailBitmap = new SizedThumbnailBitmap();

 Bitmap bitmap = null;

 MediaFileType fileType = MediaFile.getFileType(filePath);

 if (fileType != null fileType.fileType == MediaFile.FILE_TYPE_JPEG) {

 createThumbnailFromEXIF(filePath, targetSize, maxPixels, sizedThumbnailBitmap);

 bitmap = sizedThumbnailBitmap.mBitmap;

 if (bitmap == null) {

 FileInputStream stream = null;

 try {

 stream = new FileInputStream(filePath);

 FileDescriptor fd = stream.getFD();

 BitmapFactory.Options options = new BitmapFactory.Options();

 options.inSampleSize = 1;

 options.inJustDecodeBounds = true;

 BitmapFactory.decodeFileDescriptor(fd, null, options);

 if (options.mCancel || options.outWidth == -1

 || options.outHeight == -1) {

 return null;

 options.inSampleSize = computeSampleSize(

 options, targetSize, maxPixels);

 options.inJustDecodeBounds = false;

 options.inDither = false;

 options.inPreferredConfig = Bitmap.Config.ARGB_8888;

 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);

 } catch (IOException ex) {

 Log.e(TAG, "", ex);

 } catch (OutOfMemoryError oom) {

 Log.e(TAG, "Unable to decode file " + filePath + ". OutOfMemoryError.", oom);

 } finally {

 try {

 if (stream != null) {

 stream.close();

 } catch (IOException ex) {

 Log.e(TAG, "", ex);

 if (kind == Images.Thumbnails.MICRO_KIND) {

 // now we make it a "square thumbnail" for MICRO_KIND thumbnail

 bitmap = extractThumbnail(bitmap,

 TARGET_SIZE_MICRO_THUMBNAIL,

 TARGET_SIZE_MICRO_THUMBNAIL, OPTIONS_RECYCLE_INPUT);

 return bitmap;

 * Create a video thumbnail for a video. May return null if the video is

 * corrupt or the format is not supported.

 * @param filePath the path of video file

 * @param kind could be MINI_KIND or MICRO_KIND

 public static Bitmap createVideoThumbnail(String filePath, int kind) {

 Bitmap bitmap = null;

 MediaMetadataRetriever retriever = new MediaMetadataRetriever();

 try {

 retriever.setDataSource(filePath);

 bitmap = retriever.getFrameAtTime(-1);

 } catch (IllegalArgumentException ex) {

 // Assume this is a corrupt video file

 } catch (RuntimeException ex) {

 // Assume this is a corrupt video file.

 } finally {

 try {

 retriever.release();

 } catch (RuntimeException ex) {

 // Ignore failures while cleaning up.

 if (bitmap == null) return null;

 if (kind == Images.Thumbnails.MINI_KIND) {

 // Scale down the bitmap if its too large.

 int width = bitmap.getWidth();

 int height = bitmap.getHeight();

 int max = Math.max(width, height);

 if (max 512) {

 float scale = 512f / max;

 int w = Math.round(scale * width);

 int h = Math.round(scale * height);

 bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);

 } else if (kind == Images.Thumbnails.MICRO_KIND) {

 bitmap = extractThumbnail(bitmap,

 TARGET_SIZE_MICRO_THUMBNAIL,

 TARGET_SIZE_MICRO_THUMBNAIL,

 OPTIONS_RECYCLE_INPUT);

 return bitmap;

 * Creates a centered bitmap of the desired size.

 * @param source original bitmap source

 * @param width targeted width

 * @param height targeted height

 public static Bitmap extractThumbnail(

 Bitmap source, int width, int height) {

 return extractThumbnail(source, width, height, OPTIONS_NONE);

 * Creates a centered bitmap of the desired size.

 * @param source original bitmap source

 * @param width targeted width

 * @param height targeted height

 * @param options options used during thumbnail extraction

 public static Bitmap extractThumbnail(

 Bitmap source, int width, int height, int options) {

 if (source == null) {

 return null;

 float scale;

 if (source.getWidth() source.getHeight()) {

 scale = width / (float) source.getWidth();

 } else {

 scale = height / (float) source.getHeight();

 Matrix matrix = new Matrix();

 matrix.setScale(scale, scale);

 Bitmap thumbnail = transform(matrix, source, width, height,

 OPTIONS_SCALE_UP | options);

 return thumbnail;

 * Compute the sample size as a function of minSideLength

 * and maxNumOfPixels.

 * minSideLength is used to specify that minimal width or height of a

 * bitmap.

 * maxNumOfPixels is used to specify the maximal size in pixels that is

 * tolerable in terms of memory usage.

 * The function returns a sample size based on the constraints.

 * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,

 * which indicates no care of the corresponding constraint.

 * The functions prefers returning a sample size that

 * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.

 * Also, the function rounds up the sample size to a power of 2 or multiple

 * of 8 because BitmapFactory only honors sample size this way.

 * For example, BitmapFactory downsamples an image by 2 even though the

 * request is 3. So we round up the sample size to avoid OOM.

 private static int computeSampleSize(BitmapFactory.Options options,

 int minSideLength, int maxNumOfPixels) {

 int initialSize = computeInitialSampleSize(options, minSideLength,

 maxNumOfPixels);

 int roundedSize;

 if (initialSize = 8 ) {

 roundedSize = 1;

 while (roundedSize initialSize) {

 roundedSize = 1;

 } else {

 roundedSize = (initialSize + 7) / 8 * 8;

 return roundedSize;

 private static int computeInitialSampleSize(BitmapFactory.Options options,

 int minSideLength, int maxNumOfPixels) {

 double w = options.outWidth;

 double h = options.outHeight;

 int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :

 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));

 int upperBound = (minSideLength == UNCONSTRAINED) ? 128 :

 (int) Math.min(Math.floor(w / minSideLength),

 Math.floor(h / minSideLength));

 if (upperBound lowerBound) {

 // return the larger one when there is no overlapping zone.

 return lowerBound;

 if ((maxNumOfPixels == UNCONSTRAINED) 

 (minSideLength == UNCONSTRAINED)) {

 return 1;

 } else if (minSideLength == UNCONSTRAINED) {

 return lowerBound;

 } else {

 return upperBound;

 * Make a bitmap from a given Uri, minimal side length, and maximum number of pixels.

 * The image data will be read from specified pfd if its not null, otherwise

 * a new input stream will be created using specified ContentResolver.

 * Clients are allowed to pass their own BitmapFactory.Options used for bitmap decoding. A

 * new BitmapFactory.Options will be created if options is null.

 private static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,

 Uri uri, ContentResolver cr, ParcelFileDescriptor pfd,

 BitmapFactory.Options options) {

 Bitmap b = null;

 try {

 if (pfd == null) pfd = makeInputStream(uri, cr);

 if (pfd == null) return null;

 if (options == null) options = new BitmapFactory.Options();

 FileDescriptor fd = pfd.getFileDescriptor();

 options.inSampleSize = 1;

 options.inJustDecodeBounds = true;

 BitmapFactory.decodeFileDescriptor(fd, null, options);

 if (options.mCancel || options.outWidth == -1

 || options.outHeight == -1) {

 return null;

 options.inSampleSize = computeSampleSize(

 options, minSideLength, maxNumOfPixels);

 options.inJustDecodeBounds = false;

 options.inDither = false;

 options.inPreferredConfig = Bitmap.Config.ARGB_8888;

 b = BitmapFactory.decodeFileDescriptor(fd, null, options);

 } catch (OutOfMemoryError ex) {

 Log.e(TAG, "Got oom exception ", ex);

 return null;

 } finally {

 closeSilently(pfd);

 return b;

 private static void closeSilently(ParcelFileDescriptor c) {

 if (c == null) return;

 try {

 c.close();

 } catch (Throwable t) {

 // do nothing

 private static ParcelFileDescriptor makeInputStream(

 Uri uri, ContentResolver cr) {

 try {

 return cr.openFileDescriptor(uri, "r");

 } catch (IOException ex) {

 return null;

 * Transform source Bitmap to targeted width and height.

 private static Bitmap transform(Matrix scaler,

 Bitmap source,

 int targetWidth,

 int targetHeight,

 int options) {

 boolean scaleUp = (options OPTIONS_SCALE_UP) != 0;

 boolean recycle = (options OPTIONS_RECYCLE_INPUT) != 0;

 int deltaX = source.getWidth() - targetWidth;

 int deltaY = source.getHeight() - targetHeight;

 if (!scaleUp (deltaX 0 || deltaY 0)) {

 * In this case the bitmap is smaller, at least in one dimension,

 * than the target. Transform it by placing as much of the image

 * as possible into the target and leaving the top/bottom or

 * left/right (or both) black.

 Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,

 Bitmap.Config.ARGB_8888);

 Canvas c = new Canvas(b2);

 int deltaXHalf = Math.max(0, deltaX / 2);

 int deltaYHalf = Math.max(0, deltaY / 2);

 Rect src = new Rect(

 deltaXHalf,

 deltaYHalf,

 deltaXHalf + Math.min(targetWidth, source.getWidth()),

 deltaYHalf + Math.min(targetHeight, source.getHeight()));

 int dstX = (targetWidth - src.width()) / 2;

 int dstY = (targetHeight - src.height()) / 2;

 Rect dst = new Rect(

 dstX,

 dstY,

 targetWidth - dstX,

 targetHeight - dstY);

 c.drawBitmap(source, src, dst, null);

 if (recycle) {

 source.recycle();

 c.setBitmap(null);

 return b2;

 float bitmapWidthF = source.getWidth();

 float bitmapHeightF = source.getHeight();

 float bitmapAspect = bitmapWidthF / bitmapHeightF;

 float viewAspect = (float) targetWidth / targetHeight;

 if (bitmapAspect viewAspect) {

 float scale = targetHeight / bitmapHeightF;

 if (scale .9F || scale 1F) {

 scaler.setScale(scale, scale);

 } else {

 scaler = null;

 } else {

 float scale = targetWidth / bitmapWidthF;

 if (scale .9F || scale 1F) {

 scaler.setScale(scale, scale);

 } else {

 scaler = null;

 Bitmap b1;

 if (scaler != null) {

 // this is used for minithumb and crop, so we want to filter here.

 b1 = Bitmap.createBitmap(source, 0, 0,

 source.getWidth(), source.getHeight(), scaler, true);

 } else {

 b1 = source;

 if (recycle b1 != source) {

 source.recycle();

 int dx1 = Math.max(0, b1.getWidth() - targetWidth);

 int dy1 = Math.max(0, b1.getHeight() - targetHeight);

 Bitmap b2 = Bitmap.createBitmap(

 dx1 / 2,

 dy1 / 2,

 targetWidth,

 targetHeight);

 if (b2 != b1) {

 if (recycle || b1 != source) {

 b1.recycle();

 return b2;

 * SizedThumbnailBitmap contains the bitmap, which is downsampled either from

 * the thumbnail in exif or the full image.

 * mThumbnailData, mThumbnailWidth and mThumbnailHeight are set together only if mThumbnail

 * is not null.

 * The width/height of the sized bitmap may be different from mThumbnailWidth/mThumbnailHeight.

 private static class SizedThumbnailBitmap {

 public byte[] mThumbnailData;

 public Bitmap mBitmap;

 public int mThumbnailWidth;

 public int mThumbnailHeight;

 * Creates a bitmap by either downsampling from the thumbnail in EXIF or the full image.

 * The functions returns a SizedThumbnailBitmap,

 * which contains a downsampled bitmap and the thumbnail data in EXIF if exists.

 private static void createThumbnailFromEXIF(String filePath, int targetSize,

 int maxPixels, SizedThumbnailBitmap sizedThumbBitmap) {

 if (filePath == null) return;

 ExifInterface exif = null;

 byte [] thumbData = null;

 try {

 exif = new ExifInterface(filePath);

 thumbData = exif.getThumbnail();

 } catch (IOException ex) {

 Log.w(TAG, ex);

 BitmapFactory.Options fullOptions = new BitmapFactory.Options();

 BitmapFactory.Options exifOptions = new BitmapFactory.Options();

 int exifThumbWidth = 0;

 int fullThumbWidth = 0;

 // Compute exifThumbWidth.

 if (thumbData != null) {

 exifOptions.inJustDecodeBounds = true;

 BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, exifOptions);

 exifOptions.inSampleSize = computeSampleSize(exifOptions, targetSize, maxPixels);

 exifThumbWidth = exifOptions.outWidth / exifOptions.inSampleSize;

 // Compute fullThumbWidth.

 fullOptions.inJustDecodeBounds = true;

 BitmapFactory.decodeFile(filePath, fullOptions);

 fullOptions.inSampleSize = computeSampleSize(fullOptions, targetSize, maxPixels);

 fullThumbWidth = fullOptions.outWidth / fullOptions.inSampleSize;

 // Choose the larger thumbnail as the returning sizedThumbBitmap.

 if (thumbData != null exifThumbWidth = fullThumbWidth) {

 int width = exifOptions.outWidth;

 int height = exifOptions.outHeight;

 exifOptions.inJustDecodeBounds = false;

 sizedThumbBitmap.mBitmap = BitmapFactory.decodeByteArray(thumbData, 0,

 thumbData.length, exifOptions);

 if (sizedThumbBitmap.mBitmap != null) {

 sizedThumbBitmap.mThumbnailData = thumbData;

 sizedThumbBitmap.mThumbnailWidth = width;

 sizedThumbBitmap.mThumbnailHeight = height;

 } else {

 fullOptions.inJustDecodeBounds = false;

 sizedThumbBitmap.mBitmap = BitmapFactory.decodeFile(filePath, fullOptions);


====================================================================================

  作者:欧阳鹏  欢迎转载,与人分享是进步的源泉!

  转载请保留原文地址:http://blog.csdn.net/ouyang_peng

===================================================================================


Android C++系列:JNI调用 Java 类的构造方法和父类的方法 Android JNI开发时经常遇到C/C++层访问Java层对象的,比如C/C++层创建一个String返回,或者访问Java层提供的MediaCodec等,此时我们就需要通过 JNI 来调用 Java 一个类的构造方法来创建这个 Java 类。
Android Studio运行报错:无法访问XXX......请删除该文件或确保该文件位于正确的类路径子目录中 今天运行一个项目发现运行不起来了,报错 错误: 无法访问XXX 错误的类文件: C:\Users\xxx\.gradle\caches\transforms-2\files-2.1\xxx\xxx(xxx/xxx/xxx.class) 错误的 RuntimeInvisibleParameterAnnotations 属性: xxxx 请删除该文件或确保该文件位于正确的类路径子目录中。
字节卷动 You will never know how excellent you are unless you impel yourself once.