Android Bitmap 完全指南:从基础到高级优化

在 Android 开发中,图像处理是一个核心且复杂的领域,而 Bitmap 作为 Android 中表示图像的基本单位,贯穿了从简单图片显示到复杂图像编辑的各个场景。然而,Bitmap 处理不当往往会导致应用性能下降、内存溢出(OOM)等问题,成为许多开发者的痛点。本文将从 Bitmap 的基础概念出发,全面覆盖其创建、加载、处理、优化等各个方面,结合实际案例和最佳实践,帮助开发者彻底掌握 Android Bitmap 的使用技巧。

一、Bitmap 基础概念

1.1 什么是 Bitmap

Bitmap(位图)是一种将图像像素化的存储格式,它通过记录图像中每个像素的颜色信息来精确表示图像。在 Android 中,android.graphics.Bitmap类是处理位图的核心类,负责管理图像数据和提供各种图像处理方法。

与矢量图(Vector)相比,Bitmap 具有以下特点:

  • 优点:能够精确表示复杂图像细节,渲染速度快
  • 缺点:放大后会失真,文件体积和内存占用通常较大
  • 适用场景:照片、复杂图像、需要像素级操作的场景

在 Android 系统中,Bitmap 广泛应用于:

  • 界面元素(图标、背景、按钮等)
  • 图片展示(相册、社交应用、电商商品图等)
  • 图像编辑(裁剪、滤镜、涂鸦等)
  • 自定义控件绘制

1.2 Bitmap 的内部结构

理解 Bitmap 的内部结构对于优化其内存占用至关重要。一张 Bitmap 图像由以下几个关键部分组成:

1.像素数据(Pixel Data):这是 Bitmap 占用内存的主要部分,存储了每个像素的颜色信息。

2.宽度和高度(Width & Height):以像素为单位的图像尺寸,直接影响内存占用。

3.像素格式(Pixel Format):决定每个像素占用的字节数,常见格式包括:

  • ARGB_8888:每个像素占 4 字节(Alpha、Red、Green、Blue 各 8 位),画质最佳
  • RGB_565:每个像素占 2 字节(Red 5 位、Green 6 位、Blue 5 位),无透明度
  • ARGB_4444:每个像素占 2 字节,画质较差,已不推荐使用
  • ALPHA_8:仅存储透明度,每个像素占 1 字节
  1. 密度(Density):图像的像素密度(dpi),影响在不同密度屏幕上的显示尺寸。
  2. 配置信息:包括是否有 mipmap、是否可修改等属性。

示例:计算 Bitmap 内存占用

Bitmap 的内存占用可以通过以下公式计算:

内存大小 = 宽度 × 高度 × 每个像素占用的字节数

以一张 1920×1080 的图片为例:

  • 使用ARGB_8888格式:1920 × 1080 × 4 = 8,294,400 字节 ≈ 8MB
  • 使用RGB_565格式:1920 × 1080 × 2 = 4,147,200 字节 ≈ 4MB

这意味着一张高清图片可能轻易占用数 MB 内存,当同时加载多张图片时,很容易触发 OOM。

1.3 Android 中 Bitmap 的内存管理变迁

Android 系统对 Bitmap 内存的管理方式随着版本迭代发生过重要变化,了解这些变化有助于更好地进行内存优化:

1.Android 2.2 及之前(API ≤ 8)

  • Bitmap 的像素数据存储在 native 内存中
  • 回收时机不确定,可能导致 native 内存泄漏

2.Android 3.0 到 Android 7.0(API 9 - 24)

  • 像素数据移至 Java 堆内存
  • 可通过Bitmap.recycle()主动释放内存
  • 受 Java GC 管理,降低了内存泄漏风险,但增加了 Java 堆压力

3.Android 8.0 及之后(API ≥ 26)

  • 像素数据又回到 native 内存,但由 Bitmap 对象在 Java 堆中持有引用
  • 当 Bitmap 对象被 GC 回收时,native 内存会自动释放
  • 无需手动调用recycle(),系统管理更智能

这种变迁反映了 Android 系统在 Bitmap 内存管理上的不断优化,也要求开发者根据目标版本调整内存管理策略。

二、Bitmap 的创建与加载

2.1 从资源文件加载 Bitmap

从应用的资源文件(res/drawable、res/mipmap 等)加载 Bitmap 是最常见的场景之一。Android 提供了BitmapFactory类来简化这一过程。

基本用法

// 从资源文件加载Bitmap
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.image)// 显示到ImageView
imageView.setImageBitmap(bitmap)

进阶用法:使用 Options 控制加载

BitmapFactory.Options类提供了丰富的参数来控制 Bitmap 的加载过程,是优化内存占用的关键:

val options = BitmapFactory.Options().apply {// 仅获取图像尺寸,不加载像素数据inJustDecodeBounds = true// 先解码一次获取尺寸BitmapFactory.decodeResource(resources, R.drawable.large_image, this)// 计算采样率(见2.5节)inSampleSize = calculateInSampleSize(this, targetWidth, targetHeight)// 现在真正加载图像inJustDecodeBounds = false// 设置像素格式(降低内存占用)inPreferredConfig = Bitmap.Config.RGB_565// 根据设备密度调整inDensity = resources.displayMetrics.densityDpiinTargetDensity = imageView.resources.displayMetrics.densityDpiinScaled = true
}val optimizedBitmap = BitmapFactory.decodeResource(resources, R.drawable.large_image, options)

注意事项

  • 不同 drawable 目录(如 drawable-hdpi、drawable-xhdpi)会根据设备密度自动缩放图像
  • 尽量将图片放在合适密度的目录,避免系统自动缩放导致的内存浪费
  • 对于大型图片,务必使用inSampleSize降低采样率

2.2 从文件加载 Bitmap

从本地文件系统加载 Bitmap(如相机拍摄的照片)也是常见需求:

// 从文件路径加载
val file = File(Environment.getExternalStorageDirectory(), "photo.jpg")
val bitmap = BitmapFactory.decodeFile(file.absolutePath)// 带选项的加载
val options = BitmapFactory.Options().apply {inPreferredConfig = Bitmap.Config.ARGB_8888inSampleSize = 2 // 1/2尺寸加载
}
val optimizedBitmap = BitmapFactory.decodeFile(file.absolutePath, options)

从输入流加载

// 从输入流加载(如文件输入流、网络输入流)
val inputStream = FileInputStream(file)
val bitmap = BitmapFactory.decodeStream(inputStream)
inputStream.close() // 记得关闭流

注意事项

  • 从外部存储加载需要申请READ_EXTERNAL_STORAGE权限(Android 10 之前)
  • Android 10 及以上推荐使用MediaStore API 访问媒体文件
  • 始终记得关闭输入流,避免资源泄漏

2.3 从网络加载 Bitmap

从网络加载图片是现代应用的常见功能,通常需要结合异步处理:

