iOS vs Android Deep Link Complete Comparison Guide: In-Depth Analysis of Universal Links & App Links

🌏 閱讀中文版本


iOS vs Android Deep Link Complete Comparison Guide: In-Depth Analysis of Universal Links & App Links

In modern mobile application development, Deep Links have become a key technology for enhancing user experience. Whether through social media sharing, email marketing, or QR code scanning, the ability to direct users to specific pages within an app is crucial. This article provides an in-depth comparison of Deep Link implementation technologies on iOS and Android platforms, helping developers choose the most appropriate solution.

Core Concepts: Three Types of Deep Links

1. URI Schemes (Traditional Approach)

Definition: Using custom protocols (such as myapp://) to open applications.

Examples:

myapp://products/123
fb://profile/123456789
twitter://user?screen_name=example

Advantages:

  • Simple implementation, supported by both iOS and Android
  • No server configuration required
  • Suitable for in-app navigation

Disadvantages:

  • Low security (any app can register the same scheme)
  • Displays error message if app isn’t installed
  • Cannot use standard HTTPS URLs
  • Not SEO-friendly

2. Universal Links (iOS) / App Links (Android)

Definition: Using standard HTTPS URLs with domain verification mechanisms to ensure security.

Examples:

https://example.com/products/123
https://blog.example.com/articles/how-to-guide

Advantages:

  • High security (requires domain ownership verification)
  • Fallback mechanism (opens web version if app not installed)
  • SEO-friendly (standard HTTPS URLs)
  • One URL works across all platforms
  • Better user experience (no confirmation popup)

Disadvantages:

  • More complex configuration
  • Requires server deployment of configuration files
  • Version requirements: iOS 9+ / Android 6.0+

3. Deferred Deep Links

Definition: Can direct users to specific content after installation, even if the app wasn’t previously installed.

Use Cases:

  • Marketing campaign tracking
  • Referral code systems
  • First-launch onboarding

Third-Party Solutions: Firebase Dynamic Links, Branch.io, AppsFlyer

iOS Universal Links vs Android App Links Technical Comparison

Configuration File Comparison

Feature iOS (AASA) Android (assetlinks.json)
Filename apple-app-site-association assetlinks.json
File Format JSON (no extension) JSON (with .json extension)
Deployment Path /.well-known/apple-app-site-association /.well-known/assetlinks.json
Content-Type application/json or application/pkcs7-mime application/json
File Size Limit 128 KB No explicit limit (recommended < 1 MB)
HTTPS Requirement Required (HTTP not supported) Required (HTTP not supported)
Verification Mechanism Team ID + Bundle Identifier Package Name + SHA-256 Certificate Fingerprint
Update Check Frequency On app install/update, approximately every 24 hours On app install, manual trigger
Supported Versions iOS 9.0+ Android 6.0+ (API Level 23)

iOS AASA File Example

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAM_ID.com.example.app",
        "paths": [
          "/products/*",
          "/articles/*",
          "NOT /api/*",
          "NOT /admin/*"
        ]
      }
    ]
  },
  "webcredentials": {
    "apps": ["TEAM_ID.com.example.app"]
  }
}

Key Field Descriptions:

  • appID: Format is TEAM_ID.BUNDLE_ID (viewable in Apple Developer account)
  • paths: Defines which paths open in the app
    • * wildcard matches any characters
    • ? matches a single character
    • NOT excludes specific paths
  • webcredentials: Supports Password AutoFill functionality (optional)

Android assetlinks.json File Example

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.app",
      "sha256_cert_fingerprints": [
        "14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"
      ]
    }
  }
]

Key Field Descriptions:

  • relation: Permission relationship, typically delegate_permission/common.handle_all_urls
  • namespace: Fixed as android_app
  • package_name: Android app’s Package Name (e.g., com.example.app)
  • sha256_cert_fingerprints: SHA-256 fingerprint of APK signing certificate (can have multiple for different build versions)

How to Get SHA-256 Fingerprint

# From keystore (debug version)
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

# From APK (release version)
keytool -printcert -jarfile app-release.apk

# Using Gradle (recommended)
./gradlew signingReport

Application-Side Configuration Comparison

iOS Configuration (Xcode)

Step 1: Enable Associated Domains

1. Select Target → Signing & Capabilities
2. Click "+ Capability"
3. Add "Associated Domains"
4. Add domains:
   - applinks:example.com
   - applinks:blog.example.com

Step 2: Handle Universal Links (Swift)

// AppDelegate.swift or SceneDelegate.swift
func application(_ application: UIApplication,
                 continue userActivity: NSUserActivity,
                 restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let url = userActivity.webpageURL else {
        return false
    }

    // Parse URL and navigate
    if url.pathComponents.contains("products") {
        // Navigate to product page
        let productID = url.lastPathComponent
        navigateToProduct(productID)
        return true
    }

    return false
}

