2024年安卓最新Android开发又一新挑战!CameraX 即将一统江湖?(2),驱动核心源码详解和Binder超系统学习资源

最后

在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

如果想要切换镜头,只要将目标镜头的CameraSelector示例绑定到CameraProvider即可。我们在画面上添加按钮以切换镜头。

public void onChangeGo(View view) {

if (mCameraProvider != null) {

isBack = !isBack;

bindPreview(mCameraProvider, binding.previewView);

}

}

private void bindPreview(@NonNull ProcessCameraProvider cameraProvider,

PreviewView previewView) {

CameraSelector cameraSelector = isBack ? CameraSelector.DEFAULT_BACK_CAMERA

: CameraSelector.DEFAULT_FRONT_CAMERA;

// 绑定前确保解除了所有绑定,防止CameraProvider重复绑定到Lifecycle发生异常

cameraProvider.unbindAll();

mCamera = cameraProvider.bindToLifecycle(this, cameraSelector, mPreview);

}

镜头聚焦


无法聚焦的拍摄是不完整的,我们监听Preview的触摸事件将触摸坐标告知CameraX开始聚焦。

protected void onCreate(@Nullable Bundle savedInstanceState) {

binding.previewView.setOnTouchListener((v, event) -> {

FocusMeteringAction action = new FocusMeteringAction.Builder(

binding.previewView.getMeteringPointFactory()

.createPoint(event.getX(), event.getY())).build();

try {

showTapView((int) event.getX(), (int) event.getY());

mCamera.getCameraControl().startFocusAndMetering(action);

}…

});

}

private void showTapView(int x, int y) {

PopupWindow popupWindow = new PopupWindow(ViewGroup.LayoutParams.WRAP_CONTENT,

ViewGroup.LayoutParams.WRAP_CONTENT);

ImageView imageView = new ImageView(this);

imageView.setImageResource(R.drawable.ic_focus_view);

popupWindow.setContentView(imageView);

popupWindow.showAsDropDown(binding.previewView, x, y);

binding.previewView.postDelayed(popupWindow::dismiss, 600);

binding.previewView.playSoundEffect(SoundEffectConstants.CLICK);

}

除了图像预览以外还有很多其他使用场景,比如图像拍摄,图像分析和视频录制。CameraX将这些使用场景统一抽象为UseCase。

它有四个子类,分别为Preview,ImageCapture,ImageAnalysis和VideoCapture。接下来介绍下它们如何使用。

图像拍摄


借助ImageCapture提供的takePicture()可以将图像拍摄下来。支持保存到外部存储空间,当然需要获得external storage的读写权限。

private void takenPictureInternal(boolean isExternal) {

final ContentValues contentValues = new ContentValues();

contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, CAPTURED_FILE_NAME

+ “_” + picCount++);

contentValues.put(MediaStore.MediaColumns.MIME_TYPE, “image/jpeg”);

ImageCapture.OutputFileOptions outputFileOptions =

new ImageCapture.OutputFileOptions.Builder(

getContentResolver(),

MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)

.build();

if (mImageCapture != null) {

mImageCapture.takePicture(outputFileOptions, CameraXExecutors.mainThreadExecutor(),

new ImageCapture.OnImageSavedCallback() {

@Override

public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {

Toast.makeText(DemoActivityLite.this, “Picture got”

+ (outputFileResults.getSavedUri() != null

? " @ " + outputFileResults.getSavedUri().getPath()

: “”) + “.”, Toast.LENGTH_SHORT)

.show();

}

});

}

}

private void bindPreview(@NonNull ProcessCameraProvider cameraProvider,

PreviewView previewView) {

mImageCapture =  new ImageCapture.Builder()

.setTargetRotation(previewView.getDisplay().getRotation())

.build();

// 需要将ImageCapture场景一并绑定

mCamera = cameraProvider.bindToLifecycle(this, cameraSelector, mPreview, mImageCapture);

}

图像分析


图像分析指的是对预览的图像实时分析,将色彩,内容等信息识别出来,应用在机器学习,二维码识别等业务场景。

继续对demo做些改造,添加扫描二维码的按钮。点击按钮后进入扫码模式,并在二维码解析成功后弹出解析结果。

public void onAnalyzeGo(View view) {

if (!isAnalyzing) {

mImageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), image -> {

analyzeQRCode(image);

});

}

}

// 从ImageProxy取出图像数据,交由二维码框架zxing解析

private void analyzeQRCode(@NonNull ImageProxy imageProxy) {

ByteBuffer byteBuffer = imageProxy.getPlanes()[0].getBuffer();

byte[] data = new byte[byteBuffer.remaining()];

byteBuffer.get(data);

BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

Result result;

try {

result = multiFormatReader.decode(bitmap);

}

showQRCodeResult(result);

imageProxy.close();

}

private void showQRCodeResult(@Nullable Result result) {

if (binding != null && binding.qrCodeResult != null) {

binding.qrCodeResult.post(() ->

binding.qrCodeResult.setText(result != null ? “Link:\n” + result.getText() : “”));

binding.qrCodeResult.playSoundEffect(SoundEffectConstants.CLICK);

}

}

视频录制


依托VideoCapture的startRecording()可以进行视频录制。

在demo上添加一个图像拍摄和视频录制模式的切换按钮,切换到视频录制模式的时候将视频拍摄的UseCase綁定到CameraProvider。

public void onVideoGo(View view) {

bindPreview(mCameraProvider, binding.previewView, isVideoMode);

}

private void bindPreview(@NonNull ProcessCameraProvider cameraProvider,

PreviewView previewView, boolean isVideo) {

mVideoCapture = new VideoCapture.Builder()

.setTargetRotation(previewView.getDisplay().getRotation())

.setVideoFrameRate(25)

.setBitRate(3 * 1024 * 1024)

.build();

cameraProvider.unbindAll();

if (isVideo) {

mCamera = cameraProvider.bindToLifecycle(this, cameraSelector,

mPreview, mVideoCapture);

} else {

mCamera = cameraProvider.bindToLifecycle(this, cameraSelector,

mPreview, mImageCapture, mImageAnalysis);

}

mPreview.setSurfaceProvider(previewView.getSurfaceProvider());

}

点击录制按钮后首先确保获得外部存储和audio权限,之后再开始视频的录制。

public void onCaptureGo(View view) {

if (isVideoMode) {

if (!isRecording) {

// Check permission first.

ensureAudioStoragePermission(REQUEST_STORAGE_VIDEO);

}

}

}

private void ensureAudioStoragePermission(int requestId) {

if (requestId == REQUEST_STORAGE_VIDEO) {

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)

!= PackageManager.PERMISSION_GRANTED

|| ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)

!= PackageManager.PERMISSION_GRANTED) {

ActivityCompat.requestPermissions(…);

return;

}

recordVideo();

}

}

private void recordVideo() {

try {

mVideoCapture.startRecording(

new VideoCapture.OutputFileOptions.Builder(getContentResolver(),

MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues)

.build(),

CameraXExecutors.mainThreadExecutor(),

new VideoCapture.OnVideoSavedCallback() {

@Override

public void onVideoSaved(@NonNull VideoCapture.OutputFileResults outputFileResults) {

// Notify user…

}

}

);

}

toggleRecordingStatus();

}

private void toggleRecordingStatus() {

// Stop recording when toggle to false.

if (!isRecording && mVideoCapture != null) {

mVideoCapture.stopRecording();

}

}

小插曲

实现视频录制功能的时候发现一个问题。

点击视频录制按钮的时候,如果此刻尚未获得audio权限,那么将申请该权限。即便此后获得了权限调用拍摄接口仍将发生异常。日志显示AudioRecorder实例为null引发了NPE。

仔细查看相关逻辑发现,demo现在的处理是在切换为视频录制模式的时候,就将VideoCapture绑定到了CameraProvider。这个时间点如果还未获得audio权限的话,那么将无法初始化AudioRecorder。

其实日志里也会给出相应提示:AudioRecord object cannot initialized correctly。可是后面获得了权限再去调用VideoCapture的拍摄接口为何还是会发生NPE?

因为拍摄接口startRecording()的内部处理是AudioRecorder实例为null的话将直接终止请求。后面无论调用多少遍也无济于事。事实上该函数的后段存在再次获取AudioRecorder实例的逻辑,但因为前面发生了NPE而没有机会执行。

// VideoCapture.java

public void startRecording(

@NonNull OutputFileOptions outputFileOptions, @NonNull Executor executor,

@NonNull OnVideoSavedCallback callback) {

try {

// mAudioRecorder为null将引发NPE终止录制的请求

mAudioRecorder.startRecording();

} catch (IllegalStateException e) {

postListener.onError(ERROR_ENCODER, “AudioRecorder start fail”, e);

return;

}

mRecordingFuture.addListener(() -> {

if (getCamera() != null) {

// 前面发生了NPE,那么将失去此处再次获得AudioRecorder实例的机会

setupEncoder(getCameraId(), getAttachedSurfaceResolution());

notifyReset();

}

}, CameraXExecutors.mainThreadExecutor());

}

不知道这是VideoCapture实现上的漏洞还是开发者有意为之。但是在明明已经获得了audio权限的情况下调用录制接口却仍然发生NPE貌似并不合理。

当下只能采取一些回避方案,或者说开发者本该就这么做?

现在是在获得了audio权限前执行了VideoCapture的绑定,这存在发生上述反复NPE的可能。所以改成获得audio权限后再绑定VideoCapture即可回避。

话说回来,在VideoCaptue的文档里加上需要获得audio的权限的说明是不是更好一些呢?

相机效果扩展


光有上述几个场景的使用并不能满足日益丰富的拍摄需求,人像,夜拍,美颜等相机效果是必不可少的。幸好CameraX是支持效果扩展的。但不是所有设备都能兼容这种扩展,具体可在官网的设备兼容列表里查询到。

可供扩展的效果主要分为两大类。

一个是用于图像预览时效果扩展的PreviewExtender,另一个是用于图像拍摄时效果扩展的ImageCaptureExtender。

结尾

最后,针对上面谈的内容,给大家推荐一个Android资料,应该对大家有用。

首先是一个知识清单:(对于现在的Android及移动互联网来说,我们需要掌握的技术)

泛型原理丶反射原理丶Java虚拟机原理丶线程池原理丶
注解原理丶注解原理丶序列化
Activity知识体系(Activity的生命周期丶Activity的任务栈丶Activity的启动模式丶View源码丶Fragment内核相关丶service原理等)
代码框架结构优化(数据结构丶排序算法丶设计模式)
APP性能优化(用户体验优化丶适配丶代码调优)
热修复丶热升级丶Hook技术丶IOC架构设计
NDK(c编程丶C++丶JNI丶LINUX)
如何提高开发效率?
MVC丶MVP丶MVVM
微信小程序
Hybrid
Flutter

接下来是资料清单:(敲黑板!!!


1.数据结构和算法

2.设计模式

3.全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记

4.面试专题资料包(怎么能少了一份全面的面试题总结呢~)

不论遇到什么困难,都不应该成为我们放弃的理由!共勉~

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

敲黑板!!!**)


1.数据结构和算法

[外链图片转存中…(img-p9HDAiRL-1715808763160)]

2.设计模式

[外链图片转存中…(img-MrCZ8OOa-1715808763160)]

3.全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记

[外链图片转存中…(img-drKl1rnv-1715808763160)]

4.面试专题资料包(怎么能少了一份全面的面试题总结呢~)

[外链图片转存中…(img-LlVgvcjE-1715808763161)]

不论遇到什么困难,都不应该成为我们放弃的理由!共勉~

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

[外链图片转存中…(img-xpI2JWIt-1715808763161)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值