【问题标题】:Flutter Platform Channels - Invoke channel method on android, hangs the uiFlutter Platform Channels - 在 android 上调用 channel 方法,挂起 ui
【发布时间】:2020-01-31 11:04:38
【问题描述】:

我正在尝试使用以下软件包 https://github.com/arrrrny/tesseract_ocr 在 Flutter 中使用 Tesseract

我已经下载了应用并运行了。

问题是 extractText 挂起 UI。

看Java代码:

  Thread t = new Thread(new Runnable() {
    public void run() {
      baseApi.setImage(tempFile);
      recognizedText[0] = baseApi.getUTF8Text();
      baseApi.end();
    }
  });
  t.start();
  try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); }
  result.success(recognizedText[0]);

我可以看到它正在一个新线程上运行,所以我希望它不会挂起应用程序,但它仍然会挂起。

我找到了这个例子:

            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    // Call the desired channel message here.
                    baseApi.setImage(tempFile);
                    recognizedText[0] = baseApi.getHOCRText(0);
                    baseApi.end();
                    result.success(recognizedText[0]);

                }
            });

来自https://flutter.dev/docs/development/platform-integration/platform-channels#channels-and-platform-threading

但它也会挂起 UI。

文档也说

**Channels and Platform Threading**
Invoke all channel methods on the platform’s main thread when writing code on the platform side.

有人能澄清一下这句话吗?

根据Richard Heap的回答,我尝试从native调用一个方法到dart,传递结果:

飞镖方面:

_channel.setMethodCallHandler((call) {
  print(call);
  switch (call.method) {
    case "extractTextResult":
      final String result = call.arguments;
      print(result);
  }
  var t;
  return t;
});

Java端:

channel.invokeMethod("extractTextResult","hello");

如果我从主线程调用这个方法,这工作正常,但线程阻塞。

如果我这样做

            Thread t = new Thread(new Runnable() {
                public void run() {
                    channel.invokeMethod("extractTextResult","test1231231");

                }
            });
            t.start();

            result.success("tst"); // return immediately

然后应用程序崩溃并显示以下消息:

我也试过了:

           Thread t = new Thread(new Runnable() {
                public void run() {
                    new Handler(Looper.getMainLooper()).post(new Runnable() {
                        @Override
                        public void run() {
                            // Call the desired channel message here.
                            baseApi.setImage(tempFile);
                            recognizedText[0] = baseApi.getHOCRText(0);
                            baseApi.end();
                            result.success(recognizedText[0]);
                            //                                channel.invokeMethod("extractTextResult", "test1231231");
                        }
                    });

                }
            });
            t.start();

            result.success("tst");

这就是我理解 Richard Heap 最后评论的意思,但它仍然挂起 ui。

【问题讨论】:

    标签: multithreading flutter


    【解决方案1】:

    我遇到了同样的问题,并用 TesseractOcrPlugin.java 中的 MethodCallWrapper 修复了它

    此代码适用于我(无需更改 Dart 代码):

    package io.paratoner.tesseract_ocr;
    
    import com.googlecode.tesseract.android.TessBaseAPI;
    
    import android.os.Handler;
    import android.os.Looper;
    import android.os.AsyncTask;
    
    import java.io.File;
    import io.flutter.plugin.common.MethodCall;
    import io.flutter.plugin.common.MethodChannel;
    import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
    import io.flutter.plugin.common.MethodChannel.Result;
    import io.flutter.plugin.common.PluginRegistry.Registrar;
    
    /** TesseractOcrPlugin */
    public class TesseractOcrPlugin implements MethodCallHandler {
    
      private static final int DEFAULT_PAGE_SEG_MODE = TessBaseAPI.PageSegMode.PSM_SINGLE_BLOCK;
    
      /** Plugin registration. */
      public static void registerWith(Registrar registrar) {
        final MethodChannel channel = new MethodChannel(registrar.messenger(), "tesseract_ocr");
        channel.setMethodCallHandler(new TesseractOcrPlugin());
      }
    
      // MethodChannel.Result wrapper that responds on the platform thread.
      private static class MethodResultWrapper implements Result {
        private Result methodResult;
        private Handler handler;
    
        MethodResultWrapper(Result result) {
          methodResult = result;
          handler = new Handler(Looper.getMainLooper());
        }
    
        @Override
        public void success(final Object result) {
          handler.post(new Runnable() {
            @Override
            public void run() {
              methodResult.success(result);
            }
          });
        }
    
        @Override
        public void error(final String errorCode, final String errorMessage, final Object errorDetails) {
          handler.post(new Runnable() {
            @Override
            public void run() {
              methodResult.error(errorCode, errorMessage, errorDetails);
            }
          });
        }
    
        @Override
        public void notImplemented() {
          handler.post(new Runnable() {
            @Override
            public void run() {
              methodResult.notImplemented();
            }
          });
        }
      }
    
      @Override
      public void onMethodCall(MethodCall call, Result rawResult) {
    
        Result result = new MethodResultWrapper(rawResult);
    
        if (call.method.equals("extractText")) {
          final String tessDataPath = call.argument("tessData");
          final String imagePath = call.argument("imagePath");
          String DEFAULT_LANGUAGE = "eng";
          if (call.argument("language") != null) {
            DEFAULT_LANGUAGE = call.argument("language");
          }
          calculateResult(tessDataPath, imagePath, DEFAULT_LANGUAGE, result);
        } else {
          result.notImplemented();
        }
      }
    
      private void calculateResult(final String tessDataPath, final String imagePath, final String language,
          final Result result) {
    
        new AsyncTask<Void, Void, Void>() {
          @Override
          protected Void doInBackground(Void... params) {
            final String[] recognizedText = new String[1];
            final TessBaseAPI baseApi = new TessBaseAPI();
            baseApi.init(tessDataPath, language);
            final File tempFile = new File(imagePath);
            baseApi.setPageSegMode(DEFAULT_PAGE_SEG_MODE);
            baseApi.setImage(tempFile);
            recognizedText[0] = baseApi.getUTF8Text();
            baseApi.end();
            result.success(recognizedText[0]);
            return null;
          }
    
          @Override
          protected void onPostExecute(Void result) {
            super.onPostExecute(result);
          }
        }.execute();
      }
    
    }
    

    【讨论】:

      【解决方案2】:

      通过使用join,你让主线程等待后台线程,阻塞它。您必须删除连接并立即返回结果。

      那么,你如何返回 ocr 结果,它不会立即可用。当它可用时,您然后调用从 native 到 dart 的方法,并传递结果。在 dart 结束时,您将结果作为任何异步事件处理。

      您问题的最后一段的重点是您的结果将在您的后台线程上可用,因此您希望在那里调用 native to dart 方法。你不能。您必须将方法调用代码发布到主循环器 - 您已经展示了一些用于发布到主循环器的代码,您可以将其用作示例。

      【讨论】:

      • 我无法根据您的回答解决,请澄清。我将在问题正文中描述我的尝试
      【解决方案3】:

      根据Richard Heap 的回答我想出了这个:

      飞镖代码:

      _channel.setMethodCallHandler((call) {
        switch (call.method) {
          case "extractTextResult":
            final String result = call.arguments;
            print(result);
        }
        var t;
        return t;
      });
      

      Java 代码:

                  Thread t = new Thread(new Runnable() {
                      public void run() {
                          baseApi.setImage(tempFile);
                          recognizedText[0] = baseApi.getHOCRText(0);
                          baseApi.end();
                          new Handler(Looper.getMainLooper()).post(new Runnable() {
                              @Override
                              public void run() {
                                  channel.invokeMethod("extractTextResult", recognizedText[0]);
                              }
                          });
                      }
                  });
                  t.start();
      
                  result.success("tst");
      

      解释: 此代码将在单独的线程中运行 Java extractText,当结果准备好时,它将 hopp 返回 ui 线程并调用 Looper.getMainLooper(),然后将消息发送回 Dart 端,后者必须接收ui线程上的消息,这就是该消息的含义:

      **Channels and Platform Threading**
      Invoke all channel methods on the platform’s main thread when writing code on the platform side.
      

      注意在 Dart 方面,这仍然是一个不完整的例子,因为你需要向 ui 报告收到了一条消息,这可以通过 Completer 来完成,它用于 createcomplete future

      【讨论】:

      • 看起来不错。一个好的模式是在 Dart 端提供一个 init 方法,该方法执行 setMethodCallHandler 调用。您可以将回调传递给记住的 init 方法,然后在处理 extractTextResult 方法时,您可以将结果传递给调用者的回调 - 而不仅仅是 print(result)
      猜你喜欢
      • 2020-03-03
      • 2018-10-05
      • 2021-03-18
      • 1970-01-01
      • 1970-01-01
      • 2019-11-07
      • 1970-01-01
      • 1970-01-01
      • 2018-06-26
      相关资源
      最近更新 更多