拍照模块
📋 模块概述
拍照模块提供独立的拍照功能,支持自定义文字叠加(设备ID+GPS位置)、自动上传等特性。
核心类:
android.znhaas.service.RecordingService(服务层)android.znhaas.recoder.VideoRecorderAPI(API层)
🎯 主要功能
- ✅ 独立拍照(不需要正在录制)
- ✅ 自定义文字叠加(设备ID + GPS坐标)
- ✅ 自动上传功能(可配置)
- ✅ 文件自动命名(
IMG_yyyyMMdd_HHmmss.jpg) - ✅ 拍照状态回调
- ✅ 相机资源自动管理
📚 核心API
1. 通过RecordingService拍照
takePhoto(): Boolean
最常用的拍照方式,通过服务统一管理。
流程:
- 检查录制器是否初始化,未初始化则自动重新初始化
- 更新自定义文字(设备ID + GPS位置)
- 等待100ms确保文字更新完成
- 调用底层拍照API
- 根据配置决定是否自动上传
使用示例:
class MainActivity : AppCompatActivity() {
private var recordingService: RecordingService? = null
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as RecordingService.LocalBinder
recordingService = binder.service
// 设置设备信息和位置服务
recordingService?.setDeviceInfo(deviceId, locationService)
// 设置监听器
recordingService?.setRecordingServiceListener(recordingListener)
}
override fun onServiceDisconnected(name: ComponentName?) {
recordingService = null
}
}
private val recordingListener = object : RecordingService.RecordingServiceListener {
override fun onPhotoTaken(filePath: String) {
Log.d(TAG, "拍照成功: $filePath")
runOnUiThread {
Toast.makeText(this@MainActivity,
"照片已保存", Toast.LENGTH_SHORT).show()
// 显示照片预览
showPhotoPreview(filePath)
}
}
override fun onPhotoError(errorMessage: String) {
Log.e(TAG, "拍照失败: $errorMessage")
runOnUiThread {
Toast.makeText(this@MainActivity,
"拍照失败: $errorMessage", Toast.LENGTH_SHORT).show()
}
}
// 其他回调方法...
override fun onRecordingStarted(filePath: String) {}
override fun onRecordingStopped(filePath: String) {}
override fun onRecordingError(errorMessage: String) {}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 绑定服务
val intent = Intent(this, RecordingService::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
// 拍照按钮
photoButton.setOnClickListener {
takePhoto()
}
}
private fun takePhoto() {
val success = recordingService?.takePhoto()
if (success == true) {
Log.d(TAG, "拍照请求已发送")
} else {
Log.e(TAG, "拍照请求失败")
Toast.makeText(this, "拍照失败,请稍后重试", Toast.LENGTH_SHORT).show()
}
}
private fun showPhotoPreview(filePath: String) {
val bitmap = BitmapFactory.decodeFile(filePath)
imageView.setImageBitmap(bitmap)
}
override fun onDestroy() {
super.onDestroy()
if (isBound) {
unbindService(serviceConnection)
}
}
}
2. 通过VideoRecorderAPI拍照
更底层的拍照API,适合自定义场景。
val recorder = VideoRecorderFactory.createVideoRecorderAPI(context)
// 初始化
val config = RecordingUtils.createHDConfig()
recorder.initialize(context, config)
// 拍照
recorder.takePhoto(object : VideoRecorderAPI.PhotoListener {
override fun onPhotoTaken(filePath: String) {
Log.d(TAG, "照片保存到: $filePath")
}
override fun onPhotoError(error: String) {
Log.e(TAG, "拍照错误: $error")
}
})
🔧 自定义文字叠加
文字内容格式
拍照时自动叠加的文字格式:
{设备ID}
{纬度}, {经度}
示例:
HELMET_12345
22.547000, 114.085000
实现原理
服务层代码(RecordingService.java):
private String getCurrentLocationText() {
if (mLocationService == null) {
return mHasValidLocation ?
String.format("%.6f , %.6f", mLastValidLatitude, mLastValidLongitude) : "";
}
NativeLocationService.LocationResult location = mLocationService.getLastLocation();
long currentTime = System.currentTimeMillis();
if (location != null && location.getLatitude() != 0.0) {
// 更新缓存
mLastValidLatitude = location.getLatitude();
mLastValidLongitude = location.getLongitude();
mHasValidLocation = true;
mLastLocationUpdateTime = currentTime;
return String.format("%.6f , %.6f", mLastValidLatitude, mLastValidLongitude);
} else if (mHasValidLocation &&
(currentTime - mLastLocationUpdateTime) < LOCATION_CACHE_VALIDITY_MS) {
// 使用缓存的位置(5秒内有效)
return String.format("%.6f , %.6f", mLastValidLatitude, mLastValidLongitude);
}
return "";
}
public boolean takePhoto() {
// ... 初始化检查 ...
// 设置自定义文字
String customText = buildCustomText();
mRecorder.setCustomText(customText);
// 等待100ms确保文字更新完成
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 执行拍照
return mRecorder.takePhoto(photoListener);
}
private String buildCustomText() {
StringBuilder sb = new StringBuilder();
// 设备ID
if (mDeviceId != null && !mDeviceId.isEmpty()) {
sb.append(mDeviceId).append("\n");
}
// GPS位置
String locationText = getCurrentLocationText();
if (!locationText.isEmpty()) {
sb.append(" ").append(locationText);
}
return sb.toString();
}
位置缓存机制
- 缓存有效期: 5秒
- 缓存策略: 在有效期内,即使GPS无法获取新位置,也会使用缓存的位置
- 超时处理: 超过有效期且无新位置,则不显示位置信息
📤 自动上传功能
配置上传
通过SharedPreferences配置是否自动上传:
val settings = getSharedPreferences("settings", Context.MODE_PRIVATE)
settings.edit().putBoolean("photoUploadAlarm", true).apply()
上传实现
// RecordingService.java
private void handlePhotoSuccess(String filePath) {
SharedPreferences settingPerf = getSharedPreferences("settings", Context.MODE_PRIVATE);
boolean photoUploadAlarm = settingPerf.getBoolean("photoUploadAlarm", false);
if (photoUploadAlarm && mUploadUtil != null) {
File photoFile = new File(filePath);
if (photoFile.exists()) {
mUploadUtil.uploadPhoto(photoFile, new UploadUtil.UploadCallback() {
@Override
public void onUploadSuccess(String fileName, int seqNo) {
Log.d(TAG, "照片上传成功: " + fileName);
}
@Override
public void onUploadFailure(String error, int seqNo) {
Log.e(TAG, "照片上传失败: " + error);
}
});
}
}
}
设置上传工具
recordingService?.setUploadUtil(uploadUtil)
📁 文件管理
文件命名
自动生成文件名格式:
IMG_yyyyMMdd_HHmmss.jpg
示例:
IMG_20250119_143052.jpg
保存路径
照片保存在应用专属目录:
/storage/emulated/0/Android/data/android.znhaas/files/
获取照片
val photosDir = File(context.getExternalFilesDir(null), "photos")
val photos = photosDir.listFiles { file ->
file.name.startsWith("IMG_") && file.name.endsWith(".jpg")
}
⚠️ 注意事项
1. 权限要求
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
2. 相机资源管理
- 拍照不需要正在录制
- 拍照后可能释放相机资源
- 下次拍照会自动重新初始化
- 录制时相机资源始终保持
3. 线程安全
- 拍照操作是异步的
- 回调在主线程执行
- 可以直接更新UI
4. 文字更新延迟
拍照前会等待100ms确保文字叠加完成:
// 等待100ms确保文字更新完成
Thread.sleep(100);
5. 初始化检查
如果相机未初始化,会自动重新初始化:
if (!reinitializeResources()) {
return false;
}
🔗 相关资源
-
源码:
app/src/main/java/android/znhaas/service/RecordingService.javaapp/src/main/java/android/znhaas/recoder/VideoRecorderAPI.java
-
相关模块: