Flutter android端接收外部分享链接

608 阅读2分钟

最近在做Flutter端接收外部分享的需求,中间遇到了很多问题,包括分享的Activity如何配置、Activity样式如何配置、数据如何交给Flutter处理。接收外部分享的示意图:

image.png

AndroidManifest配置

粘一下我的配置:

<activity
            android:name=".OutShareActivity"
            android:exported="true"
            android:launchMode="singleInstance"
            android:theme="@android:style/Theme.Translucent.NoTitleBar"
            android:taskAffinity="com.out.share.affinity"
            android:excludeFromRecents="true">

            <intent-filter android:label="Share to Me"> //分享列表的title
                <action
                    android:name="android.intent.action.SEND"/>
                <category
                    android:name="android.intent.category.DEFAULT"/>
                <category
                    android:name="android.intent.category.BROWSABLE"/>
                <data android:mimeType="text/*"/>
            </intent-filter>
        </activity>
        <activity

首先是intent action的配置,需要加上android.intent.action.SEND,这里我主要解析的是url,所以data type配置 text/* 即可。

其次是launchMode的配置,起初我设置的是singleTask,但是在应用已经打开过MainActivity的情况下会先打开MainAcitivity的栈然后push OutShareActivity,感觉效果怪怪的,所以我这里配置了singleInstance和taskAffinity,让它在一个完全独立的栈显示。

因为要对外暴露,所以exported也要设置为true,不再赘述,还有就是excludeFromRecents,即我希望它不会出现在最近任务栈列表,但是我发现如果OutShareActivity是当前应用唯一打开的Activity时该配置不生效,最近任务列表还是会出现一个透明的Activity-.-

透明Activity配置

FlutterActivity默认会配置两个theme: LaunchTheme和NormalTheme,LaunchTheme直接配置到xml中,表示Activity初始化完成前的样式。还有一个就是NormalTheme,是通过setTheme 代码设置上去的,关于二者的关系官方文档写了:

With the above settings, your launch theme will be used when loading the app, and then the theme will be switched to your normal theme once the app has initialized.
Do not change aspects of system chrome between a launch theme and normal theme. Either define both themes to be fullscreen or not, and define both themes to display the same status bar and navigation bar settings. If you wish to adjust system chrome once your Flutter app renders, use platform channels to instruct Android to do so at the appropriate time. This will avoid any jarring visual changes during app startup.

但是分享Activity似乎没有这种需求,所以我直接在xml中配置android:theme="@android:style/Theme.Translucent.NoTitleBar"如果选择自定义Style,需要注意不能加上windowFullScreen属性,否则会和FlutterActivity的默认设置发生冲突。

Activity设置透明主题还不够,一张图说明:

image.png

如果要实现一个完全透明的界面,除了Activity需要配置外,FlutterView和FlutterWidget也要配置。关于FlutterView的透明化直接看源码:

//FlutterActivity.java
public RenderMode getRenderMode() {
  return getBackgroundMode() == BackgroundMode.opaque ? RenderMode.surface : RenderMode.texture;
}

public TransparencyMode getTransparencyMode() {
  return getBackgroundMode() == BackgroundMode.opaque
      ? TransparencyMode.opaque
      : TransparencyMode.transparent;
}

//FlutterActivityDelegate.java
View onCreateView(
      LayoutInflater inflater,
      @Nullable ViewGroup container,
      @Nullable Bundle savedInstanceState,
      int flutterViewId,
      boolean shouldDelayFirstAndroidViewDraw) {

  if (host.getRenderMode() == RenderMode.surface) {
    FlutterSurfaceView flutterSurfaceView =
        new FlutterSurfaceView(
            host.getContext(), host.getTransparencyMode() == TransparencyMode.transparent);
  } else {
    FlutterTextureView flutterTextureView = new FlutterTextureView(host.getContext());
    flutterTextureView.setOpaque(host.getTransparencyMode() == TransparencyMode.opaque);
  }

  ...
}

FlutterView是否透明关键就在于getBackgroundMode方法的返回值,如果返回transparent那就使用FlutterTextureView, 同时opaque属性也设为false。反之,则使用FlutterSurfaceView。

FlutterWidget的透明化就不赘述了~

最后是statusbar的处理,在FlutterActivity的onCreate方法中默认对Statusbar color做了处理:

//FlutterActivity
onCreate(){
    setContentView(...
    configureStatusBarForFullscreenFlutterExperience()
}

private void configureStatusBarForFullscreenFlutterExperience() {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    Window window = getWindow();
    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
    window.setStatusBarColor(0x40000000); //黑色半透明效果
    window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
  }
}
  • setStatusBarColor API在5.0才推出
  • FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS标志位设置完才能修改statubar color
  • PlatformPlugin.DEFAULT_SYSTEM_UI是两个标志位的联合,View.SYSTEM_UI_FLAG_LAYOUT_STABLE表示状态栏和导航栏变化时内容区域保持稳定,View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN表示activity窗口侵入到状态栏下方

解决这个问题很简单,复写onCreate方法:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Window win = getWindow();
    win.setStatusBarColor(Color.TRANSPARENT);
}

如果导航栏也要设置成透明可以一并设置:window.setNavigationBarColor(Color.TRANSPARENT)

虽然Flutter测也可以设置statusBarColor[AnnotatedRegion/AppBar/SystemChorme],但是起效的时机比较晚,体验不好

分享文本到flutter

用户分享url的时候打开OutShareActivity, 可以通过Intent来获取分享的文本,社区已经有了对应的插件:[share_handler](share_handler | Flutter Package (pub.dev)):

final media = await ShareHandlerPlatform.instance.getInitialSharedMedia();
String? text = media?.content;

除了文本还可以接收图片、视频等多媒体资源的分享,只需要在AndroidManifest中配置对应的dataType

OutShareActivity继承了FlutterActivity,项目出现了多引擎共存的情况,为了方便管理,我在OutShareActivity类中覆写了getDartEntrypointFunctionName方法,返回自定义入口的方法名,这样引擎启动后就会run我指定的方法,而不是默认的main方法。

public String getDartEntrypointFunctionName() {
    return "outShareMain";
}

如果想要使用FlutterEngineGroup来创建引擎,可以先在FlutterActivity覆写provideFlutterEngine方法,然后使用EngineGroup来创建引擎:

public FlutterEngine getEngine() {
    return group.createAndRunEngine(this, new DartExecutor.DartEntrypoint(FlutterInjector.instance().flutterLoader().findAppBundlePath(),"outShareMain"));
}

需要注意的是在dart测创建entryPoint方法的时候需要加上@pragma('vm:entry-point')注解,否则在打包的时候可能会把该方法当成无用方法直接移除。

@pragma('vm:entry-point')
void outShareMain() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(OutShareMainApp());