🌏 閱讀中文版本
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 characterNOTexcludes 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(orhttp) - 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/123better than/p/123 - Avoid Conflicts: Reserve
/api/*,/admin/*for web use - Consistency: iOS and Android use same path structure
- Versioning: Consider
/v2/products/123for 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-knowndirectory 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.