NeutrinosPlatform / cordova-plugin-document-scanner

cordova plugin for document scan
https://www.neutrinos.co/
MIT License
85 stars 61 forks source link

Android fatal exception when calling scanDoc for the second time #99

Closed Hamata6 closed 2 years ago

Hamata6 commented 3 years ago

Describe the bug Fatal Exception on Android when calling scanDoc for the second time. java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@4693e70

Stacktrace W/Bitmap: Called hasAlpha() on a recycle()'d bitmap! This is undefined behavior!
Called hasAlpha() on a recycle()'d bitmap! This is undefined behavior!
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
Process: io.ionic.starter, PID: 12600
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@4693e70
at android.graphics.BaseCanvas.throwIfCannotDraw(BaseCanvas.java:77) at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:278) at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:91) at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:548) at android.widget.ImageView.onDraw(ImageView.java:1435) at android.view.View.draw(View.java:23901) at android.view.View.updateDisplayListIfDirty(View.java:22776) at android.view.View.draw(View.java:23631) at android.view.ViewGroup.drawChild(ViewGroup.java:5336) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:5093) at android.view.View.updateDisplayListIfDirty(View.java:22762) at android.view.View.draw(View.java:23631) at android.view.ViewGroup.drawChild(ViewGroup.java:5336) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:5093) at android.view.View.draw(View.java:23904) at android.view.View.updateDisplayListIfDirty(View.java:22776) at android.view.View.draw(View.java:23631) at android.view.ViewGroup.drawChild(ViewGroup.java:5336) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:5093) at android.view.View.updateDisplayListIfDirty(View.java:22762) at android.view.View.draw(View.java:23631) at android.view.ViewGroup.drawChild(ViewGroup.java:5336) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:5093) at android.view.View.updateDisplayListIfDirty(View.java:22762) at android.view.View.draw(View.java:23631) at android.view.ViewGroup.drawChild(ViewGroup.java:5336) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:5093) at android.view.View.updateDisplayListIfDirty(View.java:22762) at android.view.View.draw(View.java:23631) at android.view.ViewGroup.drawChild(ViewGroup.java:5336) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:5093) at android.view.View.updateDisplayListIfDirty(View.java:22762) at android.view.View.draw(View.java:23631) at android.view.ViewGroup.drawChild(ViewGroup.java:5336) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:5093) at android.view.View.draw(View.java:23904) at com.android.internal.policy.DecorView.draw(DecorView.java:1282) at android.view.View.updateDisplayListIfDirty(View.java:22776) at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:579) at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:585) at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:662) at android.view.ViewRootImpl.draw(ViewRootImpl.java:5042) at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4749) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3866) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2618) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9965) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1010) at android.view.Choreographer.doCallbacks(Choreographer.java:809) at android.view.Choreographer.doFrame(Choreographer.java:744) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:995) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:246) at android.app.ActivityThread.main(ActivityThread.java:8512) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130) I/Process: Sending signal. PID: 12600 SIG: 9

To Reproduce Let's make a brand new all clean project to reproduce:

Manually creating new project

1. Make a new Ionic Angular project with Capacitor: `ionic start DocScanIssue blank --type=angular --capacitor`
2. Install this cordova scanner plugin for Capacitor:
`npm install cordova-plugin-document-scanner`
`npm install @ionic-native/core`
`npm install @ionic-native/document-scanner`
3. Make src/app/home/home.page.ts look like this:

import { AfterViewInit, Component } from '@angular/core';
import { DocumentScannerOptions, DocumentScanner } from '@ionic-native/document-scanner';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements AfterViewInit {

  constructor() {}

  ngAfterViewInit() {
    this.makeScanHandler();
  }

  private async makeScanHandler() {
    //make infinite scans until error, just as example
    let scanResult = false;
    do {
      scanResult = await this.makeScan();
    } while (scanResult);
  }

  private makeScan(): Promise {
    const opts: DocumentScannerOptions = {
      sourceType : 1,
      fileName : "image",
      quality : 3,
      returnBase64 : false
    };
    return DocumentScanner.scanDoc(opts)
      .then((res) => {
        console.log("res = ", res);
        return true;
      })
      .catch((error) => {
        console.log("catched");
        console.log(error);
        return false;
      });
  }
}
4. Run `ionic cap sync`
5. Generate Android project: `ionic cap add android`
6. Open the Android project with Android Studio: `ionic cap open android`
7. If your physical device is Android 10+, set targetSdkVersion to 28 in Gradle Scripts/variables.gradle (don't forget to press Sync now) to bypass the storage permission issue for now (default targets 29)
8. Connect a physical device with USB debugging enabled, select it from the devices list, and run the project (I haven't tested it on emulator)
9. Try to make a second scan

Or just clone this repo in which I did all the work already ;) https://github.com/Hamata6/DocScanIssue Execute npm i, ionic cap sync, ionic cap open android Connect a physical device with USB debugging enabled, select it from the devices list, and run the project (I haven't tested it on emulator) Try to make a second scan

Expected behavior Being able to make a second (and more) scan. The second time the native Android camera activity opens, but the activity in the background crashes.

Smartphone (please complete the following information):

Hamata6 commented 3 years ago

I just found out that sometimes it happens on the second scan, sometimes on the third scan

ChrisTomAlx commented 3 years ago

Could you try with this version of the plugin? https://github.com/NeutrinosPlatform/cordova-plugin-document-scanner/issues/74#issuecomment-777267803

Cheers and have a nice day :) Chris Neutrinos

Hamata6 commented 3 years ago

Thanks for your quick response @ChrisTomAlx. I did. However I'm using Capacitor so cordova plugin commands won't work. I did update it to the beta.1 with editing the package.json to contain "cordova-plugin-document-scanner": "^5.0.0-beta.1"and rerun npm i. It still gives me the same error (after i had encountered and fixed the same issue as #96 )

Hamata6 commented 3 years ago

@ChrisTomAlx I updated my repo to install the beta.1 version. You can try it yourself: https://github.com/Hamata6/DocScanIssue

ChrisTomAlx commented 3 years ago

Could you instead just send me the apk file you are testing with

EDIT: Whoops accidentally closed the issue - My bad sorry...

Cheers and have a nice day :) Chris Neutrinos

Hamata6 commented 3 years ago

Generated a debug app of my repo so you can attach the Android debugger to it: app-debug.apk.zip

Hamata6 commented 3 years ago

I think I found out the issue. When the .then() of .scanDoc() is called, the native library (in Androids case the Java code of the scan-library) is not finished yet. So when calling .scanDoc() in a immediate loop without any delay, the .scanDoc() function gets called again before the native code is finished cleaning up (or something).

I found out that the same was happening on iOS. The iOS simulator opened the camera the first time when calling .scanDoc(), but refused to open the camera a second time.

For example this code works perfectly fine:

  private async makeScanHandler() {
    let scanResult = await this.makeScan();
    if (scanResult) {
      setTimeout(() => {
        this.makeScanHandler();
      }, 500); //Timeout of 500ms
    }
  }

  private makeScan(): Promise<boolean> {
    return DocumentScanner.scanDoc()
      .then((res) => {
        console.log("res = ", res);
        return true;
      })
      .catch((error) => {
        console.log("catch, error = ", error);
        return false;
      });
  }

While the old code without delay does not work:

  private async makeScanHandler() {
    let scanResult = false;
    do {
      scanResult = await this.makeScan();
    } while (scanResult);
  }

  private makeScan(): Promise<boolean> {
    return DocumentScanner.scanDoc()
      .then((res) => {
        console.log("res = ", res);
        return true;
      })
      .catch((error) => {
        console.log("catch, error = ", error);
        return false;
      });
  }

I think I can handle this myself now xD. The remaining question is: Can you make .scanDoc() resolve only when the native library is finished doing its work? Then we don't need a delay for the second scan.