【问题标题】:Android - Camera API takes too much time to capture and save imageAndroid - Camera API 需要太多时间来捕获和保存图像
【发布时间】:2017-02-10 14:41:00
【问题描述】:

我正在尝试为我的应用使用 Camera API。当我点击拍照按钮时,UI 会挂起大约 10 秒钟,然后再进一步响应。

我在 SO here 上找到了一个帖子,但解决方案不是很具体。我认为这是多线程的问题。我是android开发的初学者,不精通多线程。

UploadPrescription.java:

package com.example.ayusch.medomo;


import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.hardware.Camera;
import android.media.ExifInterface;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.ShareCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.util.Size;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.Toast;

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;


/**
 * A simple {@link Fragment} subclass.
 */
public class UploadPrescription extends Fragment {
    private static final int REQUEST_EXTERNAL_STORAGE_PERMISSION = 2;
    private Camera mCamera = null;
    private CameraPreview mCameraPreview;
    private final int REQUEST_CAMERA_PERMISSION = 1;
    private FrameLayout preview;
    private File pictureFile;
    private FileOutputStream fos;
    private byte[] byteArray;

    public UploadPrescription() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        // Inflate the layout for this fragment
        View root = inflater.inflate(R.layout.fragment_upload_prescription, container, false);
        preview = (FrameLayout) root.findViewById(R.id.camera_preview);

        setPreview();

        Button captureButton = (Button) root.findViewById(R.id.button_capture);
        captureButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("ayusch", "inside on click");
                mCamera.takePicture(null, null, mPicture);
            }

        });
        return root;
    }

    private void setPreview() {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
                setup();
            } else {

                if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {

                    Toast.makeText(getContext(), "Permission needed to use camera", Toast.LENGTH_LONG).show();
                }

                requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
            }
        } else {
            setup();
        }

    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        if (requestCode == REQUEST_CAMERA_PERMISSION) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                setup();
            } else {
                Toast.makeText(getContext(), "Camera Permission not granted", Toast.LENGTH_LONG).show();
                return;
            }
        } else if (requestCode == REQUEST_EXTERNAL_STORAGE_PERMISSION) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                proceedtowrite();
            } else {
                Toast.makeText(getContext(), "Storage permission not granted", Toast.LENGTH_LONG).show();
                return;
            }
        }
    }

    public void setup() {

        mCamera = getCameraInstance();
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.set("jpeg-quality", 70);
        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        parameters.setPictureFormat(ImageFormat.JPEG);
        List<Camera.Size> sizes = parameters.getSupportedPictureSizes();
        Camera.Size size = sizes.get(Integer.valueOf((sizes.size() - 1) / 2)); //choose a medium resolution
        parameters.setPictureSize(size.width, size.height);
        mCamera.setParameters(parameters);
        mCameraPreview = new CameraPreview(getContext(), mCamera);
        preview.addView(mCameraPreview);
    }

    private Camera getCameraInstance() {

        Camera camera = null;
        try {
            camera = Camera.open(0);

        } catch (Exception e) {
            e.printStackTrace();
            // cannot get camera or does not exist
        }
        return camera;
    }

    Camera.PictureCallback mPicture = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            Log.i("ayusch", "inside onPictureTaken");

            pictureFile = getOutputMediaFile();

            if (pictureFile == null) {
                Log.i("ayusch", "Null Picture file, returning...");
                return;
            }
            try {
                fos = new FileOutputStream(pictureFile);

                ////////////////////////////////////
                Bitmap realImage = BitmapFactory.decodeByteArray(data, 0, data.length);

                ExifInterface exif = new ExifInterface(pictureFile.toString());

                Log.d("ayusch", exif.getAttribute(ExifInterface.TAG_ORIENTATION));
                if (exif.getAttribute(ExifInterface.TAG_ORIENTATION).equalsIgnoreCase("6")) {
                    realImage = rotate(realImage, 90);
                } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION).equalsIgnoreCase("8")) {
                    realImage = rotate(realImage, 270);
                } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION).equalsIgnoreCase("3")) {
                    realImage = rotate(realImage, 180);
                } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION).equalsIgnoreCase("0")) {
                    realImage = rotate(realImage, 90);
                }
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                realImage.compress(Bitmap.CompressFormat.PNG, 100, stream);
                byteArray = stream.toByteArray();

                ////////////////////////////////////
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                        proceedtowrite();
                    } else {

                        if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {

                            Toast.makeText(getContext(), "Permission needed to store and upload Prescription", Toast.LENGTH_LONG).show();
                        }

                        requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_EXTERNAL_STORAGE_PERMISSION);
                    }
                } else {
                    proceedtowrite();
                }
            } catch (FileNotFoundException e) {

            } catch (IOException e) {
            }
        }
    };

    public void proceedtowrite() {
        try {
            fos.write(byteArray);
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        Intent i = new Intent(getActivity(), PrescriptionUpload.class);
        i.putExtra("prescription", pictureFile);
        startActivity(new Intent(i));
    }

    public static Bitmap rotate(Bitmap bitmap, int degree) {
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();

        Matrix mtx = new Matrix();
        //       mtx.postRotate(degree);
        mtx.setRotate(degree);

        return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
    }

    private static File getOutputMediaFile() {
        Log.i("ayusch", "Getting output media file");
        File mediaStorageDir = new File(
                Environment
                        .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                "MyCameraApp");
        if (!mediaStorageDir.exists()) {
            if (!mediaStorageDir.mkdirs()) {
                Log.d("MyCameraApp", "failed to create directory");
                return null;
            }
        }
        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
                .format(new Date());
        File mediaFile;
        mediaFile = new File(mediaStorageDir.getPath() + File.separator
                + "IMG_" + timeStamp + ".jpg");
        Log.i("ayusch", "Filename = " + mediaFile);
        return mediaFile;
    }

    @Override
    public void onPause() {

        super.onPause();
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCameraPreview.getHolder().removeCallback(mCameraPreview);
            mCamera.stopPreview();
            mCamera.release();

        }
    }

    @Override
    public void onResume() {

        super.onResume();
        try {
            mCameraPreview.getHolder().removeCallback(mCameraPreview);
            setPreview();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

CameraPreview.java:

package com.example.ayusch.medomo;

import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.io.IOException;

/**
 * Created by Ayusch on 09-Feb-17.
 */
public class CameraPreview extends SurfaceView implements
        SurfaceHolder.Callback {
    private SurfaceHolder mSurfaceHolder;
    private Camera mCamera;


    // Constructor that obtains context and camera
    @SuppressWarnings("deprecation")
    public CameraPreview(Context context, Camera camera) {
        super(context);

        this.mCamera = camera;
        this.mSurfaceHolder = this.getHolder();
        this.mSurfaceHolder.addCallback(this);
        this.mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }


    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {

        try {

            mCamera.setPreviewDisplay(surfaceHolder);
            mCamera.setDisplayOrientation(90);
            mCamera.startPreview();
        } catch (IOException e) {
            // left blank for now
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        Log.i("ayusch", "Inside surfaceDestroyed");
        mCamera.stopPreview();
        mCamera.release();
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int format,
                               int width, int height) {

        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(surfaceHolder);
            mCamera.startPreview();
        } catch (Exception e) {
            // intentionally left blank for a test
        }
    }
}

MainActivity.java:

package com.example.ayusch.medomo;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.design.widget.TabLayout;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private static final int REQUEST_CAMERA_PERMISSION = 1;
    Toolbar toolbar;
    TabLayout tabLayout;
    ViewPager viewPager;
    ViewPagerAdapter viewPagerAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
        toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        tabLayout = (TabLayout) findViewById(R.id.tabLayout);
        viewPager = (ViewPager) findViewById(R.id.viewPager);
        viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
        viewPagerAdapter.addFragments(new UploadPrescription(), "Upload Prescription");
        viewPagerAdapter.addFragments(new OrderByName(), "Enter Medicine Name");
        viewPagerAdapter.addFragments(new Contact(), "Contact Us");
        viewPager.setAdapter(viewPagerAdapter);
        tabLayout.setupWithViewPager(viewPager);

    }

}

另外,在挂起期间,如果我再次按下按钮,应用程序会崩溃:

02-10 19:57:31.481 966-966/com.example.ayusch.medomo E/AndroidRuntime: FATAL EXCEPTION: main
                                                                       Process: com.example.ayusch.medomo, PID: 966
                                                                       java.lang.RuntimeException: Camera is being used after Camera.release() was called
                                                                           at android.hardware.Camera.native_takePicture(Native Method)
                                                                           at android.hardware.Camera.takePicture(Camera.java:1523)
                                                                           at android.hardware.Camera.takePicture(Camera.java:1468)
                                                                           at com.example.ayusch.medomo.UploadPrescription$1.onClick(UploadPrescription.java:73)
                                                                           at android.view.View.performClick(View.java:5714)
                                                                           at android.widget.TextView.performClick(TextView.java:10926)
                                                                           at android.view.View$PerformClick.run(View.java:22589)
                                                                           at android.os.Handler.handleCallback(Handler.java:739)
                                                                           at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                           at android.os.Looper.loop(Looper.java:148)
                                                                           at android.app.ActivityThread.main(ActivityThread.java:7325)
                                                                           at java.lang.reflect.Method.invoke(Native Method)
                                                                           at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
                                                                           at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

是由于UploadPrescription.java的onClick方法中mCamera.takePicture(null, null, mPicture);这行

自 2 天以来,我一直在努力解决它,但没有任何成功。请帮忙!!

提前致谢

【问题讨论】:

  • 不清楚 PrescriptionUpload 活动应该做什么。无论如何,为了让您的 UI 响应,您必须在主 (UI) 之外执行耗时的任务(并且您的 onPictureTaken() 是一项耗时的任务)线。 AsyncTask 在这里可能是一个简单的工具。强烈建议也从主线程执行 Camera.open():在某些设备上,它相当快,但在其他设备上,它可能非常慢。
  • 关于捕获过程中的点击,您应该在点击时禁用捕获按钮,并在处理结束时再次启用它。
  • 那么你的意思是我需要将我的 onClick 方法任务移动到 AsyncTask 吗?我的意思是在 AsyncTask 中调用 takePicture 方法。对不起,如果我听起来像个菜鸟(因为我是:??????)
  • 不,不要乱用 onClick()takePicture() 是即时的,仅启动相机捕获的异步过程。最后,相机会触发你的onPictureTaken()。你应该做什么,使用 onPictureTaken() 中的 AsyncTask 来完成所有的处理。本质上,您的 onPictureTaken() 应该只做一件事:准备并调用 AsyncTask。
  • 您应该真正分析您的代码并弄清楚一切需要多长时间。没有它,我们真的无能为力。

标签: java android multithreading camera


【解决方案1】:

我为类似的问题做了一个快速而肮脏的解决方法,我用相机预览填充屏幕,然后在需要时捕获屏幕截图(几乎立即响应) - 这实际上让我基本上可以拍摄一个序列帧速率合理。

希望这足以让您暂时继续。如果没有,请告诉我。

【讨论】:

  • 抱歉,我无法理解您的解决方案。能否请您提及有关程序或其他内容的更多详细信息。
  • 明天会更详细地回复你,我会编辑我的答案,但简单来说,我的解决方案是用相机预览填充整个屏幕,然后当我想拍照时,我只是捕捉什么立即出现在屏幕上 - 请记住,其他任何内容(任何叠加或任何内容)也将作为图像的一部分被捕获。不理想,但对我有用。这听起来适合你吗?
  • 好吧好吧....我得到了你的解决方案,但我需要在我的活动中显示操作栏,因此无法用相机预览覆盖屏幕。你能推荐点别的吗?
  • 能否在抓屏时暂时隐藏操作栏,然后在拍照时重新渲染?
  • 我添加了我正在尝试实现的任务的屏幕截图。我无法隐藏操作栏,因为相机会在启动时立即启动。
【解决方案2】:
     ByteArrayOutputStream stream = new ByteArrayOutputStream();
     realImage.compress(Bitmap.CompressFormat.PNG, 100, stream);
     byteArray = stream.toByteArray();

Bitmap#compress 对于 PNG 格式非常低效(缺乏硬件加速等)。尝试在保存之前调整位图大小、压缩为 JPEG 或使用本机 io 转储原始位图。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 2016-07-26
    • 1970-01-01
    • 2013-09-18
    • 1970-01-01
    相关资源
    最近更新 更多