【问题标题】:Android Web Intent IssueAndroid 网络意图问题
【发布时间】:2016-06-16 03:30:04
【问题描述】:

生日,

免责声明:我不是 Android 开发人员,我正在针对我所描述的问题对 Android 应用进行 QA。我用来描述这个问题的技术术语可能是错误的。

我正在测试一个 Android 应用程序,该应用程序在其清单中描述它可以处理类型为 https://www.example.com/app/(.*) 的网络意图。它应该处理这些 URL 的方式是它获取第一个匹配组 $1 并向 https://api.example.com/$1 发送请求,如果响应是 HTTP200,它会在应用程序中呈现响应。如果没有,它应该在用户安装在其设备上的任何浏览器应用程序中打开 URL(通过发送打开 URL 的意图)。如果用户没有在他们的设备上安装浏览器应用程序,我们会显示一个错误提示,说明他们没有安装可以处理此 URL 的浏览器。

现在,这可以正常工作,除非用户将此应用程序标记为默认处理像 https://www.example.com/app/(.*) 这样的 URL,当它第一次尝试打开像 https://www.example.com/app/(.*) 这样的 URL 时。然后,即使用户在他们的系统上安装了浏览器应用程序,当他们打开需要在浏览器中打开的链接时,唯一的选择似乎是我们的原始应用程序,我们必须显示错误消息(看起来就像系统上没有安装可以处理此 URL 的其他浏览器应用程序一样)。

解决此问题的一种方法是,当我们遇到需要在浏览器应用程序中打开的 URL 但唯一的选择是我们自己的应用程序时,显示一条消息,要求用户清除此应用程序的默认值 - 但这很糟糕用户体验。这个问题还有其他解决方法吗?

了解问题的示例代码:https://gist.github.com/GVRV/5879fcf0b1838b495e3a2151449e0da3

编辑1:添加示例代码链接

【问题讨论】:

  • 你能分享一些代码部分吗?所以这个很容易观察到的问题。
  • @USKMobility 添加了示例代码链接。谢谢!

标签: android android-intent


【解决方案1】:

要解决此问题并保持系统对意图的默认处理,您需要 2 个额外的活动和 1 个<activity-alias>

  1. 新建一个不可见的空Activity。我称之为 IntentFilterDelegationActivity。此活动负责从activity-alias(在下一步中定义)接收 URL 意图。清单:

    <activity
        android:name=".intent_filter.IntentFilterDelegationActivity"
        android:excludeFromRecents="true"
        android:exported="true"
        android:launchMode="singleInstance"
        android:noHistory="true"
        android:taskAffinity=""
        android:theme="@style/Theme.Transparent"/>
    

代码:

public class IntentFilterDelegationActivity extends Activity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        findAndStartMatchingActivity(getIntent());
        finish();
    }
}
  1. 创建一个&lt;activity-alias&gt;。活动别名负责将您的 URL 意图委托给您的 IntentFilterDelegationActivity。只需要一个 Manifest 条目:

    <activity-alias
        android:name="${packageName}.IntentFilterDelegation"
        android:enabled="true"
        android:exported="true"
        android:targetActivity=".intent_filter.IntentFilterDelegationActivity">
        <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="www.example.com"/>
        </intent-filter>
    </activity-alias>
    
  2. 现在您可以做到这一点:您可以在启动自己的 URL 意图之前停用activity-alias,并在启动后激活别名。这会导致 android 您的应用不会被列为可以处理 URL 意图的应用。要实现激活和停用,您需要一个额外的活动。我称它为ForceOpenInBrowserActivity。 清单:

    <activity
        android:name=".activity.ForceOpenInBrowserActivity"
        android:excludeFromRecents="true"
        android:launchMode="singleInstance"
        android:noHistory="true"
        android:taskAffinity=""
        android:theme="@style/Theme.Transparent"/>
    