// 简单实现(实际项目建议使用Glide等库)
fun loadBitmapFromNetwork(url: String, imageView: ImageView) {// 在后台线程执行CoroutineScope(Dispatchers.IO).launch {try {val connection = URL(url).openConnection() as HttpURLConnectionconnection.doInput = trueconnection.connect()val inputStream = connection.inputStream// 解码Bitmapval bitmap = BitmapFactory.decodeStream(inputStream)inputStream.close()connection.disconnect()// 在主线程更新UIwithContext(Dispatchers.Main) {imageView.setImageBitmap(bitmap)}} catch (e: Exception) {e.printStackTrace()}}
}

注意事项

  • 网络操作必须在后台线程执行,避免阻塞主线程
  • 需要申请INTERNET权限
  • 简单实现缺乏缓存、错误处理等功能,实际项目建议使用成熟库
  • 大图片需要设置合理的inSampleSize

2.4 创建空白 Bitmap

有时需要创建空白 Bitmap 进行自定义绘制:

// 创建指定尺寸和格式的空白Bitmap
val width = 500
val height = 500
val config = Bitmap.Config.ARGB_8888
val blankBitmap = Bitmap.createBitmap(width, height, config)// 从现有Bitmap创建新Bitmap(共享像素数据)
val mutableBitmap = blankBitmap.copy(Bitmap.Config.ARGB_8888, true) // true表示可修改

使用 Canvas 绘制

// 创建可绘制的Bitmap
val bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap) // 将Bitmap与Canvas关联// 使用Canvas绘制
val paint = Paint().apply {color = Color.REDstyle = Paint.Style.FILL
}
canvas.drawCircle(200f, 200f, 100f, paint) // 绘制圆形// 显示结果
imageView.setImageBitmap(bitmap)

2.5 采样率(inSampleSize)计算

inSampleSize是控制 Bitmap 内存占用的关键参数,它表示图像的缩放比例:

  • inSampleSize = 1:原始尺寸加载
  • inSampleSize = 2:宽高各为原来的 1/2,像素数为 1/4,内存为 1/4
  • 取值必须是 2 的幂次方(Android 会自动向下取最接近的 2 的幂次方)

计算合适的采样率

/*** 计算合适的采样率* @param options 包含原始图像尺寸的Options* @param reqWidth 目标宽度* @param reqHeight 目标高度* @return 计算得到的采样率*/
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {// 原始图像尺寸val height = options.outHeightval width = options.outWidthvar inSampleSize = 1// 如果原始尺寸大于目标尺寸,计算采样率if (height > reqHeight || width > reqWidth) {val halfHeight = height / 2val halfWidth = width / 2// 找到最大的inSampleSize,使采样后的尺寸不小于目标尺寸while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {inSampleSize *= 2}}return inSampleSize
}

使用示例

// 加载一张适合ImageView尺寸的图片
val options = BitmapFactory.Options().apply {inJustDecodeBounds = trueBitmapFactory.decodeResource(resources, R.drawable.large_image, this)// 目标尺寸设为ImageView的尺寸val targetWidth = imageView.widthval targetHeight = imageView.height// 计算采样率inSampleSize = calculateInSampleSize(this, targetWidth, targetHeight)inJustDecodeBounds = false
}val bitmap = BitmapFactory.decodeResource(resources, R.drawable.large_image, options)

注意:imageView.width在布局未完成时可能为 0,此时需要使用其他方式获取目标尺寸(如预设尺寸或屏幕尺寸)。

三、Bitmap 的处理与操作

3.1 缩放 Bitmap

除了加载时通过采样率缩放,还可以在运行时对已加载的 Bitmap 进行缩放:

/*** 缩放Bitmap到指定尺寸* @param bitmap 原始Bitmap* @param newWidth 新宽度* @param newHeight 新高度* @return 缩放后的Bitmap*/
fun scaleBitmap(bitmap: Bitmap, newWidth: Int, newHeight: Int): Bitmap {// 计算缩放比例val scaleWidth = newWidth.toFloat() / bitmap.widthval scaleHeight = newHeight.toFloat() / bitmap.height// 创建矩阵用于缩放val matrix = Matrix()matrix.postScale(scaleWidth, scaleHeight)// 进行缩放return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}

按比例缩放

/*** 按比例缩放Bitmap* @param bitmap 原始Bitmap* @param scale 缩放比例(0.5f表示缩小到1/2)* @return 缩放后的Bitmap*/
fun scaleBitmap(bitmap: Bitmap, scale: Float): Bitmap {return Bitmap.createScaledBitmap(bitmap, (bitmap.width * scale).toInt(), (bitmap.height * scale).toInt(), true // 是否使用双线性过滤,使缩放更平滑)
}

注意

  • 缩放操作会创建新的 Bitmap 对象,原始 Bitmap 需要手动回收
  • 缩放是耗时操作,应在后台线程执行
  • createScaledBitmap比使用 Matrix 更简单,但灵活性较低

3.2 裁剪 Bitmap

裁剪 Bitmap 可以提取图像的特定区域:

/*** 裁剪Bitmap的指定区域* @param bitmap 原始Bitmap* @param x 起始X坐标* @param y 起始Y坐标* @param width 裁剪宽度* @param height 裁剪高度* @return 裁剪后的Bitmap*/
fun cropBitmap(bitmap: Bitmap, x: Int, y: Int, width: Int, height: Int): Bitmap {// 确保裁剪区域在Bitmap范围内val safeX = x.coerceIn(0, bitmap.width)val safeY = y.coerceIn(0, bitmap.height)val safeWidth = width.coerceIn(0, bitmap.width - safeX)val safeHeight = height.coerceIn(0, bitmap.height - safeY)return Bitmap.createBitmap(bitmap, safeX, safeY, safeWidth, safeHeight)
}

示例:裁剪中心区域

/*** 裁剪Bitmap的中心正方形区域*/
fun cropCenterSquare(bitmap: Bitmap): Bitmap {val size = minOf(bitmap.width, bitmap.height)val x = (bitmap.width - size) / 2val y = (bitmap.height - size) / 2return cropBitmap(bitmap, x, y, size, size)
}

3.3 旋转与翻转

使用 Matrix 可以实现 Bitmap 的旋转和翻转:

/*** 旋转Bitmap* @param bitmap 原始Bitmap* @param degrees 旋转角度(顺时针)* @return 旋转后的Bitmap*/
fun rotateBitmap(bitmap: Bitmap, degrees: Float): Bitmap {val matrix = Matrix()matrix.postRotate(degrees)return Bitmap.createBitmap(bitmap, 0, 0,bitmap.width, bitmap.height,matrix, true)
}/*** 水平翻转Bitmap*/
fun flipHorizontal(bitmap: Bitmap): Bitmap {val matrix = Matrix()matrix.postScale(-1f, 1f) // 水平翻转return Bitmap.createBitmap(bitmap, 0, 0,bitmap.width, bitmap.height,matrix, true)
}/*** 垂直翻转Bitmap*/
fun flipVertical(bitmap: Bitmap): Bitmap {val matrix = Matrix()matrix.postScale(1f, -1f) // 垂直翻转return Bitmap.createBitmap(bitmap, 0, 0,bitmap.width, bitmap.height,matrix, true)
}

注意:旋转操作可能会改变 Bitmap 的宽高(如旋转 90 度或 270 度),需要注意后续处理。

3.4 颜色处理与滤镜

通过ColorMatrix可以实现各种颜色滤镜效果:

/*** 应用灰度滤镜*/
fun applyGrayscaleFilter(bitmap: Bitmap): Bitmap {// 创建可修改的Bitmapval result = bitmap.copy(Bitmap.Config.ARGB_8888, true)val canvas = Canvas(result)// 创建灰度颜色矩阵val colorMatrix = ColorMatrix().apply {setSaturation(0f) // 饱和度为0即灰度}// 创建画笔并设置颜色滤镜val paint = Paint().apply {colorFilter = ColorMatrixColorFilter(colorMatrix)}// 应用滤镜canvas.drawBitmap(result, 0f, 0f, paint)return result
}/*** 调整亮度* @param brightness 亮度值(-255到255)*/
fun adjustBrightness(bitmap: Bitmap, brightness: Int): Bitmap {val result = bitmap.copy(Bitmap.Config.ARGB_8888, true)val canvas = Canvas(result)val colorMatrix = ColorMatrix().apply {set(floatArrayOf(1f, 0f, 0f, 0f, brightness.toFloat(),0f, 1f, 0f, 0f, brightness.toFloat(),0f, 0f, 1f, 0f, brightness.toFloat(),0f, 0f, 0f, 1f, 0f))}val paint = Paint().apply {colorFilter = ColorMatrixColorFilter(colorMatrix)}canvas.drawBitmap(result, 0f, 0f, paint)return result
}

使用 PorterDuff 混合模式

/*** 应用颜色叠加效果*/
fun applyColorOverlay(bitmap: Bitmap, color: Int, alpha: Int): Bitmap {val result = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)val canvas = Canvas(result)// 绘制原始图像canvas.drawBitmap(bitmap, 0f, 0f, null)// 创建叠加画笔val paint = Paint().apply {this.color = colorthis.alpha = alphaxfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP) // 叠加模式}// 绘制叠加颜色canvas.drawRect(0f, 0f, bitmap.width.toFloat(), bitmap.height.toFloat(), paint)return result
}

3.5 合成与水印

将多张 Bitmap 合成一张,或添加水印:

/*** 给Bitmap添加文字水印*/
fun addTextWatermark(bitmap: Bitmap, text: String): Bitmap {val result = bitmap.copy(Bitmap.Config.ARGB_8888, true)val canvas = Canvas(result)// 创建文字画笔val paint = Paint().apply {color = Color.WHITEtextSize = 48falpha = 128 // 半透明typeface = Typeface.DEFAULT_BOLDisAntiAlias = true // 抗锯齿}// 计算文字位置(右下角)val textWidth = paint.measureText(text)val x = result.width - textWidth - 20val y = result.height - 40f// 绘制文字阴影paint.color = Color.BLACKcanvas.drawText(text, x + 2, y + 2, paint)// 绘制文字paint.color = Color.WHITEcanvas.drawText(text, x, y, paint)return result
}/*** 合并两张Bitmap(底部图和顶部图)*/
fun mergeBitmaps(base: Bitmap, overlay: Bitmap, x: Int, y: Int): Bitmap {val result = base.copy(Bitmap.Config.ARGB_8888, true)val canvas = Canvas(result)// 在指定位置绘制叠加图canvas.drawBitmap(overlay, x.toFloat(), y.toFloat(), null)return result
}

3.6 保存 Bitmap 到文件

将处理后的 Bitmap 保存到存储设备:

/*** 保存Bitmap到文件* @param bitmap 要保存的Bitmap* @param file 目标文件* @param format 保存格式(JPEG或PNG)* @param quality 质量(0-100,仅对JPEG有效)* @return 是否保存成功*/
fun saveBitmapToFile(bitmap: Bitmap,file: File,format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,quality: Int = 90
): Boolean {if (quality < 0 || quality > 100) {throw IllegalArgumentException("Quality must be between 0 and 100")}var out: OutputStream? = nulltry {out = FileOutputStream(file)return bitmap.compress(format, quality, out)} catch (e: Exception) {e.printStackTrace()} finally {try {out?.close()} catch (e: IOException) {e.printStackTrace()}}return false
}

使用示例

// 保存为JPEG
val jpegFile = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "image.jpg")
saveBitmapToFile(bitmap, jpegFile, Bitmap.CompressFormat.JPEG, 80)// 保存为PNG(无损)
val pngFile = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "image.png")
saveBitmapToFile(bitmap, pngFile, Bitmap.CompressFormat.PNG)

注意

  • PNG 格式支持透明度,但文件体积通常较大
  • JPEG 格式不支持透明度,但可以通过 quality 参数控制压缩率
  • Android 10 及以上推荐使用MediaStore API 保存到公共目录

四、Bitmap 内存管理与优化

4.1 避免内存溢出(OOM)

内存溢出是 Bitmap 处理中最常见的问题,尤其是在加载大量图片或高分辨率图片时。以下是避免 OOM 的关键策略:

1.合理设置采样率:根据显示需求加载合适尺寸的图片,而非原始尺寸。

2.选择合适的像素格式

  • 不需要透明度时使用RGB_565(内存占用为ARGB_8888的一半)
  • 仅需透明度时使用ALPHA_8

3.及时回收不再使用的 Bitmap

// 当Bitmap不再需要时
if (bitmap != null && !bitmap.isRecycled) {bitmap.recycle() // 释放native内存// 帮助GC回收bitmap = null
}

注意:Android 8.0 及以上系统会自动管理回收,手动调用recycle()的必要性降低,但仍可作为优化手段。

4.使用弱引用缓存

// 使用WeakReference存储Bitmap,允许GC在内存紧张时回收
val weakBitmap = WeakReference<Bitmap>(bitmap)// 使用时检查是否已被回收
val bitmap = weakBitmap.get()
if (bitmap != null && !bitmap.isRecycled) {// 使用Bitmap
}

5.限制同时加载的图片数量:在列表等场景中,仅加载当前可见区域的图片。

6.监控内存使用

// 获取内存信息
val memoryInfo = ActivityManager.MemoryInfo()
(getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).getMemoryInfo(memoryInfo)// 当可用内存不足时采取措施(如清理缓存)
if (memoryInfo.lowMemory) {clearImageCache()
}

4.2 内存缓存(LruCache)