Android Configuration (AndroidManifest.xml)

Step 1: Add intent-filter

<activity android:name=".MainActivity">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <data
            android:scheme="https"
            android:host="example.com"
            android:pathPrefix="/products" />
        <data
            android:scheme="https"
            android:host="example.com"
            android:pathPrefix="/articles" />
    </intent-filter>
</activity>

Key Attributes:

  • android:autoVerify=”true”: Enable App Links automatic verification (required)
  • android:scheme: Must be https (or http)
  • android:host: Domain name (without protocol)
  • android:pathPrefix: Path prefix (optional)

Step 2: Handle App Links (Kotlin)

// MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    handleIntent(intent)
}

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

private fun handleIntent(intent: Intent) {
    val action = intent.action
    val data = intent.data

    if (Intent.ACTION_VIEW == action && data != null) {
        // Parse URL and navigate
        when {
            data.path?.startsWith("/products/") == true -> {
                val productId = data.lastPathSegment
                navigateToProduct(productId)
            }
            data.path?.startsWith("/articles/") == true -> {
                val articleId = data.lastPathSegment
                navigateToArticle(articleId)
            }
        }
    }
}

Validation and Testing Tools

iOS Validation Tools

1. Apple Official Validator

https://search.developer.apple.com/appsearch-validation-tool/

2. Command Line Validation

# Check if file is accessible
curl -I https://example.com/.well-known/apple-app-site-association

# Download and format JSON
curl https://example.com/.well-known/apple-app-site-association | jq .

# Check Content-Type
curl -I https://example.com/.well-known/apple-app-site-association | grep -i content-type

3. Device Testing

  • Paste link in Notes app, long-press to test
  • Open link through Safari
  • Click link from Messages or Mail

Android Validation Tools

1. Google Official Validator

https://developers.google.com/digital-asset-links/tools/generator

2. Command Line Validation

# Check assetlinks.json
curl https://example.com/.well-known/assetlinks.json | jq .

# Verify App Links configuration (requires Android Debug Bridge)
adb shell am start -a android.intent.action.VIEW -d "https://example.com/products/123" com.example.app

# Check App Links verification status
adb shell dumpsys package d
# Search for your package name, check "status" field

3. App Links Assistant (Android Studio)

Tools → App Links Assistant
- Provides step-by-step setup wizard
- Automatically generates intent-filter and assetlinks.json
- Tests URL mapping and intent handling

Common Issues and Solutions

iOS Common Issues

Issue 1: Universal Links Not Working

Possible Causes:

  • AASA file not accessible via HTTPS
  • Incorrect Team ID or Bundle ID
  • Associated Domains not properly configured in Xcode
  • iOS hasn’t updated AASA cache yet (up to 24 hours)
  • URL entered directly in Safari address bar (doesn’t trigger Universal Links)

Solutions:

# 1. Confirm Team ID
# Apple Developer → Membership → Team ID

# 2. Verify AASA accessibility
curl -v https://example.com/.well-known/apple-app-site-association

# 3. Delete and reinstall app (clear cache)
# 4. Use Apple validation tool to check configuration

Issue 2: First Click Ineffective, Works on Second Try

Reason: iOS downloads the AASA file when first encountering a Universal Link, which may take time.

Solution: Deploy AASA file 24 hours before publishing app update.

Android Common Issues

Issue 1: App Links Verification Failed

Possible Causes:

  • SHA-256 fingerprint mismatch (debug vs release versions different)
  • Incorrect Package Name
  • assetlinks.json format error
  • Server not properly setting Content-Type

Check Verification Status:

# View verification status
adb shell dumpsys package domain-preferred-apps

# Manually trigger verification (Android 12+)
adb shell pm verify-app-links --re-verify com.example.app

# View detailed verification results
adb shell pm get-app-links com.example.app

Issue 2: Links Open in Browser Instead of App

Reasons:

  • android:autoVerify="true" not set in AndroidManifest.xml
  • Verification failed (status not “always”)
  • User manually chose to open in browser

Solutions:

# Reset user preferences (for development testing)
adb shell pm set-app-links --package com.example.app 0 all

# View current settings
adb shell pm get-app-links com.example.app

Deployment Best Practices

1. Unified Domain Strategy

Recommendation: Use a single primary domain to handle Deep Links

✅ Recommended:
https://example.com/products/123
https://example.com/articles/456

❌ Avoid:
https://shop.example.com/products/123
https://blog.example.com/articles/456
(Requires multiple configuration files)