代码:

        public class ForceOpenInBrowserActivity extends Activity
    {
        public static final String URI = IntentUtils.getIntentExtraString(ForceOpenInBrowserActivity.class, "URI");

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

            Uri uri = fetchUriFromIntent();
            if (uri != null)
            {
                startForcedBrowserActivity(uri);
            }
            else
            {
                finish();
            }
        }

        @Nullable
        private Uri fetchUriFromIntent()
        {
            Uri uri = null;

            Intent intent = getIntent();
            if (intent != null)
            {
                uri = intent.getParcelableExtra(URI);
            }

            return uri;
        }

        private void startForcedBrowserActivity(Uri uri)
        {
            disableActivityAlias(this);

            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

            // After starting another activity, this activity will be destroyed
            // android:noHistory="true" android:excludeFromRecents="true"
            startActivity(intent);
        }

        /**
         * Re-enable intent filters when returning to app.
         * Note: The intent filters will also be enabled when starting the app.
         */
        @Override
        protected void onStop()
        {
            super.onStop();
            enableActivityAlias();
        }

        public void disableActivityAlias()
        {
            String packageName = getPackageName();
            ComponentName componentName = new ComponentName(packageName, packageName + ".IntentFilterDelegation"); // Activity alias
            getPackageManager().setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
        }


        public void enableActivityAlias()
        {
            String packageName = getPackageName();
            ComponentName componentName = new ComponentName(packageName, packageName + ".IntentFilterDelegation");
            getPackageManager().setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
        }
    }
  1. 现在您可以将任何必须在外部浏览器中打开的 URL 意图发送到ForceOpenInBrowserActivity

    @NonNull
    public static Intent createForceBrowserIntent(Context context, @NonNull Uri uri)
    {
        Intent intent = new Intent(context, ForceOpenInBrowserActivity.class);
        intent.setAction(Intent.ACTION_VIEW);
        intent.putExtra(ForceOpenInBrowserActivity.URI, uri);
        return intent;
    }
    

【讨论】:

  • 这是一个非常有趣的方法。用户可以将应用设置为默认应用吗?还是禁用它也会清除默认值?
【解决方案2】:

如果网站 https://www.example.com/ 在您的监督下,您可以更改逻辑并使用独特的架构,例如 example://app/(.) 来处理您的案例。然后,该网站可以使用重定向来进行导航。这样,当您为操作视图广播 https://www.example.com/ 时,只有浏览器应用程序才能处理此问题,而您的应用程序只会监听您的自定义架构 example://app/(.) 而不会启动。

否则,您可以检查默认活动并将其清除而不是显示警报。

PackageManager pm = context.getPackageManager();
final ResolveInfo res = pm.resolveActivity(your_intent, 0);
if (res.activityInfo != null && getPackageName()
            .equals(res.activityInfo.packageName)) {
       pm.clearPackagePreferredActivities("you_package_name");
       broadcast your intent
    }

【讨论】:

  • 我们不能只为应用程序使用自定义模式,这一点很重要,例如,如果用户访问某些页面(如电子商务网站上的产品详细信息页面),我们希望它打开在应用程序上,以便我们更好地自定义用户体验。
【解决方案3】:

遗憾的是,这个问题没有官方解决方案(请参阅this SO question)。


解决方法如下: 使用PackageManager.queryIntentActivities(),将结果修改为不包含您的应用并将其显示在自定义选择器对话框中。 如果您不希望用户每次都选择浏览器,您可以在应用内管理自定义默认浏览器。


如果您控制域,则有一个更简洁的解决方法: 假设您的网址是http://www.example.com。您的 Android IntentFilter 应该侦听该架构。现在您创建第二个模式,例如http://web.example.com,显示的内容和普通的url一样。如果您想从您的应用程序重定向到网络,请使用第二个架构。在其他任何地方,使用第一个。

请注意,您不应使用像 example:// 这样的自定义架构,因为如果您的应用不存在,这会导致问题。

【讨论】:

    猜你喜欢
    • 2013-03-12
    • 1970-01-01
    • 1970-01-01
    • 2010-11-14
    • 1970-01-01
    • 1970-01-01
    • 2011-08-15
    • 1970-01-01
    • 2022-10-13
    相关资源
    最近更新 更多