LruCache(最近最少使用缓存)是 Android 提供的高效内存缓存类,非常适合缓存 Bitmap:

class BitmapMemoryCache(maxSize: Int) : LruCache<String, Bitmap>(maxSize) {/*** 计算每个Bitmap的大小*/override fun sizeOf(key: String, value: Bitmap): Int {// 返回Bitmap的字节数return value.byteCount}/*** 当Bitmap被移除缓存时调用,可用于回收资源*/override fun entryRemoved(evicted: Boolean,key: String?,oldValue: Bitmap?,newValue: Bitmap?) {super.entryRemoved(evicted, key, oldValue, newValue)// 如果是因为内存不足被移除,主动回收if (evicted && oldValue != null && !oldValue.isRecycled) {oldValue.recycle()}}
}// 初始化缓存(通常在Application或单例中)
fun initBitmapCache(context: Context) {// 获取应用可用内存的1/8作为缓存大小val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManagerval memoryClass = activityManager.memoryClass // 应用可用内存(MB)val cacheSize = (memoryClass / 8) * 1024 * 1024 // 转换为字节bitmapCache = BitmapMemoryCache(cacheSize)
}// 使用缓存
fun loadBitmapWithCache(key: String, loader: () -> Bitmap): Bitmap? {// 先从缓存获取bitmapCache.get(key)?.let { return it }// 缓存未命中,加载图片val bitmap = loader()// 存入缓存if (bitmap != null) {bitmapCache.put(key, bitmap)}return bitmap
}

最佳实践

