flutter实践

项目背景

项目需要从钉钉微应用跳转 WPS 打开 word 文档,但是 WPS 只提供了 StartActivity 方式携带参数跳转应用,deeplink 只能打开应用,而钉钉微应用只支持 deeplink ,所以需要搭建一个中间跳转的app工具。

Flutter

1
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。

flutter使用dart语言,是将来谷歌新操作系统 Fuchsia 的主要构建方式,组件使用响应式框架构建,使用组件构建 UI ,组件可以改变状态,提供热加载,最大的特点是同时在iOS和Android系统中开发应用,支持混合开发。

获取 deeplink 路径

在flutter应用中导入 uni_links 包,接收deeplink:

在 pubspec.yaml 文件 dependencies 下增加 uni_links: ^0.1.4 ,记得执行 flutter packages get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class FirstPageState extends State<FirstPage> {

@override
  void initState() {
    ...
      getInitialUri().then((Uri url) {
        print('URL received: $url');
        if (url != null && url.scheme == 'gsoft') {
          map.addAll(url.queryParameters);
        }
      });
    ...
    super.initState();
  }
...
}

执行 getInitialUri 方法可以拿到 deeplink 的路径,获取传递的参数。

通过 StartActivity 方式启动WPS

flutter应用与原生之间交互需要通过插件模式:

定义 plugin,两边 plugin 名需要一致

flutter端:

1
2
3
4
5
6
class FirstPageState extends State<FirstPage> {
...
     const demoPlugin = const MethodChannel('wps.plugin');
     demoPlugin.invokeMethod('interaction', map);
...
}

android端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class MainActivity extends FlutterActivity {

    private static final String CHANNEL = "wps.plugin";

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

        // 跳转wps
        new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall call, MethodChannel.Result result) {
                if (call.method.equals("interaction")) {
                    String fileUrl = call.argument("fileUrl");
                    Bundle bundle = new Bundle();
                    //打开文档参数-打开模式
                    Object openMode = call.argument("OpenMode");
                    if (openMode != null) {
                        bundle.putString(WpsModel.OPEN_MODE, String.valueOf(openMode));
                    }

                    //打开文档参数-打开直接进入编辑模式
                    Object editMode = call.argument("EditMode");
                    if (editMode != null) {
                        bundle.putBoolean("Edit Mode", Boolean.valueOf(editMode.toString()));
                    }

                    //文档记录-删除使用记录
                    Object clearTrace = call.argument("ClearTrace");
                    if (clearTrace != null) {
                        bundle.putBoolean("ClearTrace", Boolean.valueOf(clearTrace.toString()));
                    }

                    //文档初始化参数-自动跳转
                    Object autoJump = call.argument("AutoJump");
                    if (autoJump != null) {
                        bundle.putBoolean("AutoJump", Boolean.valueOf(autoJump.toString()));
                    }

                    //修订相关参数-打开文档是否显示修订批注面板
                    Object showReviewingPaneRightDefault = call.argument("ShowReviewingPaneRightDefault");
                    if (showReviewingPaneRightDefault != null) {
                        bundle.putBoolean("ShowReviewingPaneRightDefault", Boolean.valueOf(showReviewingPaneRightDefault.toString()));
                    }

                    //修订相关参数-修订模式打开文档
                    Object enterReviseMode = call.argument("EnterReviseMode");
                    if (enterReviseMode != null) {
                        bundle.putBoolean("EnterReviseMode", Boolean.valueOf(enterReviseMode.toString()));
                    }

                    //水印相关参数-设置水印文字内容
                    Object waterMaskText = call.argument("WaterMaskText");
                    if (waterMaskText != null) {
                        bundle.putString("WaterMaskText", String.valueOf(waterMaskText));
                    }

                    //批注-批注的作者
                    Object userName = call.argument("UserName");
                    if (userName != null) {
                        bundle.putString("UserName", String.valueOf(userName));
                    }

                    //文件保存时发送广播
                    bundle.putBoolean("SendSaveBroad", true);


                    Intent intent = new Intent();
                    File file = new File(fileUrl);

                    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                    Uri contentUri = FileProvider.getUriForFile(MainActivity.this,
                            BuildConfig.APPLICATION_ID + ".provider", file);
                    intent.setData(contentUri);
                    intent.putExtras(bundle);

                    intent.setAction(android.content.Intent.ACTION_VIEW);
                    intent.setClassName(WpsModel.PackageName.PRO, WpsModel.ClassName.NORMAL);
                    MainActivity.this.startActivity(intent);

                    result.success("success");
                } else {
                    result.notImplemented();
                }
            }
        });

        GeneratedPluginRegistrant.registerWith(this);
    }

}

WPS保存文件操作后广播

原生安卓端接收 WPS 广播后,传递给 flutter 处理。

android端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
...
    private static final String SAVED_CHANNEL = "saved.wps.plugin";
...
        //接收wps广播
        new EventChannel(getFlutterView(), SAVED_CHANNEL)
                .setStreamHandler(new EventChannel.StreamHandler() {
                    private BroadcastReceiver savedReceiver;

                    @Override
                    public void onListen(Object o, EventChannel.EventSink eventSink) {
                        savedReceiver = createSavedReceiver(eventSink);
                        IntentFilter intentFilter = new IntentFilter();
                        intentFilter.addAction("cn.wps.moffice.broadcast.AfterSaved");
                        registerReceiver(savedReceiver, intentFilter);
                    }

                    @Override
                    public void onCancel(Object o) {
                        unregisterReceiver(savedReceiver);
                        savedReceiver = null;
                    }
                });
...

   private BroadcastReceiver createSavedReceiver(final EventChannel.EventSink events) {
        return new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (intent.getBooleanExtra(SAVE_AS, false)) {
                    events.success(intent.getStringExtra("CurrentPath"));
                }
            }
        };
    }

flutter端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  static const fromAndroidPlugin = const EventChannel('saved.wps.plugin');

...
  void _fromAndroidPlugin() {
    fromAndroidPlugin
        .receiveBroadcastStream()
        .listen(_onFromAndroidEvent, onError: _onFromAndroidError);
  }

  void _onFromAndroidEvent(Object event) {
    print("文件修改保存路径:" + event);
  }

  void _onFromAndroidError(Object error) {
    print(error);
  }
...