Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fails to connect to camera on specific models when returning to fragment #68

Closed
Moonbloom opened this issue Apr 14, 2017 · 7 comments
Closed

Comments

@Moonbloom
Copy link

Moonbloom commented Apr 14, 2017

I'm having an issue with the library throwing an exception.

My setup is as follows:
Activity hosts Fragment1 (which has the CameraView)
When image is taken, Activity adds Fragment2 (which shows an ImageView with the newly taken image).
This works fine, but when i press back (and pop the backstack to return to Fragment1), then i get the following exception:

04-14 19:53:45.447 22185-23113/xx.y.zz E/CameraKit: Starting camera failed
                                                                    java.lang.RuntimeException: Fail to connect to camera service
                                                                        at android.hardware.Camera.native_setup(Native Method)
                                                                        at android.hardware.Camera.<init>(Camera.java:413)
                                                                        at android.hardware.Camera.open(Camera.java:366)
                                                                        at com.flurgle.camerakit.Camera1.openCamera(Camera1.java:319)
                                                                        at com.flurgle.camerakit.Camera1.start(Camera1.java:89)
                                                                        at com.flurgle.camerakit.CameraView$3.run(CameraView.java:229)
                                                                        at java.lang.Thread.run(Thread.java:841)

And afterwards, the log is constantly spammed with this:

04-14 19:53:45.467 22185-22546/xx.y.zz E/BufferQueue: [unnamed-22185-1] dequeueBuffer: BufferQueue has been abandoned!
04-14 19:53:45.467 1935-22956/? E/SecCameraHardware: Could not dequeue gralloc buffer!
04-14 19:53:45.467 1935-22956/? E/ISecCameraHardware: bool android::ISecCameraHardware::previewThreadEX()::flushSurface() fail

The above 3 lines are spammed constantly until i close the Activity.
No matter what i call with regard to stop/start the camera while this is happening has any effect.

I'm able to reproduce the issue every single time on a Samsung Galaxy S3 (GT-I9300) running Android 4.3.
I also have a OnePlus Three (running Android 7.1) which has absolutely no issues.

I'm willing to help out and test any potential fixes, so i'll check this post regularly.

@suomi35
Copy link
Contributor

suomi35 commented Apr 16, 2017

I'm having this issue as well on some devices running Android 4.4.2.
Perhaps you could provide a way to catch something more specific than a RuntimeException so we can act upon the UI accordingly (with a "Camera not available at this time" message, etc).
Or maybe there is already a mechanism for this?

@android-dataticket
Copy link
Contributor

so here is my work around to avoiding crash on older API devices with this library in a fragment - basically just need to give a little bit more time for stuff to settle before we call the camera to start.. also i start my fragment in a viewpager so to save battery, i don't let the camera start unless the fragment is visible

this is overriding the onPause() and onResume() along with setUserVisibleHint()

Thread delayThread0;
Thread delayThread1;

@Override
public void onResume() {
    super.onResume();
    if(getUserVisibleHint()) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                delayThread0 = Thread.currentThread();
                try {
                    delayThread0.sleep(500);
                    CameraActivity.this.cameraView.start();
                }catch(Exception e){
                    e.printStackTrace();
                }

            }
        }).start();
    }else{
        if(delayThread0 != null) {
            if (delayThread0.isAlive()) {
                delayThread0.interrupt();
            }
        }
        if(delayThread1 != null) {
            if (delayThread1.isAlive()) {
                delayThread1.interrupt();
            }
        }
        cameraView.stop();
    }
}

@Override
public void onPause() {
    if(delayThread0 != null) {
        if (delayThread0.isAlive()) {
            delayThread0.interrupt();
        }
    }
    if(delayThread1 != null) {
        if (delayThread1.isAlive()) {
            delayThread1.interrupt();
        }
    }
    cameraView.stop();
    super.onPause();
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if(cameraView != null) {
        if (!isVisibleToUser) {
            if(delayThread0 != null) {
                if (delayThread0.isAlive()) {
                    delayThread0.interrupt();
                }
            }
            if(delayThread1 != null) {
                if (delayThread1.isAlive()) {
                    delayThread1.interrupt();
                }
            }
            cameraView.stop();
        }
        if (isVisibleToUser) {
            if(delayThread0 != null){
                if(!delayThread0.isAlive()){
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            delayThread1 = Thread.currentThread();
                            try {
                                delayThread1.sleep(500);
                                CameraActivity.this.cameraView.start();
                            }catch(Exception e){
                                e.printStackTrace();
                            }
                        }
                    }).start();
                }else{
                    //let thread 0 start camera
                }
            }else{
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        delayThread1 = Thread.currentThread();
                        try {
                            delayThread1.sleep(500);
                            CameraActivity.this.cameraView.start();
                        }catch(Exception e){
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
    }
}

