如何通过短信、链接等跳转到APP,并打开相应Flutter页面

  今天,来聊聊通过短信、邮件中的链接打开跳转到APP,并打开特定的Flutter页面。在做原生开发的时候,我们经常可能碰到这种情况,一般是为了拉新,或者网页业务跳转等。通过点击某个超链接跳转到APP,并打开相应的页面。

前言

  这其实也不难,在Android端和iOS实现也差不多,配置schema、host和path等等。Android端有deep Link和App Link两种方式,至于有啥区别,怎么做?这篇就不说了,网上都处都有,可以去查一下。iOS端不是很熟悉,了解到也是两种,一种是配置scheme;另一种是通过open url。应该和Android端大同小异,大家都可以去查一查。(Android端可以查看:https://developer.android.com/training/app-links/deep-linking)
  这里主要是聊一下,接受到Open url或者说schema打开APP的数据,怎么去跳转到Flutter写的特定页面,也就是原生端收到跳转数据,如何在Flutter端打开特定的页面。

一、原生与Flutter端通信

  我们应该可想到,像通过schema或者open url打开Android或是iOS应用,这无非是Android和iOS原生系统实现的功能,那么也就是说,通过schema或者open Url打开App,也只能是原生端收到了消息。
  那么问题来了,如果是根据某个参数或者path不同,需要打开Flutter端中的某个页面,那么Flutter端怎么知道呢?那么这就涉及到了原生与Flutter端的通信问题了,关于原生与Flutter的通信有三种方式:在这篇文章里有介绍《Flutter与原生如何进行通信》
  这里我们需要用到Event Channel,来从原生端发送消息给Flutter端,Flutter端监听到Open Url消息后,跳转到相应页面。

二、Android原生端处理

2.1 先定义好Event Channel Name,并注册链接Event Channel
class MainActivity: FlutterActivity() {
    //定义好Event Channel url
    private val EVENT_CHANNEL = "***.**/event/channel"
    private var appLinkEventSink: EventChannel.EventSink? = null
    private var appLinkData: Map<String, Any?>? = null

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        ...
        //注册链接Event Channel
        registerAppLinkEventChannel(flutterEngine.dartExecutor)
    }

    private fun registerAppLinkEventChannel(dartExecutor: DartExecutor) {
        val appLinkEventChannel = EventChannel(dartExecutor, EVENT_CHANNEL)
        appLinkEventChannel.setStreamHandler(object : EventChannel.StreamHandler{
            override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
                //与Flutter建立链接后,将EventChannel.EventSink赋值给全局变量
                appLinkEventSink = events
                sendLinkDataToFlutter()
            }

            override fun onCancel(arguments: Any?) {
                appLinkEventSink = null
            }
        })
    }

    private fun sendLinkDataToFlutter() {
        if(appLinkEventSink != null && appLinkData != null){
            //给Flutter端发送数据
            appLinkEventSink?.success(appLinkData)
            appLinkData = null
        }
    }

}

2.2 在MainActivity中接受到Open Url数据后,组装需要发送给Flutter端的数据

我这里是用了一个Util类来专门组装,发送给Flutter端的数据类型可以是用一个字典(Map)组装。

class AppLinkUtil {

    companion object{
        fun getAppLinkData(appLinkUri: Uri?, context: Context): Map<String, Any?>? {
            var dataMap: Map<String, Any?>? = null;
            var appLinkType = getAppLinkType(appLinkUri, context)
            if(appLinkType != -1) {
                dataMap = HashMap()
                dataMap["appLinkType"] = getAppLinkType(appLinkUri, context)
                dataMap["data"] = getAppLinkParamData(appLinkUri)
                dataMap["url"] = appLinkUri?.path
            }
            return dataMap
        }

        private fun getAppLinkParamData(appLinkUri: Uri?): Map<String, Any?>? {
            val path = appLinkUri?.path
            var paramData: Map<String, Any?>? = null
            if(path != null){
                paramData = HashMap()
                val parameterNames: Set<String>? = appLinkUri.queryParameterNames
                if (parameterNames != null) {
                    for(parameterName in parameterNames){
                        paramData[parameterName] = appLinkUri.getQueryParameter(parameterName)
                    }
                }
            }
            return paramData
        }

        private fun getAppLinkType(appLinkUri: Uri?, context: Context): Int {
            var appLinkeType = -1
            val path = appLinkUri?.path
            if(path != null){
                if(path.equals("path A")){
                    appLinkeType = AppLinkType.A.ordinal
                }else if(path.equals("path B")){
                    appLinkeType = AppLinkType.B.ordinal
                }
            }
            return appLinkeType
        }
    }


}

