Flutter在平台方法调用中异步返回结果

3,156 阅读2分钟

Flutter在平台方法调用中异步返回结果

asynchronous_method_channel是一个在Flutter和Native之间异步调用方法时,支持异步返回结果的插件。

在利用Flutter编写跨平台应用时,一些功能需要调用Native方法才能实现,可以利用Flutter为我们提供的MethodChannel实现。对与Flutter来说,所有的Native方法调用都是异步返回的,但是对于Native来说,对于来自Flutter的方法调用,我们要返回的结果却不能异步返回,如果我们尝试在执行异步操作之后调用result.success(something),执行时会得到java.lang.IllegalStateException: Reply already submitted的错误信息。所以AsynchronousMethodChannel是对MethodChannel的一次封装,为其添加了异步返回方法调用结果的功能。因为对于Flutter,和Native的通信即方法调用必须在平台主线程,但我们又知道应该尽量避免在主线程进行耗时操作,所以需要有一个措施来支持平台方法调用时异步返回结果,即编写本库的目的。

关于asynchronous_method_channel

Package: pub.dev/packages/as…

Example: github.com/microtears/…

Repository: github.com/microtears/…

Documentation: pub.dev/documentati…

View/report issues: github.com/microtears/…

Flutter配置

  • version: v1.9.1+hotfix.6
  • channel: master

在Android上通过AsynchronousMethodChannel和kotlin编写插件

下面是一个利用kotlin协程执行异步任务并返回结果的示例。

在正式开始之前,需要我们了解的一件事是:Flutter应用程序中Android模块的gradle不会自动导入我们需要的Java包,您必须手动添加以下代码。

import io.flutter.plugins.asynchronous_method_channel.AsynchronousMethodChannel
class MainActivity: FlutterActivity() , AsynchronousMethodChannel.MethodCallHandler {
    companion object{
        const val CHANNEL="AsynchronousMethodChannelExample"
    }
    private var parentJob = Job()
    private val coroutineContext: CoroutineContext
        get() = parentJob + Dispatchers.Main
    private val scope = CoroutineScope(coroutineContext)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        GeneratedPluginRegistrant.registerWith(this)
        AsynchronousMethodChannel(flutterView, CHANNEL).setMethodCallHandler(this)
    }


    override fun onMethodCall(call: MethodCall, result: AsynchronousMethodChannel.Result) {
        when (call.method) {
            "getBatteryLevel" -> {
                result.success(null)
                scope.launch(Dispatchers.IO){
                    // Do something
                    // Perform asynchronous time-consuming tasks

                    // Just return results after 2 seconds
                    delay(2000)

                    // The method in AsynchronousMethodChannel.Result must be called on the main thread of the platform
                    scope.launch(Dispatchers.Main){
                        result.successAsynchronous(getBatteryLevel().toString())
                    }
                }
            }
            else -> result.notImplemented()
        }
    }

    private fun getBatteryLevel(): Int {
        val batteryLevel: Int
        batteryLevel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
            batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
        } else {
            val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
            intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
        }
        return batteryLevel
    }

    override fun onDestroy() {
        // cancel all asynchronous jobs
        scope.cancel()
        super.onDestroy()
    }
}

在Flutter上通过AsynchronousMethodChannel和dart编写插件

下面是一个在Flutter应用程序中使用AsynchronousMethodChannel的示例。

class _MyAppState extends State<MyApp> {
  static final platform =
      AsynchronousMethodChannel('AsynchronousMethodChannelExample');
  String _batteryLevel = 'Unknown';
  String _timeInfo = "";
  static const style = TextStyle(
    fontSize: 16,
    fontFamily: "monospace",
  );

  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String batteryLevel;
    // Platform messages may fail, so we use a try/catch PlatformException.
    try {
      final sb = StringBuffer();
      final startAt = DateTime.now();
      sb.writeln("[start] [$startAt]");
      batteryLevel =
          await platform.invokeAsynchronousMethod("getBatteryLevel");
      final endAt = DateTime.now();
      sb.writeln("[end  ] [$endAt]");
      sb.writeln("[tag  ] [hours:minutes:seconds:us]");
      sb.writeln("[total] [${endAt.difference(startAt)}]");
      _timeInfo = sb.toString();
    } on PlatformException {
      batteryLevel = 'Failed to get platform version.';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('AsynchronousMethodChannel example app'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(20.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text('Battery level: $_batteryLevel\n', style: style),
              Text(_timeInfo, style: style),
              Center(
                child: FlatButton(
                  onPressed: initPlatformState,
                  child: Text("Get battery level"),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

截图

example

在测试时使用AsynchronousMethodChannel

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  final AsynchronousMethodChannel channel =
      AsynchronousMethodChannel('asynchronous_method_channel');

  setUp(() {
    channel.setMockAsynchronousMethodCallHandler(
        (MethodCall methodCall, MockResult result) async {
      switch (methodCall.method) {
        case "asynchronousMethod":
          // Delay 30 milliseconds to return results
          Future.delayed(Duration(milliseconds: 30),
              () => result.success(methodCall.arguments));
          break;
        case "syncMethod":
          return "ok";
          break;
        case "getBatteryLevel":
          result.success("100");
          break;
      }
      return null;
    });
  });

  tearDown(() {
    channel.setMockAsynchronousMethodCallHandler(null);
  });

  test('testMethod', () async {
    expect(
      await channel.invokeAsynchronousMethod(
        "asynchronousMethod",
        {"arg": "arg1"},
      ),
      {"arg": "arg1"},
    );
    expect(
      await channel.invokeMethod("syncMethod"),
      "ok",
    );
    expect(
      await channel.invokeAsynchronousMethod("getBatteryLevel"),
      "100",
    );
  });
}

在IOS上使用AsynchronousMethodChannel

预计在下一个版本发布。

关于更多

请参阅示例