如果深度链接无法由应用处理,如何优雅地回退到网站

时间:2015-01-22 18:50:01

标签: android deep-linking

情况:

  1. 您有一个广泛的移动网站m.somewhere.com
  2. 在Google Play上,您有一个Android应用程序,它复制了m.somewhere.com的主要功能,但不是全部。
  3. 您的客户/雇主/投资者已要求您为应用程序可以处理的网址实施深层链接。
  4. TL; DR - 你是如何实现这个的?

    我的方法到目前为止:

    第一直觉:只匹配某些网址并为其启动。问题:AndroidManifest intent-filter中表达式的缺乏阻止了这种情况(例如http://weiyang.wordpress.ncsu.edu/2013/04/11/a-limitation-in-intent-filter-of-android-application/)。

    作为问题的一个子集,假设m.somewhere.com上的服务器知道任何以数字结尾的网址都会进入网站上的某个页面,并且营销人员会不断地使用seo,所以例如

    我想推出以下应用:

    http://m.somewhere.com/find/abc-12345
    https://m.somewhere.com/shop/xyz-45678928492
    

    但不适用于

    http://m.somewhere.com/find/abc-12345-xyz
    https://m.somewhere.com/about-us
    

    path,pathPrefix或pathPattern的组合不会处理此问题。

    stackoverflow(Match URIs with <data> like http://example.com/something in AndroidManifest)上的最佳做法似乎是捕获所有内容,然后当你到达onCreate()并意识到你不应该处理这个特定的url时处理这种情况:

    Android Manifest:

    ...
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="http"
              android:host="m.somewhere.com"
              android:pathPattern=".*"/>
    </intent-filter>
    ...    
    

    活动onCreate():

    Intent i = getIntent()
    String action = i.getAction();
    Uri uri = i.getData();
    if (Intent.ACTION_VIEW.equals(action) && cantHandleUrl(uri)) {
        // TODO - fallback to browser.
    }
    

    我编写了类似于上述内容的程序,但它会导致非常糟糕的最终用户体验:

    1. 在浏览m.somewhere.com时,每个网址点击都会出现打嗝 当应用程序启动然后回落。
    2. 为每个链接点击m.somewhere.com弹出一个Chooser屏幕有一个讨厌的习惯,询问用户他们想要使用哪个(并且Android应用程序与浏览器一起列出,但点击在Android应用程序上再次启动选择器屏幕)。如果我不小心,我会为我的应用程序进入一个无限重新启动循环(如果用户选择“始终”),即使我很小心,用户也会看到他们的“始终”选择被忽略。
    3. 可以做些什么?

      (编辑:在应用程序的WebView中显示未处理页面的网站不是一个选项。)

7 个答案:

答案 0 :(得分:4)

这样做的方式有些晦涩:

  • 在清单中,为m.somewhere.com创建一个intent-filter,以打开特定的深层链接处理程序活动。
  • 在该活动中,确定您的应用是否支持该网址。
  • 如果确实如此,只需打开任何活动
  • 如果没有,请发送未解析的ACTION_VIEW意图,以便您的浏览器打开。这里的问题是,您的应用程序也会捕获此意图,如果您的应用程序被选为该URL的默认处理程序,这将创建一个无限循环。解决方案是在发送该意图之前使用PackageManager.setComponentEnabledSetting()禁用您的deeplink处理程序Activity,并在之后重新启用它。

一些示例代码:

public class DeepLinkHandlerActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Uri uri = intent.getData();
        Intent intent = makeInternallySupportedIntent(uri);
        if (intent == null) {
            final PackageManager pm = getPackageManager();
            final ComponentName component = new ComponentName(context, DeepLinkHandlerActivity.class);
            pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);

            Intent webIntent = new Intent(Intent.ACTION_VIEW);
            webIntent.setData(uri);
            context.startActivity(webIntent);

            AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {

                @Override
                protected Void doInBackground(Void[] params) {
                    SystemClock.sleep(2000);
                    pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
                    return null;
                    }
                };
             task.execute();
        } else {
            startActivity(intent);
        }
        finish();
    }
}

希望有所帮助。

注意:看起来您需要将重新启用延迟几秒才能使其正常工作。

注意2:为了获得更好的体验,对您的活动使用透明主题会使您的应用看起来甚至没有打开。

注意3:如果由于某种原因你的应用程序在组件重新注册之前崩溃或被杀死,你将永远失去深层链接支持(或直到下次更新/重新安装),所以我也会重新启用组件在App.onCreate()以防万一。

答案 1 :(得分:3)

迟到的答案,但对于未来的读者:如果您支持最低 API级别15 ,那么有一种更直接(更少hacky)的方式可以回退到您认识到的网址的浏览器不想诉诸于禁用/重新启用URL捕获组件。

nbarraille's answer很有创意,如果您需要支持低于15的API,可能是您唯一的选择,但如果您不支持,那么您可以使用Intent.makeMainSelectorActivity()直接启动用户的默认浏览器,允许您绕过Android的ResolverActivity应用选择对话框。

不要这样做

所以不要像这样重复广播URL Intent:

// The URL your Activity intercepted
String data = "example.com/someurl"
Intent webIntent = new Intent(Intent.ACTION_VIEW, data);
webIntent.addCategory(Intent.CATEGORY_BROWSABLE);
startActivity(webIntent);

这样做

您将改为播放此Intent:

Intent defaultBrowser = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER);
defaultBrowser.setData(data);
startActivity(defaultBrowser);

这将告诉Android加载浏览器应用和data网址。即使安装了多个浏览器应用程序,也应绕过选择器对话框。如果没有选择器对话框,您不必担心应用程序会陷入无限循环的拦截/重新播放相同的意图。

买者

您必须在用户的浏览器中打开URL(您不想处理的URL)。如果您想为其他非浏览器应用程序提供打开链接的机会,则此解决方案将无效,因为没有选择器对话框。

陷阱

据我所知,使用此解决方案的唯一特点是,当用户点击您的某个深层链接时,他们会选择在您的应用或浏览器中打开,等等。当他们选择您的应用程序和您的内部应用程序逻辑意识到它是一个不想拦截的URL,用户立即显示浏览器。所以他们选择你的应用程序,但是显示浏览器。

注意:当我在这个答案中说“broadcast”时,我指的是通用术语,而不是实际的Android系统功能。

答案 2 :(得分:1)

URX提供了一个免费工具(urxlinks.js),可以在安装应用程序时自动将移动网络用户重定向到应用程序。该文档可在此处获取:http://developers.urx.com/deeplinks/urx-links.html#using-urx-links-js

答案 3 :(得分:0)

如果两个应用程序使用相同的方案,那么选择器屏幕将弹出,因为android不会知道该链接用于哪个应用程序。为您的应用使用自定义方案可能会解决此问题。但是你仍然无法确定没有其他人会使用这个方案。

答案 4 :(得分:0)

听起来您正试图将您的移动应用和移动网站视为相同体验的扩展。一般来说,这是一种很好的做法,但在这一点上,这两者根本不是平价。至少在他们达到平价之前,我不建议自动将最终用户推入您的移动应用程序,因为故意使用移动网站以查找您的应用程序丢失的内容的用户会发现这令人非常沮丧。

相反,使用智能横幅来鼓励移动网站上的用户确实具有与应用程序等效的应用程序来打开应用程序可能是有意义的。那些横幅将是你的深层链接。您可以自己创建它们,也可以集成像Branch(https://branch.io/universal-app-banner/)这样的工具来处理深层链接和智能横幅。

问题的最后一部分与深层链接的放置位置有关。使用智能横幅而不是重定向的一个优点是,您可以将它们嵌入到CMS上的相应模板中,而不需要依赖于网址检测。

祝你好运!

答案 5 :(得分:0)

这是我解决第二个问题的方法。 PackageManager.queryIntentActivities()将为您提供选择器中显示的应用程序/活动列表。遍历列表(至少应该包含浏览器)并查找其包名与当前应用程序不匹配的活动,并为其设置intent类名,然后启动具有该意图的Activity并调用finish();

public Intent getNotMeIntent(Uri uri) {
    Intent intent = new Intent(Intent.ACTION_VIEW, uri);

    PackageManager manager = context.getPackageManager();
    List<ResolveInfo> infos = manager.queryIntentActivities(intent, 0);
    for (int i = 0; i < infos.size(); i++) {
        ResolveInfo info = infos.get(i);
        // Find a handler for this url that isn't us
        if (!info.activityInfo.packageName.equals(context.getPackageName())) {
            intent.setComponent(null);
            intent.setClassName(info.activityInfo.packageName, info.activityInfo.name);
            return intent;
        }
    }

    // They have no browser
    return null;
}

透明主题(如上所述)应该是解决第一个问题的好方法。

答案 6 :(得分:0)

onCreate 的目标活动中为 Kotlin 设置此代码:

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

‌private fun handleIntent(intent: Intent?) {
   val appLinkAction: String? = intent?.action
   val appLinkData: Uri? = intent?.data
   showDeepLinkData(appLinkAction, appLinkData)
}

private fun showDeepLinkData(appLinkAction: String?, appLinkData: Uri?) {
   if (Intent.ACTION_VIEW == appLinkAction && appLinkData != null) {
       val promotionCode = appLinkData.getQueryParameter("exampleQueryString")
       Log.e("TAG", "Uri is: $appLinkData")
   }
}