enum class AppLinkType{
    A,
    B
}
2.3 将组装数据发送给Flutter端
class MainActivity: FlutterActivity() {
    ...

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        handleIntent(intent)
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        handleIntent(intent)
    }

    /**
     * Deal With Third part intent to app
     */
    private fun handleIntent(intent: Intent) {
        val appLinkAction = intent.action
        val appLinkUri: Uri? = intent.data
        if(Intent.ACTION_VIEW == appLinkAction && !launchedActivityFromHistory(intent)){
            appLinkData = AppLinkUtil.getAppLinkData(appLinkUri, this)
            //Tips 1:
            // 因为在收到Open Url数据的时候,与Flutter端的Event Channel链接可能还没建立好,所以需要将要发送的数据用全局变量保存
            // 判断是否已经链接好了,在发送
            sendLinkDataToFlutter()
        }
    }

    //Tips 2:
    // 这里一定要判断App是否是从History里启动的,因为从History(Remote)启动的话,会将上一次的intent重复发送,所以会出现重复打开页面的情况
    private fun launchedActivityFromHistory(intent: Intent?): Boolean {
        return intent != null && intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
    }

    private fun sendLinkDataToFlutter() {
        if(appLinkEventSink != null && appLinkData != null){
            //给Flutter端发送数据
            appLinkEventSink?.success(appLinkData)
            appLinkData = null
        }
    }

}

这里主要有两个需要注意的点:
Tips 1: 因为在收到Open Url数据的时候,与Flutter端的Event Channel链接可能还没建立好,所以需要将要发送的数据用全局变量保存。

       if(Intent.ACTION_VIEW == appLinkAction && !launchedActivityFromHistory(intent)){
            appLinkData = AppLinkUtil.getAppLinkData(appLinkUri, this)
            //Tips 1:
            // 因为在收到Open Url数据的时候,与Flutter端的Event Channel链接可能还没建立好,所以需要将要发送的数据用全局变量保存
            // 判断是否已经链接好了,在发送
            sendLinkDataToFlutter()
        }

Tips 2: 这里一定要判断App是否是从History里启动的,因为从History(Remote)启动的话,会将上一次的intent重复发送,所以会出现重复打开页面的情况。

    //Tips 2:这里一定要判断App是否是从History里启动的,因为从History(Remote)启动的话,会将上一次的intent重复发送,所以会出现重复打开页面的情况
    // 这里一定要判断App是否是从History里启动的,因为从History(Remote)启动的话,会将上一次的intent重复发送,所以会出现重复打开页面的情况
    private fun launchedActivityFromHistory(intent: Intent?): Boolean {
        return intent != null && intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
    }

三、iOS原生端处理

3.1 先定义好Event Channel Name,并注册链接Event Channel
@objc class AppDelegate: FlutterAppDelegate,FlutterStreamHandler {
    
    var flutterEventSink : FlutterEventSink?;
    var dict:NSMutableDictionary = NSMutableDictionary()

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        ...
        
        let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
        //开始链接Event channel
        startEventChannel(binaryMessenger: controller.binaryMessenger);
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    func onListen(withArguments arguments: Any?,
                  eventSink: @escaping FlutterEventSink) -> FlutterError? {
        //与Flutter建立链接后,将EventChannel.EventSink赋值给全局变量
        flutterEventSink = eventSink;
        openUrlSink();
        return nil
    }
    
    func onCancel(withArguments arguments: Any?) -> FlutterError? {
        flutterEventSink = nil;
        return nil
    }
    
