8.5 KiB
Hai lỗi này liên quan trực tiếp đến hiện tượng bất đồng bộ luồng (Race Condition) và quản lý vòng đời bộ đệm (State Lifecycle/Caching) giữa tầng Flutter UI và tầng Native Android (MainActivity.kt).
- Lỗi nhấn shutter lúc chụp lúc không: Do khi bạn đổi Preset, luồng ngầm xử lý ma trận màu của Preset đó đang chiếm dụng tài nguyên đồ họa (Bitmap/ImageReader) cùng lúc với lệnh chụp
takePicture(). Nếu lệnh chụp đến trước khi Preset xử lý xong\rightarrowChụp lỗi/Bị nuốt lệnh. - Lỗi đổi Preset phải bấm vài lần mới ăn: Do biến lưu cấu hình Preset ở tầng Native (
activePreset) không được cập nhậtvolatilethời gian thực, hoặc do bộ đệm xem trước (Preview Request Builder) không được gọi lệnh làm tươi (setRepeatingRequest) ngay lập tức sau khi gán thông số mới.
Dưới đây là kế hoạch Markdown chi tiết để sửa dứt điểm hai lỗi trên, đảm bảo đổi bộ lọc màu ăn ngay lập tức và nút bấm Shutter phản hồi chính xác 100%.
🛠️ KẾ HOẠCH FIX LỖI: ĐỒNG BỘ ĐỒNG THỜI PRESET REAL-TIME VÀ SỬA KHÓA LỆNH SHUTTER
📐 1. Khắc Phục Lỗi Thay Đổi Preset Không Ăn Ngay Lập Tức (MainActivity.kt)
Giải pháp: 1. Thêm từ khóa volatile vào biến lưu trữ Preset ở tầng Native để đảm bảo mọi luồng xử lý (Main Thread và Background Thread) đều đọc chung một giá trị mới nhất ngay khi người dùng chạm tay chọn.
2. Ép cấu hình Camera2 API làm tươi (Refresh Buffer) ngay lập tức bằng lệnh setRepeatingRequest sau khi người dùng thay đổi thông số.
import android.hardware.camera2.CaptureRequest
class MainActivity : FlutterActivity() {
// 🟢 SỬA LỖI: Dùng @Volatile để đảm bảo thông số Preset cập nhật ngay lập tức xuyên luồng
@Volatile
private var currentActivePreset: ColorPreset? = null
// Hàm nhận lệnh đổi Preset từ Flutter UI truyền xuống thông qua MethodChannel
fun updateActivePresetFromUI(newPreset: ColorPreset) {
this.currentActivePreset = newPreset
// 🟢 SỬA LỖI PHẢI BẤM VÀI LẦN: Ép thấu kính camera cập nhật ma trận màu Viewfinder ngay lập tức
try {
previewRequestBuilder.set(
CaptureRequest.COLOR_CORRECTION_MODE,
CaptureRequest.COLOR_CORRECTION_MODE_TRANSFORM_MATRIX
)
// Cập nhật ma trận màu phần cứng dựa trên thông số Preset mới chọn
applyPresetToPreviewBuilder(previewRequestBuilder, newPreset)
// Lệnh làm tươi Live Preview ngay một miligiây sau khi chọn
cameraCaptureSession.setRepeatingRequest(previewRequestBuilder.build(), null, null)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
⚡ 2. Khắc Phục Lỗi Nút Shutter Lúc Chụp Lúc Không (Chống Nghẽn Tài Nguyên)
Nguyên nhân: Khi bạn nhấn Shutter, ứng dụng chụp lại ảnh thô. Nếu lúc đó bạn vừa đổi Preset, hệ thống đồ họa bị "xung đột" quyền truy cập Bitmap. Nếu luồng chụp không khóa hàng đợi (Thread Lock), lệnh capture sẽ bị hủy âm thầm khiến camera đứng im không chụp.
Giải pháp: Sử dụng cờ trạng thái isCapturing và cơ chế Đóng băng cấu hình tạm thời (Snapshot Parameter). Khi bấm Shutter, ta nhân bản nhanh thông số Preset tại thời điểm đó rồi đẩy vào luồng xử lý riêng, giải phóng luồng chính để sẵn sàng nhận lệnh chụp tiếp theo.
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicBoolean
// Sử dụng AtomicBoolean chống hiện tượng kích kép (Double tap) hoặc nghẽn lệnh Shutter
private val isCapturing = AtomicBoolean(false)
fun onShutterButtonPressed() {
// Nếu đang trong tiến trình bắt ảnh thô từ cảm biến, chặn không cho bấm tiếp để tránh nghẽn phần cứng
if (isCapturing.get()) {
print("Camera đang bận chụp tấm trước, vui lòng đợi mốc miligiây!")
return
}
// Khóa trạng thái bắt đầu chụp
isCapturing.set(true)
triggerFlashAnimationUI() // Kích hoạt hiệu ứng chớp màn hình lập tức cho người dùng biết
// 🟢 CHỐNG XUNG ĐỘT: Sao chép nhanh (Snapshot) thông số Preset hiện tại ra một biến độc lập
val presetSnapshot = this.currentActivePreset?.copy()
// Tiến hành gọi phần cứng chụp ảnh thô
val captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
captureBuilder.addTarget(imageReader.surface)
cameraCaptureSession.capture(captureBuilder.build(), object : CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
super.onCaptureCompleted(session, request, result)
// MỞ KHÓA CAMERA LẬP TỨC: Khôi phục luồng ngắm để người dùng bấm chụp phát tiếp theo được ngay
runOnUiThread {
cameraCaptureSession.setRepeatingRequest(previewRequestBuilder.build(), null, null)
// Đã giải phóng xong phần cứng camera, nút Shutter sẵn sàng hoạt động 100%
isCapturing.set(false)
}
// ĐẨY LUỒNG XỬ LÝ ẢNH NẶNG XUỐNG COROUTINE NGẦM HOÀN TOÀN
GlobalScope.launch(Dispatchers.Default) {
val rawBitmap = getAndLockBitmapFromReader(imageReader)
// Áp dụng bản sao Preset đã chụp snapshot (Không sợ bị lỗi khi người dùng đổi màu khác lúc đang lưu)
val finalFilteredBitmap = if (presetSnapshot != null) {
applyHighImpactPreset(rawBitmap, presetSnapshot)
} else {
rawBitmap
}
// Chạy luồng lưu file ngầm
saveBitmapToSystemGalleryIO(finalFilteredBitmap)
// Thu hồi RAM dọn dẹp bộ nhớ đệm sạch sẽ
rawBitmap.recycle()
finalFilteredBitmap.recycle()
}
}
}, null)
}
🌐 3. Đồng Bộ Chặt Chẽ Tại Tầng Giao Diện Người Dùng (CameraScreen.dart)
Ở tầng Flutter UI, khi người dùng trượt Ruler hoặc nhấn chọn Preset, ta gọi trực tiếp MethodChannel xuống Native một cách dứt khoát, không bọc qua các hàm delay trung gian:
// Mỗi lần người dùng trượt Ruler thay đổi thông số màu
void onPresetSliderChanged(double value) {
_currentPreset.brightness = value;
// Gửi trực tiếp xuống Native ép mô-tơ đồ họa và camera làm tươi dữ liệu ngay
_methodChannel.invokeMethod('updatePreset', _currentPreset.toJson());
setState(() {}); // Làm mới thanh trượt trên UI của Flutter
}
📅 4. Checklist Xác Minh Sau Khi Sửa Lỗi (Verification Checklist)
- Kiểm thử đổi Preset ăn ngay: Nhấn liên tục vào các Preset khác nhau màu Trắng Đen
\rightarrowVintage\rightarrowRực rỡ. (Yêu cầu: Kính ngắm Viewfinder phải đổi màu theo ngay lập tức ngay tại thời điểm chạm ngón tay, không có độ trễ, không phải bấm 2-3 lần mới ăn). - Kiểm thử Shutter chính xác 100%: Bấm nút Shutter liên tục (Burst-click) khoảng 5-10 lần với tốc độ nhanh. (Yêu cầu: Mỗi lần bấm màn hình đều phải chớp sáng sáng 1 cái biểu thị đã ghi nhận lệnh chụp, không xuất hiện tình trạng "bấm nhưng camera đơ ra không chụp").
- Kiểm thử lưu ảnh đúng màu: Đang đứng ở
Preset Trắng Đen, bấm nút Shutter, rồi lập tức bấm tay chuyển nhanh sangPreset Rực Rỡ. Mở bộ sưu tập ảnh kiểm tra tấm ảnh vừa chụp xem có lưu đúng màuTrắng Đencủa thời điểm bấm nút chụp không. (Yêu cầu: Đúng màu nhờ thuật toán Snapshot ở Mục 2).