@tikimcfee
Copy link

tikimcfee commented Apr 19, 2017

Hey @suomi35 , I just wanted to add a comment about the above. In #68, @android-dataticket mentions there (and above!) about how to workaround the problem this library has at the moment. Rather than implement these threads, I found it much easier to pull the repo, import the module with the tiny modification of mCameraImpl.start(); running on the main thread. The above thread manipulation is almost guaranteed to cause heartache in the future with debugging and lifecycle issues, so until that fix gets in, give it a pull + modify! Even better, you can make a PR =)

@khltrifork
Copy link

@tikimcfee
I have implemented the .start() modification, along with a few other things in my own fork of this library.
It still fails like i posted in my original post.

@android-dataticket
That looks like an ugly hack i must admit.
It'll end up with a bad user experience, as there will be a delay each time the camera is starting, and it will also be a hell to debug with all those threads.

I've refactored my code into using 2 activities instead of 1 activity and 2 fragments, and this works absolutely perfect. (Without using any ugly hacks, except that not everyone can use activities instead of fragments of course)
Something with the Fragment lifecycle is not working well together with this library.

@android-dataticket
Copy link
Contributor

@khltrifork it's a very ugly hack, but it's been working great :) yea I have to use it in a fragment so I have to get creative, the user experience is fine as it's only a max half a second delay and the fragment I've created with this library is 10x faster than using the phones default camera app to snap and store a photo.

@android-dataticket
Copy link
Contributor

@tikimcfee what is your exact code change to .start()?

@bajicdusko
Copy link

bajicdusko commented May 27, 2017

As @tikimcfee suggested, I've forked repo and modified CameraView#start() from

new Thread(new Runnable() {
            @Override
            public void run() {
                mCameraImpl.start();
            }
        }).start();

to

mCameraImpl.start();

however, I'd still like to run camera initialization on a separate thread and in my app, I've implemented camera start by using RxJava retryWhen and delay running on Schedulers#newThread.
My idea was to allow an instant call to camera start but to retry for 10 times with 100ms delay between retries.

CompositeDisposable disposables = new CompositeDisposable();
...
Disposable disposable = Flowable.fromCallable(() -> {
            cvMain.start();
            return true;
        })
                .subscribeOn(Schedulers.newThread())
                .doOnError(throwable -> Timber.d("Error occurred. " + throwable.getClass().getName()))
                .retryWhen(new RXUtil.RetryWithDelay(10, 100))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(a -> {
                    //camera is started, do something if you'd like to
                }, throwable -> {
                    //handle failure case (after 10 retries)
                });

disposables.add(disposable);

Don't forget to dispose everything in onPause() since we don't want to retry if fragment is popped in the meantime.

@Override
    public void onPause() {
        disposables.dispose();
        cvMain.stop();
        cvMain.setCameraListener(null);
        super.onPause();
    }

I know it's far from perfect solution, and in the case of multiple retries, UX will be bad. However, it's more like the patch in case of failure. In my case, camera start fails sometimes when the camera was active in one fragment and replacing that fragment with another that also activates the camera.
Really can't think of anything better at the moment.

As for the RetryWithDelay, here is the implementation (which can be found on SO as well)

public static class RetryWithDelay implements Function<Flowable<? extends Throwable>, Flowable<?>> {
        private final int maxRetries;
        private final int retryDelayMillis;
        private int retryCount;

        public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
            this.maxRetries = maxRetries;
            this.retryDelayMillis = retryDelayMillis;
            this.retryCount = 0;
        }

        @Override
        public Flowable<?> apply(final Flowable<? extends Throwable> attempts) {
            return attempts
                    .flatMap(new Function<Throwable, Flowable<?>>() {
                        @Override
                        public Flowable<?> apply(final Throwable throwable) {
                            if (++retryCount < maxRetries) {
                                // When this Observable calls onNext, the original
                                // Observable will be retried (i.e. re-subscribed).
                                return Flowable.timer(retryDelayMillis,
                                        TimeUnit.MILLISECONDS);
                            }

                            // Max retries hit. Just pass the error along.
                            return Flowable.error(throwable);
                        }
                    });
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants