通过 Camera API 2 捕获图片并将其保存为位图

Posted

技术标签:

【中文标题】通过 Camera API 2 捕获图片并将其保存为位图【英文标题】:Capture picture through Camera API 2 and save it as a Bitmap 【发布时间】:2018-09-26 14:16:55 【问题描述】:

我正在开发一个使用相机拍照然后在另一个活动中显示的应用程序,我尝试了以下代码

public void onImageAvailable(ImageReader imageReader) 
        Image image = imageReader.acquireLatestImage();
        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
        byte[] bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
        Bitmap myBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, null);
        SetBitmap setBitmap = new SetBitmap();
        setBitmap.setMbitmap(myBitmap);
        image.close();
    

在此之后,我在像这样的其他活动中获得我的位图

    myBitmap = CamActivity.setBitmap.getMbitmap();
    ImageView myImage = (ImageView) findViewById(R.id.imageView);
    myImage.setImageBitmap(myBitmap);

SetBitmap 是一个设置位图的类

当我点击它时,我有一个捕捉摄像头的按钮,它假设拍照并启动另一个活动来显示图像,但它随后崩溃 那么我现在该怎么办我以错误的方式开始我的活动?或者问题出在位图上? 这是错误日志

                                                                         java.lang.RuntimeException: Unable to start activity ComponentInfocom.example.android.camtest/com.example.android.camtest.DisplayActivity: java.lang.NullPointerException: Attempt to invoke virtual method 'android.graphics.Bitmap com.example.android.camtest.SetBitmap.getMbitmap()' on a null object reference
                                                                             at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2693)
                                                                             at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2758)
                                                                             at android.app.ActivityThread.access$900(ActivityThread.java:177)
                                                                             at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1448)
                                                                             at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                             at android.os.Looper.loop(Looper.java:145)
                                                                             at android.app.ActivityThread.main(ActivityThread.java:5942)
                                                                             at java.lang.reflect.Method.invoke(Native Method)
                                                                             at java.lang.reflect.Method.invoke(Method.java:372)
                                                                             at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1400)
                                                                             at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1195)
                                                                          Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.graphics.Bitmap com.example.android.camtest.SetBitmap.getMbitmap()' on a null object reference
                                                                             at com.example.android.camtest.DisplayActivity.onCreate(DisplayActivity.java:37)
                                                                             at android.app.Activity.performCreate(Activity.java:6283)
                                                                             at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1119)
                                                                             at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2646)
                                                                             at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2758) 
                                                                             at android.app.ActivityThread.access$900(ActivityThread.java:177) 
                                                                             at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1448) 
                                                                             at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                                             at android.os.Looper.loop(Looper.java:145) 
                                                                             at android.app.ActivityThread.main(ActivityThread.java:5942) 
                                                                             at java.lang.reflect.Method.invoke(Native Method) 
                                                                             at java.lang.reflect.Method.invoke(Method.java:372) 
                                                                             at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1400) 
                                                                             at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1195) 

                                                                       java.lang.IllegalStateException: Handler (android.media.ImageReader$ListenerHandler) b9cb033 sending message to a Handler on a dead thread
                                                                           at android.os.MessageQueue.enqueueMessage(MessageQueue.java:325)
                                                                           at android.os.Handler.enqueueMessage(Handler.java:631)
                                                                           at android.os.Handler.sendMessageAtTime(Handler.java:600)
                                                                           at android.os.Handler.sendMessageDelayed(Handler.java:570)
                                                                           at android.os.Handler.sendEmptyMessageDelayed(Handler.java:534)
                                                                           at android.os.Handler.sendEmptyMessage(Handler.java:519)
                                                                           at android.media.ImageReader.postEventFromNative(ImageReader.java:511)
                                                                           at android.hardware.camera2.legacy.LegacyCameraDevice.nativeProduceFrame(Native Method)
                                                                           at android.hardware.camera2.legacy.LegacyCameraDevice.produceFrame(LegacyCameraDevice.java:516)
                                                                           at android.hardware.camera2.legacy.RequestThreadManager$2.onPictureTaken(RequestThreadManager.java:224)
                                                                           at android.hardware.Camera$EventHandler.handleMessage(Camera.java:1142)
                                                                           at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                           at android.os.Looper.loop(Looper.java:145)
                                                                           at android.hardware.camera2.legacy.CameraDeviceUserShim$CameraLooper.run(CameraDeviceUserShim.java:144)
                                                                           at java.lang.Thread.run(Thread.java:818)

这是我拍照的地方

private void takePic() 
    if (cameraDevice == null)
        return;
    CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    try 
        CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraDevice.getId());
        Size[] jpegSizes = null;
        if (characteristics != null)
            jpegSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
                    .getOutputSizes(ImageFormat.JPEG);

        int width = 700;
        int height = 600;

        if (jpegSizes != null && jpegSizes.length > 0) 
            width = jpegSizes[0].getWidth();
            height = jpegSizes[0].getHeight();
        
        final ImageReader reader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
        List<Surface> outputSurface = new ArrayList<>(2);
        outputSurface.add(reader.getSurface());
        outputSurface.add(new Surface(textureView.getSurfaceTexture()));

        final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        captureBuilder.addTarget(reader.getSurface());
        captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);

        int rotation = getWindowManager().getDefaultDisplay().getRotation();
        captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));


        String fileName = "IMG_" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + ".jpg";

        File captureDirectory = new File("/Download");

        if (!captureDirectory.isDirectory()) captureDirectory.mkdirs();
        final File mediaFile = new File(captureDirectory, fileName);

        final Uri fileUri = FileProvider.getUriForFile(this,
                "com.mydomain.fileprovider",
                mediaFile);


        ImageReader.OnImageAvailableListener readerListener = new ImageReader.OnImageAvailableListener() 
            @Override
            public void onImageAvailable(ImageReader imageReader) 

                Image mImage = imageReader.acquireLatestImage();
                ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
                try 
                    save(bytes);
                 catch (IOException e) 
                    e.printStackTrace();
                
                OutputStream output = null;
                try 
                    output = getContentResolver().openOutputStream(fileUri);
                    output.write(bytes);
                 catch (IOException e) 
                    e.printStackTrace();
                 finally 
                    mImage.close();
                    if (output != null) 
                        try 
                            output.close();
                         catch (IOException e) 
                            e.printStackTrace();
                        
                    
                


            

            private void save(byte[] bytes) throws IOException 
                OutputStream output = null;
                try 
                    output = new FileOutputStream(mediaFile);
                    output.write(bytes);
                 finally 
                    if (output != null)
                        output.close();
                

            
        ;
        Intent displayIntent = new Intent(this, DisplayActivity.class);
        displayIntent.putExtra("FILE_URI", fileUri);

        Log.e("uri", fileUri + "");


        startActivity(displayIntent);

        reader.setOnImageAvailableListener(readerListener, mBackgroundHandler);

        final CameraCaptureSession.CaptureCallback captureListener = new CameraCaptureSession.CaptureCallback() 
            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) 
                super.onCaptureCompleted(session, request, result);
                Toast.makeText(CamActivity.this, "Saved ", Toast.LENGTH_SHORT).show();

            
        ;


     catch (CameraAccessException e) 
        e.printStackTrace();
    

这是显示活动

   @Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_display);
    imageView = findViewById(R.id.imageView);

    bundleExtras = getIntent().getExtras();
    if(bundleExtras != null)
        Uri savedBitmapUri = bundleExtras.getParcelable("FILE_URI");
        imageView.setImageURI(savedBitmapUri);
        Log.e("uri",savedBitmapUri+"");
     else 
        Toast.makeText(this, "null pic", Toast.LENGTH_SHORT).show();

    


文件提供者

<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="myDownloads" path="Download" />
<root-path name="sdcard1" path="." /> </paths>

【问题讨论】:

你能发布你的错误日志吗? @ShubhamSrivastava 确定我更新了我的问题 尝试发布的解决方案 【参考方案1】:

您将位图数据传递给另一个活动的方法是错误的。首先,您应该检查Android Activity Lifecycle。当你启动不同的 Activity 时,我们从 A 调用它 B,A Activity 将被销毁,所有资源都将被销毁。在 Android Activity LifeCycle 文档中,他们提到了这一点:

onDestroy() 回调释放所有尚未被释放的资源 由更早的回调(例如 onStop())释放。

因此,活动销毁后您无法访问变量。您应该做的是,您应该将位图保存到文件中,并通过 Intent extra 传递文件 URI,并从另一个活动的 onCreate 回调中获取 URI。

您可以使用 FileProvider 来保存文件,并通过 Intent 传递提供者 URI,这是最好的“Android kind”方法,或者您可以保存文件的路径共享偏好,并从另一个不稳定且不是最佳实践的活动中读取它。

让我们使用 FileProvider: 您可以检查Android Documentation about FileProvider 来定义它。

// Assume that you added Captures path to your FileProvider's paths.
// Create temporary image file.
String fileName = "IMG_" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + ".jpg";  
File captureDirectory = new File(getFilesDir(), "Captures");
if( !captureDirectory.isDirectory() ) captureDirectory.mkdirs();
File mediaFile = new File(captureDirectory, fileName);

Uri fileUri = FileProvider.getUriForFile(this,
                    "com.mydomain.fileprovider",
                    mediaFile);  

// Save the Bitmap.
public void onImageAvailable(ImageReader imageReader) 
    Image mImage = imageReader.acquireLatestImage();
    ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
    byte[] bytes = new byte[buffer.remaining()];
    buffer.get(bytes);
    OutputStream output = null;
    try 
        output = getContentResolver().openOutputStream(fileUri);
        output.write(bytes);
     catch (IOException e) 
        e.printStackTrace();
     finally 
        mImage.close();
        if (output != null) 
            try 
                output.close();
             catch (IOException e) 
                e.printStackTrace();
            
        
    


// Pass the file uri via intent.
Intent displayIntent = new Intent(this, DisplayActivity.class);
displayIntent.putExtra("FILE_URI", mediaFile);
startActivity(displayIntent);

// Read the uri from started activity and show the bitmap
@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_display);

    ... 
    bundleExtras = getIntent().getExtras();
    if(bundleExtras != null)
        Uri savedBitmapUri = bundleExtras.getParcelable("FILE_URI");
        mImageView_Preview.setImageURI(savedBitmapUri);
    
    else
        // Uri not inside the intent data.
    
    ...

您不应将所有字节作为位图加载到内存中。您只需将捕获的图像保存到文件中,然后传递从应用程序的 FileProvider 创建的文件 Uri,并使用一行代码显示文件 Uri。

基本优化: - 您可以在缓存目录中创建文件 - 完成任务后,您可以使用FileProvider permissions 删除文件。 - 您可以像 Google 在android-Camera2Basic 示例中所做的那样在后台线程上进行保存工作。 - 还有更多...

【讨论】:

我确实尝试过按意图发送位图但不是这种方式,我会尝试一下,非常感谢我们的帮助 我是这样做的,你可以签出我的项目:github.com/birfincankafein/customcamera 我试过这个方法,但是我启动我的活动时它没有显示图像,它没有在设备上保存图片,这是新活动中没有图像的原因吗? 你能给出任何logcat的输出吗?我认为问题与不同的东西有关,因为我使用这种方法在我的 Camera2 api 项目的不同活动中保存图像显示。 Uri savedUri = bundleExtras.getParcelable("FILE_URI");我记录了这个然后 Log.e("uri",savedUri+"");我得到了这个日志 com.example.android.camtest E/uri: null Uri 对不起,如果我给你带来了麻烦,我真的很感谢你的帮助

以上是关于通过 Camera API 2 捕获图片并将其保存为位图的主要内容,如果未能解决你的问题,请参考以下文章

使用camera2 API无需预览即可捕获图片

使用 API 获取 Facebook 头像并将其保存到项目文件夹

使用 Camera2 API 的图片真的很暗

Laravel:如何通过 API postman 上传图像并将其保存到数据库(phpmyadmin)

安卓10(Android10API29)保存图片到相册DCIM/Camera

安卓10(Android10API29)保存图片到相册DCIM/Camera