    func startEventChannel(binaryMessenger: FlutterBinaryMessenger) {
        let eventChannel = FlutterEventChannel(name: "**.**/eventchannel",binaryMessenger: binaryMessenger)
        eventChannel.setStreamHandler(self)
    }
}
3.2 在AppDelegate中接受到Open Url数据后,组装需要发送给Flutter端的数据
    enum SkipType :Int {
        case A = 0
        case B = 1
    }
    override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        
        dict.setValue(url.absoluteString, forKey: "url");
        
        if url.host == K_**_HOST  {
            
            if url.path.hasSuffix(PathA) {
                dict.setValue(SkipType.A.rawValue,forKey: K_SKIPTYPE_KEY);
                openUrlSink();

            }else if url.path.hasSuffix(PathB) {
                
                dict.setValue(SkipType.B.rawValue,forKey: K_SKIPTYPE_KEY);
                
                let data:NSMutableDictionary = NSMutableDictionary()
                let myArray : Array? = url.query?.components(separatedBy: "&")
                
                for i in myArray ?? []{
                    let myArray2 = i.components(separatedBy:"=")
                    data.setValue(myArray2[1], forKey: myArray2[0])
                }
                if data.count > 0 {
                    dict.setValue(data,forKey: "data");
                    openUrlSink();
                }
              
            }
      
        }
        return true;
    }
3.3 组装跳转数据并发送给Flutter端
func openUrlSink() {
    if self.flutterEventSink != nil &&  self.dict.count > 0{
       eventSink(data: dict as! Dictionary<String, Any>);
       self.dict.removeAllObjects();
    }
}

func eventSink(data: Dictionary<String, Any>) {
   self.flutterEventSink?(data);
}

其实在iOS端也是一样的,因为有可能在我们接收到Open Url的时候,Event Channel并没有链接好,所以任然需要将需要传输到Flutter端的数据保存为全局数据,并判断是否已经链接好,链接好了才发送数据。

四、Flutter端接收跳转数据,并执行具体逻辑跳转到相应页面

4.1 监听接收原生端传过来的Open Url参数数据

在我的需求中,我在main.dart中监听了Event Channel,并将数据缓存到了SharedPreference中,根据不同需求,也是可以直接取数据跳转的

class AppChannel {
  static const eventChannelName = '**.**/event/channel';

  static const EventChannel eventChannel =
  const EventChannel(AppChannel.eventChannelName);

  static startListenOpenUrl()  {
    try {
      eventChannel
          .receiveBroadcastStream()
          .listen((value) {
        if(value != null){
          SharedPreferences.getInstance().then(
                  (sharedPreferences) {
                sharedPreferences.setString(ConstantString.prefs_app_link_data, jsonEncode(value)).then((value) {
                  Future.delayed(Duration(milliseconds: 1000), (){
                    AppLinkUtil.dealWithAppLinkData();
                  });
                });

              }
          );
        }

      }, onError: (error){

      });
    } on Exception catch (e) {
      LogUtil.d(e);

    }

  }
}
4.2 根据参数跳转到具体页面

这里话就是具体需求,具体逻辑了,比如说有可能没登录呀,需要登录后跳转;有的又可以直接跳转等,看具体需求,实现逻辑了。这里就是简单示例跳转页面:

  static void navigatorToTestPage(AppLinkData appLinkData) {
    String dataA = appLinkData.dataA;
    bool dataB = appLinkData?.dataB ?? true;
    Navigator.pushNamed(globalKeyNavigatorKey.currentState.context,
        'TestPage',
        arguments: {
          'DataA': dataA,
          'DataB': dataB
        });
  }

五、结语

  通过Schema或者Open Url跳转到Flutter具体页面也就收工了,写文章越来越顺手了~ O(∩_∩)O哈哈~

申明:禁用于商业用途,如若转载,请附带原文链接。https://www.jianshu.com/p/a9d49fe637ba蟹蟹~

PS: 写文不易,觉得没有浪费你时间,请给个关注和点赞~ 😁

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342