  • 缓存大小通常设为应用可用内存的 1/8
  • 缓存键(key)应唯一且稳定(如图片 URL 的哈希值)
  • 在onTrimMemory回调中根据内存紧张程度调整缓存:
    override fun onTrimMemory(level: Int) {super.onTrimMemory(level)when (level) {// 内存不足,清理所有缓存TRIM_MEMORY_COMPLETE -> bitmapCache.evictAll()// 内存紧张,清理部分缓存TRIM_MEMORY_MODERATE -> bitmapCache.trimToSize(bitmapCache.maxSize() / 2)// 低内存警告,准备清理TRIM_MEMORY_UI_HIDDEN -> bitmapCache.trimToSize(bitmapCache.maxSize() / 4)}
    }

4.3 磁盘缓存(DiskLruCache)

磁盘缓存用于持久化存储 Bitmap,避免重复下载或解码,Android 官方推荐使用DiskLruCache(需自行实现或使用第三方库):

class BitmapDiskCache(private val directory: File, maxSize: Long) {private val diskLruCache = DiskLruCache.open(directory, 1, 1, maxSize)/*** 从磁盘缓存获取Bitmap*/fun getBitmap(key: String): Bitmap? {val safeKey = key.md5() // 使用MD5哈希作为键val snapshot = diskLruCache.get(safeKey) ?: return nullreturn try {val inputStream = snapshot.getInputStream(0)BitmapFactory.decodeStream(inputStream)} finally {snapshot.close()}}/*** 将Bitmap存入磁盘缓存*/fun putBitmap(key: String, bitmap: Bitmap, format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG): Boolean {val safeKey = key.md5()val editor = diskLruCache.edit(safeKey) ?: return falsereturn try {val outputStream = editor.newOutputStream(0)val success = bitmap.compress(format, 80, outputStream)if (success) {editor.commit()} else {editor.abort()}success} catch (e: Exception) {editor.abort()false}}/*** 移除缓存*/fun remove(key: String): Boolean {val safeKey = key.md5()return diskLruCache.remove(safeKey)}/*** 清理所有缓存*/fun clear() {diskLruCache.delete()}/*** 关闭缓存*/fun close() {diskLruCache.close()}// MD5哈希工具方法private fun String.md5(): String {val bytes = MessageDigest.getInstance("MD5").digest(toByteArray())return bytes.joinToString("") { "%02x".format(it) }}
}// 初始化磁盘缓存
fun initDiskCache(context: Context) {// 缓存目录(应用私有目录)val cacheDir = File(context.cacheDir, "bitmap_cache")if (!cacheDir.exists()) {cacheDir.mkdirs()}// 缓存大小设为50MBval cacheSize = 50L * 1024 * 1024diskCache = BitmapDiskCache(cacheDir, cacheSize)
}

磁盘缓存最佳实践

  • 缓存目录使用应用私有缓存目录(context.cacheDir),系统会在内存不足时自动清理
  • 缓存大小根据应用需求设置(通常 10-100MB)
  • 定期清理过期缓存(如超过 7 天的缓存)
  • 避免在主线程进行磁盘操作

4.4 三级缓存策略

结合内存缓存、磁盘缓存和网络加载的三级缓存策略是高效加载图片的标准方案:

class ImageLoader(private val memoryCache: BitmapMemoryCache,private val diskCache: BitmapDiskCache,private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {/*** 加载图片(三级缓存)*/fun loadImage(url: String,targetWidth: Int,targetHeight: Int,onSuccess: (Bitmap) -> Unit,onError: (Exception) -> Unit) {CoroutineScope(ioDispatcher).launch {try {// 1. 先从内存缓存获取var bitmap = memoryCache.get(url)if (bitmap != null) {withContext(Dispatchers.Main) { onSuccess(bitmap) }return@launch}// 2. 内存缓存未命中,从磁盘缓存获取bitmap = diskCache.getBitmap(url)if (bitmap != null) {// 放入内存缓存memoryCache.put(url, bitmap)withContext(Dispatchers.Main) { onSuccess(bitmap) }return@launch}// 3. 磁盘缓存未命中,从网络加载bitmap = downloadBitmap(url, targetWidth, targetHeight)if (bitmap != null) {// 存入磁盘缓存和内存缓存diskCache.putBitmap(url, bitmap)memoryCache.put(url, bitmap)withContext(Dispatchers.Main) { onSuccess(bitmap) }return@launch}// 所有来源都失败withContext(Dispatchers.Main) {onError(Exception("Failed to load image from all sources"))}} catch (e: Exception) {withContext(Dispatchers.Main) { onError(e) }}}}/*** 从网络下载并解码Bitmap*/private suspend fun downloadBitmap(url: String, targetWidth: Int, targetHeight: Int): Bitmap? {return withContext(ioDispatcher) {val connection = URL(url).openConnection() as HttpURLConnectionconnection.doInput = trueconnection.connect()val inputStream = connection.inputStreamval options = BitmapFactory.Options().apply {// 先获取尺寸inJustDecodeBounds = trueBitmapFactory.decodeStream(inputStream, null, this)inputStream.reset() // 重置流以便重新解码// 计算采样率inSampleSize = calculateInSampleSize(this, targetWidth, targetHeight)inJustDecodeBounds = falseinPreferredConfig = Bitmap.Config.RGB_565}val bitmap = BitmapFactory.decodeStream(inputStream, null, options)inputStream.close()connection.disconnect()bitmap}}
}

使用示例

// 初始化图片加载器
val imageLoader = ImageLoader(bitmapCache, diskCache)// 加载图片
imageLoader.loadImage(url = "https://example.com/image.jpg",targetWidth = imageView.width,targetHeight = imageView.height,onSuccess = { bitmap ->imageView.setImageBitmap(bitmap)},onError = { e ->e.printStackTrace()imageView.setImageResource(R.drawable.error_placeholder)}
)

五、列表中的 Bitmap 优化

在RecyclerView或ListView中显示大量图片是 Bitmap 优化的典型场景,处理不当会导致滑动卡顿甚至 OOM。

5.1 RecyclerView 中的图片优化

1.使用 ViewHolder 模式:避免重复创建视图和 Bitmap 对象

class ImageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {val imageView: ImageView = itemView.findViewById(R.id.image_view)var currentUrl: String? = null // 记录当前加载的URL,用于避免图片错位
}

2.取消滑动时的加载:滑动过程中暂停图片加载,减少资源消耗

class PausableImageLoader : ImageLoader {private var isPaused = falseprivate val pendingRequests = mutableListOf<ImageRequest>()// 暂停加载fun pause() {isPaused = true}// 恢复加载fun resume() {isPaused = falsesynchronized(pendingRequests) {pendingRequests.forEach { request ->loadImage(request)}pendingRequests.clear()}}// 重写加载方法fun loadImage(request: ImageRequest) {if (isPaused) {synchronized(pendingRequests) {pendingRequests.add(request)}} else {super.loadImage(request.url,request.width,request.height,request.onSuccess,request.onError)}}
}// 在RecyclerView滚动时暂停/恢复加载
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {super.onScrollStateChanged(recyclerView, newState)when (newState) {RecyclerView.SCROLL_STATE_IDLE -> imageLoader.resume() // 停止滚动时恢复else -> imageLoader.pause() // 滚动时暂停}}
})

3.图片错位解决方案:由于 RecyclerView 的复用机制,快速滑动时可能出现图片错位

// 在绑定ViewHolder时
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {val item = items[position]holder.currentUrl = item.url// 先设置占位图holder.imageView.setImageResource(R.drawable.placeholder)// 加载图片imageLoader.loadImage(url = item.url,targetWidth = holder.imageView.width,targetHeight = holder.imageView.height,onSuccess = { bitmap ->// 检查是否是当前item的图片if (holder.currentUrl == item.url) {holder.imageView.setImageBitmap(bitmap)}},onError = {if (holder.currentUrl == item.url) {holder.imageView.setImageResource(R.drawable.error)}})
}

4.预计算图片尺寸:提前确定 ImageView 的尺寸,避免解码时尺寸为 0

// 在布局中固定ImageView尺寸(推荐)
<ImageViewandroid:layout_width="120dp"android:layout_height="120dp"android:scaleType="centerCrop"/>// 或在代码中计算
val displayMetrics = resources.displayMetrics
val imageSize = (120 * displayMetrics.density).toInt() // 120dp转换为像素

5.2 分页加载与回收

对于大量图片列表,采用分页加载减少同时加载的图片数量:

class ImagePagingAdapter : PagingDataAdapter<ImageItem, ImageViewHolder>(diffCallback) {// ... 实现Adapter相关代码override fun onViewRecycled(holder: ImageViewHolder) {super.onViewRecycled(holder)// 当ViewHolder被回收时,取消加载并清理资源holder.currentUrl?.let { cancelLoading(it) }holder.imageView.setImageBitmap(null) // 清除图片}
}

5.3 缩略图与渐进式加载

对于大图,先加载缩略图再加载高清图,提升用户体验:

fun loadImageWithThumbnail(url: String,thumbnailUrl: String,imageView: ImageView
) {// 1. 先加载缩略图imageLoader.loadImage(url = thumbnailUrl,targetWidth = imageView.width / 4, // 缩略图尺寸为目标的1/4targetHeight = imageView.height / 4,onSuccess = { thumbnail ->imageView.setImageBitmap(thumbnail)// 2. 再加载高清图imageLoader.loadImage(url = url,targetWidth = imageView.width,targetHeight = imageView.height,onSuccess = { highRes ->// 使用淡入动画切换val fadeIn = AlphaAnimation(0f, 1f).apply {duration = 300}imageView.setImageBitmap(highRes)imageView.startAnimation(fadeIn)})})
}

六、高级优化技巧

6.1 使用硬件加速

Android 的硬件加速可以显著提升 Bitmap 的绘制性能,默认情况下是开启的。可以通过以下方式控制:

在 Manifest 中为应用或 Activity 开启

<application android:hardwareAccelerated="true" ...><activity android:name=".MyActivity"android:hardwareAccelerated="true"/>
</application>

在 View 级别控制

<Viewandroid:layerType="hardware"  // 硬件加速... /><Viewandroid:layerType="software"  // 软件渲染... />

代码中设置

// 启用硬件加速
view.setLayerType(View.LAYER_TYPE_HARDWARE, null)// 禁用硬件加速
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null)

注意

  • 硬件加速不支持所有绘图操作,某些自定义绘制可能需要禁用
  • 可通过View.isHardwareAccelerated()检查是否启用了硬件加速

6.2 图片预加载与预解码

在合适的时机提前加载即将需要的图片:

class ImagePreloader(private val imageLoader: ImageLoader) {// 预加载图片到缓存fun preloadImages(urls: List<String>, width: Int, height: Int) {CoroutineScope(Dispatchers.IO).launch {urls.forEach { url ->// 仅加载到缓存,不显示imageLoader.loadImageToCache(url, width, height)}}}
}// 在进入图片列表前预加载
fun onPrepareToEnterGallery() {val upcomingImageUrls = getUpcomingImageUrls() // 获取即将显示的图片URLimagePreloader.preloadImages(upcomingImageUrls, 200, 200)
}

6.3 使用 BitmapRegionDecoder 加载超大图

对于超大图片(如地图、高分辨率扫描件),使用BitmapRegionDecoder加载局部区域:

class LargeImageLoader(private val context: Context) {private var decoder: BitmapRegionDecoder? = nullprivate var imageWidth = 0private var imageHeight = 0/*** 初始化解码器*/fun init(inputStream: InputStream) {decoder = BitmapRegionDecoder.newInstance(inputStream, false)imageWidth = decoder?.width ?: 0imageHeight = decoder?.height ?: 0}/*** 加载指定区域*/fun loadRegion(rect: Rect, sampleSize: Int = 1): Bitmap? {val options = BitmapFactory.Options().apply {inSampleSize = sampleSizeinPreferredConfig = Bitmap.Config.RGB_565}return decoder?.decodeRegion(rect, options)}/*** 释放资源*/fun release() {decoder?.recycle()decoder = null}// 获取图片原始尺寸fun getImageWidth() = imageWidthfun getImageHeight() = imageHeight
}// 使用示例(显示大图的某个区域)
val inputStream = assets.open("large_map.jpg")
largeImageLoader.init(inputStream)// 加载图片的一块区域(x=100, y=200, width=500, height=500)
val rect = Rect(100, 200, 600, 700)
val regionBitmap = largeImageLoader.loadRegion(rect)
imageView.setImageBitmap(regionBitmap)

这种方式特别适合实现图片查看器的缩放和平移功能,只加载当前可见区域。

6.4 使用 RenderScript 进行高效图像处理

RenderScript 是 Android 提供的高性能计算框架,适合进行复杂的图像处理:

/*** 使用RenderScript应用模糊效果*/
fun applyBlur(context: Context, bitmap: Bitmap, radius: Float): Bitmap {// 创建输出Bitmapval output = Bitmap.createBitmap(bitmap.width, bitmap.height, bitmap.config)// 初始化RenderScriptval rs = RenderScript.create(context)val input = Allocation.createFromBitmap(rs, bitmap)val outputAlloc = Allocation.createFromBitmap(rs, output)// 创建模糊脚本val script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))script.setRadius(radius)script.setInput(input)script.forEach(outputAlloc)// 复制结果到输出BitmapoutputAlloc.copyTo(output)// 释放资源input.destroy()outputAlloc.destroy()script.destroy()rs.destroy()return output
}

注意

  • RenderScript 特别适合计算密集型操作(如模糊、降噪、边缘检测)
  • Android 17 及以上支持,对于低版本需要使用支持库
  • 效果相同的情况下,RenderScript 通常比 Java 实现快 10-100 倍

6.5 减少 Bitmap 拷贝

频繁的 Bitmap 拷贝会消耗大量 CPU 和内存,应尽量避免:

1.直接复用 Bitmap

// 复用已有的Bitmap(需确保尺寸和格式兼容)
fun decodeWithReuse(inputStream: InputStream, reuseBitmap: Bitmap
): Bitmap? {val options = BitmapFactory.Options().apply {inMutable = trueinBitmap = reuseBitmap // 复用此Bitmap}return BitmapFactory.decodeStream(inputStream, null, options)
}

复用条件

  • Android 3.0(API 11)及以上支持
  • 复用的 Bitmap 必须是可变的(isMutable == true)
  • 新 Bitmap 的内存不能大于复用 Bitmap 的内存(Android 4.4 之前)

2.直接在原始 Bitmap 上绘制

// 避免创建新Bitmap,直接在原始Bitmap上绘制(需确保可修改)
fun drawOnOriginal(bitmap: Bitmap, drawAction: Canvas.() -> Unit): Bitmap {if (!bitmap.isMutable) {// 如果不可修改,只能创建副本return bitmap.copy(Bitmap.Config.ARGB_8888, true).apply {Canvas(this).drawAction()}}// 直接在原始Bitmap上绘制Canvas(bitmap).drawAction()return bitmap
}

七、第三方库的使用

手动处理 Bitmap 的各种优化细节非常繁琐,实际项目中推荐使用成熟的图片加载库,它们已经内置了各种优化策略。

7.1 Glide

Glide 是 Google 推荐的图片加载库,以易用性和性能著称:

添加依赖

dependencies {implementation 'com.github.bumptech.glide:glide:4.14.2'annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'
}

基本使用

// 加载网络图片
Glide.with(context).load("https://example.com/image.jpg").into(imageView)// 加载资源图片
Glide.with(context).load(R.drawable.image).into(imageView)// 加载文件图片
Glide.with(context).load(file).into(imageView)

高级配置

Glide.with(context).load(url).placeholder(R.drawable.placeholder) // 加载中占位图.error(R.drawable.error) // 错误占位图.fallback(R.drawable.fallback) // URL为空时的占位图.override(500, 500) // 指定尺寸.centerCrop() // 裁剪方式.circleCrop() // 圆形裁剪.thumbnail(0.5f) // 先加载缩略图(原图的50%).transition(DrawableTransitionOptions.withCrossFade()) // 淡入动画.diskCacheStrategy(DiskCacheStrategy.ALL) // 缓存策略.priority(Priority.HIGH) // 优先级.listener(object : RequestListener<Drawable> {override fun onLoadFailed(e: GlideException?,model: Any?,target: Target<Drawable>?,isFirstResource: Boolean): Boolean {// 加载失败处理return false}override fun onResourceReady(resource: Drawable?,model: Any?,target: Target<Drawable>?,dataSource: DataSource?,isFirstResource: Boolean): Boolean {// 加载成功处理return false}}).into(imageView)

Glide 的优势

  • 自动管理生命周期,避免内存泄漏
  • 内置三级缓存,性能优异
  • 支持多种图片格式和数据源
  • 自动处理图片尺寸和内存优化
  • 丰富的变换和过渡效果

7.2 Picasso

Picasso 是 Square 公司开发的轻量级图片加载库:

添加依赖

dependencies {implementation 'com.squareup.picasso:picasso:2.71828'
}

基本使用

Picasso.get().load("https://example.com/image.jpg").into(imageView)

高级用法

Picasso.get().load(url).placeholder(R.drawable.placeholder).error(R.drawable.error).resize(500, 500).centerCrop().rotate(90f) // 旋转.transform(CropCircleTransformation()) // 圆形变换.priority(Picasso.Priority.HIGH).fetch() // 仅下载不显示

自定义变换

class GrayscaleTransformation : Transformation {override fun transform(source: Bitmap): Bitmap {// 实现灰度变换val result = Bitmap.createBitmap(source.width, source.height, source.config)val canvas = Canvas(result)val paint = Paint()val colorMatrix = ColorMatrix()colorMatrix.setSaturation(0f)paint.colorFilter = ColorMatrixColorFilter(colorMatrix)canvas.drawBitmap(source, 0f, 0f, paint)source.recycle() // 回收原始Bitmapreturn result}override fun key(): String = "grayscale"
}// 使用自定义变换
Picasso.get().load(url).transform(GrayscaleTransformation()).into(imageView)

7.3 Coil

Coil 是一个基于 Kotlin 协程的现代图片加载库:

添加依赖

dependencies {implementation 'io.coil-kt:coil:2.4.0'
}

基本使用

// 加载图片
imageView.load("https://example.com/image.jpg")// 更详细的配置
imageView.load(url) {placeholder(R.drawable.placeholder)error(R.drawable.error)crossfade(true)transformations(CircleCropTransformation())size(500)
}

Coil 的优势

  • 完全基于 Kotlin 和协程,与 Kotlin 生态无缝集成
  • 性能优异,启动速度快
  • 支持 Jetpack Compose
  • 内置多种变换和缓存策略

7.4 库的选择建议

优势

劣势

适用场景

Glide

功能全面,生命周期管理完善,缓存策略优秀

体积较大

大多数应用,尤其是需要复杂功能的场景

Picasso

轻量,API 简洁,易集成

功能相对简单

简单场景,对包体积敏感的应用

Coil

基于协程,现代架构,性能好

相对较新,生态不如 Glide 成熟

Kotlin 项目,尤其是使用 Jetpack Compose 的应用

建议

  • 新项目优先考虑 Glide 或 Coil
  • 简单需求可选择 Picasso
  • Kotlin 项目推荐使用 Coil,与协程配合更佳
  • 避免为了微小差异在项目中引入多个图片库

八、常见问题与解决方案

8.1 图片拉伸与变形

问题:图片显示时出现拉伸或变形。

解决方案

1.正确设置scaleType:

<!-- 常用的scaleType -->
<ImageViewandroid:scaleType="centerCrop" <!-- 保持比例,裁剪填充 --><!-- 或 -->android:scaleType="fitCenter" <!-- 保持比例,适应视图 -->... />

2.确保 ImageView 尺寸与图片比例一致:

// 加载图片后调整ImageView尺寸以保持比例
fun adjustImageViewRatio(imageView: ImageView, bitmap: Bitmap) {val ratio = bitmap.width.toFloat() / bitmap.height.toFloat()imageView.layoutParams.height = (imageView.width / ratio).toInt()imageView.requestLayout()
}

3.使用占位图时,确保占位图与目标图片比例一致。

8.2 图片加载缓慢或卡顿

问题:图片加载速度慢,或导致 UI 卡顿。

解决方案

1.确保在后台线程进行图片解码和处理

2.使用合适的采样率,避免加载过大图片

3.实现三级缓存,减少重复加载

4.滑动列表中使用暂停 / 恢复加载机制

5.对大图使用缩略图渐进式加载

6.考虑使用 WebP 等更高效的图片格式

8.3 内存溢出(OOM)

问题:加载图片时抛出OutOfMemoryError。

解决方案

1.严格控制图片尺寸,使用合适的采样率

2.优先使用RGB_565格式

3.及时回收不再使用的 Bitmap

4.实现内存缓存并设置合理大小

5.监控内存状态,在内存不足时清理缓存

6.避免同时加载大量图片

8.4 图片错位(RecyclerView 中)

问题:在 RecyclerView 快速滑动时,图片显示混乱或错位。

解决方案

1.在 ViewHolder 中记录当前加载的 URL

2.加载完成后检查 URL 是否匹配

3.复用 ViewHolder 时清除旧图片

4.使用占位图减少视觉混乱

override fun onBindViewHolder(holder: ViewHolder, position: Int) {val item = items[position]holder.bind(item)
}fun bind(item: Item) {// 记录当前URLcurrentUrl = item.url// 清除旧图片imageView.setImageResource(R.drawable.placeholder)// 加载新图片loadImage(item.url) { bitmap ->// 检查是否是当前项if (currentUrl == item.url) {imageView.setImageBitmap(bitmap)}}
}

8.5 图片旋转问题

问题:加载的图片方向不正确(尤其是相机拍摄的照片)。

解决方案

1.读取图片的 EXIF 信息获取旋转角度:

/*** 读取图片的旋转角度*/
fun getImageRotation(file: File): Int {try {val exif = ExifInterface(file.absolutePath)val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL)return when (orientation) {ExifInterface.ORIENTATION_ROTATE_90 -> 90ExifInterface.ORIENTATION_ROTATE_180 -> 180ExifInterface.ORIENTATION_ROTATE_270 -> 270else -> 0}} catch (e: Exception) {e.printStackTrace()return 0}
}

2.加载图片时应用旋转:

fun loadImageWithRotation(context: Context, file: File, imageView: ImageView) {val rotation = getImageRotation(file)val bitmap = BitmapFactory.decodeFile(file.absolutePath)val rotatedBitmap = if (rotation != 0) {rotateBitmap(bitmap, rotation.toFloat())} else {bitmap}imageView.setImageBitmap(rotatedBitmap)bitmap.recycle() // 回收原始Bitmap
}

3.第三方库(如 Glide)会自动处理 EXIF 旋转信息,推荐使用。

九、总结与展望

Bitmap 处理是 Android 开发中的核心技术之一,也是性能优化的关键领域。从基础的加载和显示,到复杂的内存管理和性能优化,每一个环节都需要开发者深入理解 Bitmap 的特性和 Android 系统的工作机制。

本文全面介绍了 Bitmap 的基础知识、创建加载、处理操作、内存管理、优化技巧和第三方库使用,涵盖了从简单到复杂的各种场景。掌握这些知识不仅能够解决日常开发中的图片处理问题,更能帮助开发者构建高性能、低内存占用的优秀应用。

随着 Android 系统的不断演进,Bitmap 的处理方式也在持续优化。从早期的手动内存管理,到现代系统的自动内存回收;从基础的BitmapFactory,到功能强大的 Glide、Coil 等库,Bitmap 处理的便捷性和性能都在不断提升。

未来,随着硬件性能的提升和新图片格式(如 WebP、HEIF)的普及,Android 的 Bitmap 处理将更加高效。同时,Jetpack Compose 等新 UI 框架也为图片处理带来了新的方式和挑战。

作为开发者,我们需要不断学习和适应这些变化,在掌握基础原理的同时,善用系统 API 和第三方库,在功能实现和性能优化之间找到平衡,为用户提供流畅、稳定的图片体验。

Bitmap 处理的优化是一个持续迭代的过程,没有一劳永逸的解决方案。只有结合具体应用场景,不断测试、分析和优化,才能真正掌握这门技术,构建出优秀的 Android 应用。

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

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

相关文章

unity日志过滤器

背景&#xff1a;之前做游戏的时候和同组的同事聊过说日志过滤尽量不要限制大家怎么使用日志打印的接口&#xff0c;不要加额外的参数&#xff0c;比如多加一个标签string,或者使用特定的接口&#xff0c;枚举。最好就是日志大家还是用Debug.Log无感去用&#xff0c;然后通过勾…

OpenGL Camera

一. lookAt函数的参数含义glm::mat4 view glm::lookAt(cameraPos, // 相机在世界坐标系中的位置&#xff08;任意值&#xff09;cameraPos cameraFront, // 相机看向的目标点&#xff08;位置朝向&#xff09;cameraUp // 相机的"上方向"&#xff08;通…

Android RTMP推送|轻量级RTSP服务同屏实践:屏幕+音频+录像全链路落地方案

一、背景&#xff1a;从“移动终端”到“远程协作节点”&#xff0c;同屏音频录像为何成刚需&#xff1f; 在数字化办公、智慧医疗与远程教育等快速发展的推动下&#xff0c;手机作为随身终端&#xff0c;已不再只是“内容接收者”&#xff0c;而逐步成为远程信息发布与可视化…

NLP 和 LLM 区别、对比 和关系

理解自然语言处理(NLP)和大语言模型(LLM)的区别、对比和关系对于把握现代人工智能的发展非常重要。以下是清晰的分析: 核心定义 NLP (Natural Language Processing - 自然语言处理): 是什么: 一个广阔的计算机科学和人工智能子领域,致力于让计算机能够理解、解释、操作…

Altium 移除在原理图之外的元器件

Altium新手&#xff0c;最近在画原理图的时候&#xff0c;遇见了这种不小心拖到界面外的元器件&#xff0c;发现拖不回来了了&#xff0c;查阅了一下&#xff0c;总结在这里 官方推荐的方法----------------使用“SCH List”面板删除 链接&#xff1a;如何移除在原理图之外的元…

【Linux我做主】细说环境变量

Linux环境变量Linux环境变量github地址前言1. 基本概念环境变量的本质2. 认识常见的环境变量PATH查看PATH修改PATHHOMESHELL其他常见环境变量PWD与OLDPWDLOGNAME与USERSSH_TTY由环境变量理解权限使用系统调用获取环境变量理解权限3. 总结什么是环境变量3. 命令行参数和环境变量…

leecode-15 三数之和

我的解法&#xff08;不是完全解309/314&#xff09;我的思路是定义一个left和一个right&#xff0c;然后在向集合里去查询&#xff0c;看看有没有除了nums[left]&#xff0c;和nums[right]的第三个元素&#xff0c;把这个问题转换为一个遍历查找问题 利用List.contains()方法来…

精通分类:解析Scikit-learn中的KNN、朴素贝叶斯与决策树(含随机森林)

在机器学习领域&#xff0c;分类任务占据核心地位。Scikit-learn作为Python的机器学习利器&#xff0c;提供了丰富高效的分类算法。现在进行初步探讨三种经典算法&#xff1a;K最近邻&#xff08;KNN&#xff09;、朴素贝叶斯&#xff08;Naive Bayes&#xff09;和决策树&…

Galaxea机器人由星海图人工智能科技有限公司研发的高性能仿人形机器人

Galaxea机器人是由星海图人工智能科技有限公司研发的高性能仿人形机器人&#xff0c;具有多种型号&#xff0c;包括Galaxea R1和Galaxea R1 Pro。以下是关于Galaxea机器人的详细介绍&#xff1a; GitHub官网 产品特点 高自由度设计&#xff1a;Galaxea R1是一款全尺寸仿人型机…

python基础:用户输入和 while 循环

一、input() 函数的工作原理input() 函数让程序暂停运行&#xff0c;等待用户输入一些文本。获取用户输入后&#xff0c;Python 将其赋给一个变量&#xff0c;以便使用。message input("Tell me something, and I will repeat it back to you: ") print(message) 结…

开启云服务器mysql本地连接(is not allowed to connect to this mysql server)

is not allowed to connect tothis mmysql server 阿里云上安装的mysql&#xff0c;发现用本地电脑的navicat链接不上。通过了解知道了原因&#xff0c;小二在此写了一篇&#xff0c;省的以后自己在碰到。 错误如图。 aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTU4MTU1My8…

电脑的时间同步电池坏掉了,每次开机都要调整时间

电脑的时间同步的电池没电了&#xff0c;每天开机时间都不对&#xff0c;要打开时间同步按钮来设置时间解决方案1.找到这个设置并打开&#xff0c;实际上&#xff0c;要打开这个界面&#xff0c;时间才会同步&#xff0c;可能是我的电脑原因&#xff0c;所以我没办法打开这个就…

mycat在游戏中的使用场景(邮件表,mysql集群,而不是邮件服)

其实还有一种是SharingJDBC&#xff0c;而且之间在B站的同学也是说用这个&#xff0c;但是我们目前项目邮件中用的却是: mycat&#xff0c;为什么呢&#xff1f;mycat其实是中间件&#xff0c;是需要独立部署的&#xff0c;是数据库服务器这块的代理&#xff0c;在应用层的话很…

TP-Link Archer C50路由器曝安全漏洞,硬编码DES密钥可解密敏感配置

漏洞概述CERT协调中心&#xff08;CERT/CC&#xff09;发布安全公告&#xff0c;披露TP-Link Archer C50路由器存在编号为CVE-2025-6982的漏洞。该漏洞源于路由器固件中使用了硬编码的DES&#xff08;数据加密标准&#xff09;解密密钥&#xff0c;这一设计缺陷使大量家庭和小型…

番茄项目3:完成了项目的数据库设计

今天抽了会时间设计了下表结构&#xff0c;并选定的使用的数据库&#xff0c;经过调查&#xff0c;我决定还是把数据存在数据库中&#xff0c;因为写SQL是我擅长的。 最终我选择使用python自带的sqlite来实现这个工具&#xff0c;具体建表语句如下&#xff1a; 基于AI生成&…

11、read_object_model_3d 读取点云

个人理解 read_object_model_3d 这个Halcon算子中的xyz_map_width这个参数设置的目的就是,把读取的点云数据中每一个点的XYZ坐标,生成一个对应的二维图像,其中图像中的坐标值就对应每一个点的索引坐标,而图像中的灰度值就对应xyz坐标??(因为得到的是三通道图像)!!并且根…

【人工智能-17】机器学习:KNN算法、模型选择和调优、朴素贝叶斯分类

上一期【人工智能-16】机器学习&#xff1a;概念、工具介绍、数据集、特征工程 文章目录一 、KNN算法1. 应用理由2. 原理核心&#xff1a;距离度量 多数投票/平均3. 优点和缺点二、模型选择和调优1.使用理由2.原理核心&#xff1a;数据划分与性能平均3.超参数搜索4. 应用场景总…

关于继承的一些知识(C++)

当我们想要设计几个类分别记录老师&#xff0c;学生的个人信息时会发现&#xff0c;像姓名、地址、身份证号、电话等等记录基础信息的成员变量是都具有的&#xff0c;重复定义会显得冗余&#xff0c;但同时它们两者又具有不同的记录信息的成员变量&#xff0c;像学生需要记录学…

永磁同步电机无速度算法--脉振方波注入法

一、原理介绍为了实现表贴式永磁电机的低速运行&#xff0c;研究一种基于高频方波测试信号注入的无位置零低速传感器控制策略。选取注入到观测直轴的脉振高频方波信号&#xff0c; 该信号注入方案可以有效避免旋转信号注入法在转子交轴分量引起转矩脉动&#xff0c; 提高系统的…

VSCode Python 与 C++ 联合调试配置指南

VSCode Python 与 C 联合调试配置指南 为了实现 Python 与 C 的联合调试&#xff0c;需要正确配置 launch.json 文件&#xff0c;具体配置如下&#xff1a; {// IntelliSense 支持查看属性描述// 更多信息请参考: https://go.microsoft.com/fwlink/?linkid830387"version…