2. Path Design Principles

  • Clear Semantics: /products/123 better than /p/123
  • Avoid Conflicts: Reserve /api/*, /admin/* for web use
  • Consistency: iOS and Android use same path structure
  • Versioning: Consider /v2/products/123 for future expansion

3. Multi-Environment Configuration

// iOS AASA - Multi-environment support
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAM_ID.com.example.app.prod",
        "paths": ["/products/*", "/articles/*"]
      },
      {
        "appID": "TEAM_ID.com.example.app.staging",
        "paths": ["/staging/*"]
      }
    ]
  }
}

4. CDN and Caching Considerations

Important Reminder:

  • .well-known directory must not be cached through CDN (causes verification failure)
  • Must be served directly from origin server
  • No HTTP redirects (301/302) allowed

Nginx Configuration Example:

location /.well-known/ {
    # Serve directly, no caching
    add_header Content-Type application/json;
    add_header Cache-Control "no-cache, no-store, must-revalidate";
    expires 0;
}

5. Monitoring and Logging

Recommended Metrics to Track:

  • Deep Link click-through rate (from different sources)
  • App open success rate vs web fallback rate
  • AASA / assetlinks.json file download count
  • Usage frequency of different paths

Selection Recommendations: When to Use Which Approach?

Use URI Schemes When:

  • ✅ Internal app navigation (not for external sharing)
  • ✅ Rapid prototype development
  • ✅ Need to support iOS 8 or Android 5.0 and below
  • ✅ Third-party OAuth authorization callbacks

Use Universal Links / App Links When:

  • ✅ Social media link sharing
  • ✅ Email marketing campaigns
  • ✅ QR code scanning
  • ✅ Need SEO optimization for content
  • ✅ High security requirements (finance, e-commerce)
  • ✅ Cross-platform consistency (Web + iOS + Android)

Use Deferred Deep Links When:

  • ✅ New user acquisition campaigns (source tracking)
  • ✅ Referral code systems
  • ✅ Content preview when app not installed
  • ✅ Need complex attribution tracking

Cross-Platform Integration Example

Unified Deep Link Handling Architecture

// Universal route parser (for iOS/Android/Web)
interface DeepLinkRoute {
  type: 'product' | 'article' | 'profile' | 'home';
  params: Record<string, string>;
}

function parseDeepLink(url: string): DeepLinkRoute | null {
  const urlObj = new URL(url);
  const pathSegments = urlObj.pathname.split('/').filter(Boolean);

  if (pathSegments[0] === 'products' && pathSegments[1]) {
    return {
      type: 'product',
      params: { id: pathSegments[1] }
    };
  }

  if (pathSegments[0] === 'articles' && pathSegments[1]) {
    return {
      type: 'article',
      params: {
        id: pathSegments[1],
        ref: urlObj.searchParams.get('ref') || 'direct'
      }
    };
  }

  return null;
}

// Usage example
const route = parseDeepLink('https://example.com/products/123?ref=email');
// { type: 'product', params: { id: '123' } }

Performance Optimization Recommendations

1. Reduce Configuration File Size

  • iOS AASA limited to 128 KB, avoid listing too many paths
  • Use wildcards to simplify path rules
  • Remove unnecessary comments and whitespace

2. Optimize App Launch Speed

  • Deep Link processing should occur off main thread
  • Avoid time-consuming operations when handling Deep Links
  • Use asynchronous navigation, show launch screen first

3. Reduce Verification Failure Rate

  • Ensure server 99.9% availability
  • Use geographically distributed servers
  • Regularly monitor configuration file accessibility

Conclusion

While iOS Universal Links and Android App Links differ in implementation details, their core philosophy is consistent: providing secure, reliable, and user-friendly Deep Link experiences. Choosing the right approach requires considering the following factors:

Key Decision Factors:

  • Security Requirements: High security needs choose Universal Links / App Links
  • Target Versions: Need to support older versions choose URI Schemes
  • SEO Considerations: Need search engine indexing choose HTTPS-based approaches
  • Development Resources: Rapid development choose URI Schemes or third-party SDKs
  • Maintenance Costs: Long-term maintenance choose native Universal Links / App Links

Best Practices Summary:

  • ✅ Prioritize Universal Links / App Links as primary approach
  • ✅ Keep URI Schemes as fallback mechanism
  • ✅ Unify path design between iOS and Android
  • ✅ Use official validation tools to ensure correct configuration
  • ✅ Implement comprehensive monitoring and logging systems
  • ✅ Regularly check and update configuration files
  • ✅ Create separate configurations for different environments (dev/staging/prod)

After mastering these technologies and best practices, you’ll be able to provide users with smooth cross-platform Deep Link experiences, effectively improving app user engagement and conversion rates. Whether iOS or Android, understanding their similarities and differences will help you design more flexible and maintainable solutions.

Related Articles

Leave a Comment