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
英文地址,請注明出處:http://en.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…