想要吸引新用户安装您的应用,最有效的方式之一是让现有用户能够与好友分享应用中的内容。借助动态链接,您可以打造出色的用户间分享体验。用户在收到好友推荐的内容后,点击链接便可直接访问您应用中分享的内容,即使他们可能必须先前往 App Store 或 Google Play 商店安装您的应用,系统仍会确保将他们转到相应内容。
通过将用户引荐的粘性与动态链接的持久性相结合,您可以打造用户间分享和引荐功能,让新用户直接看到相关的应用内容,或开展让引荐者和受邀者双方均受益的推广活动,以此吸引新用户。
主要优势
- 根据好友想要与其分享的内容,第一次打开您的应用的新用户可享受为其特别定制的首次运行体验。例如,您可以显示分享给他们的内容,或者自动在这些用户与邀请他们的朋友之间建立联系。
- 让用户可以与多个平台上的好友轻松分享内容,无论这些好友是否安装了您的应用。
下面介绍如何添加这项功能!
设置 Firebase 和 Dynamic Links SDK
建立一个新的 Firebase 项目,并将 Dynamic Links SDK 安装到您的应用中。
安装 Dynamic Links SDK 后,Firebase 便可向应用传递有关动态链接的数据,包括在用户安装应用后传递此类数据。
创建动态链接
现在我们来设置用户可发送给好友的链接。如果用户的好友还没有安装应用,您也不用担心,因为动态链接可以帮您搞定。
为应用内每个您想要允许分享的内容元素创建动态链接。
创建动态链接时,您需要提供 HTTP 或 HTTPS 网址作为 link
参数,用于标识您要分享的内容。如果您的网站上有同等内容,应当使用该网站的网址。这将确保这些链接在桌面浏览器等不支持动态链接的平台上也能正常打开。例如:
https://example.page.link/?link=https://www.example.com/content?item%3D1234&apn=com.example.android&ibi=com.example.ios&isi=12345
您还可以添加网址编码格式的参数,从而向数据载荷添加其他信息,例如在游戏邀请中指出该链接是针对特定用户的。
https://example.page.link/?link=https://www.example.com/invitation?gameid%3D1234%26referrer%3D555&apn=com.example.android&ibi=com.example.ios&isi=12345
在分享这些链接之前,您可能需要使用 Firebase Dynamic Links URL Shortener API 来生成更简练的网址。短动态链接的形式如下所示:
https://example.page.link/WXYZ
无论您使用哪个链接,当用户在其设备上打开上述动态链接时,如果用户尚未安装 apn
参数 (Android) 或 ibi
和 isi
参数 (iOS) 所指定的应用,则会被引导至 Play 商店或 App Store 安装该应用。然后,在用户安装并打开该应用后,系统即会将“link”参数中指定的网址传递给该应用。
添加用于发送动态链接的“分享”按钮
首先,我们来看一个简单示例。这是一个与 Hangouts 类似的聊天室应用,可生成邀请人们进入聊天室的链接。
iOS
![单按钮链接邀请界面 聊天应用屏幕截图](https://firebase.google.cn/static/docs/dynamic-links/images/inviting-users-ios0.png?authuser=9&hl=vi)
![通过系统分享动作条提供的分享选项 具有分享动作条的聊天应用屏幕截图](https://firebase.google.cn/static/docs/dynamic-links/images/inviting-users-ios1.png?authuser=9&hl=vi)
Android
![单按钮链接邀请界面 聊天应用屏幕截图](https://firebase.google.cn/static/docs/dynamic-links/images/inviting-users-android0.png?authuser=9&hl=vi)
![通过系统分享动作条提供的分享选项 具有分享动作条的聊天应用屏幕截图](https://firebase.google.cn/static/docs/dynamic-links/images/inviting-users-android1.png?authuser=9&hl=vi)
Swift
func generateContentLink() -> URL { let baseURL = URL(string: "https://your-custom-name.page.link")! let domain = "https://your-app.page.link" let linkBuilder = DynamicLinkComponents(link: baseURL, domainURIPrefix: domain) linkBuilder?.iOSParameters = DynamicLinkIOSParameters(bundleID: "com.your.bundleID") linkBuilder?.androidParameters = DynamicLinkAndroidParameters(packageName: "com.your.packageName") // Fall back to the base url if we can't generate a dynamic link. return linkBuilder?.link ?? baseURL }
Objective-C
- (NSURL *)generateContentLink { NSURL *baseURL = [NSURL URLWithString:@"https://your-custom-name.page.link"]; NSString *domain = @"https://your-app.page.link"; FIRDynamicLinkComponents *builder = [[FIRDynamicLinkComponents alloc] initWithLink:baseURL domainURIPrefix:domain]; builder.iOSParameters = [FIRDynamicLinkIOSParameters parametersWithBundleID:@"com.your.bundleID"]; builder.androidParameters = [FIRDynamicLinkAndroidParameters parametersWithPackageName:@"com.your.packageName"]; // Fall back to the base url if we can't generate a dynamic link. return builder.link ?: baseURL; }
Kotlin+KTX
fun generateContentLink(): Uri { val baseUrl = Uri.parse("https://your-custom-name.page.link") val domain = "https://your-app.page.link" val link = FirebaseDynamicLinks.getInstance() .createDynamicLink() .setLink(baseUrl) .setDomainUriPrefix(domain) .setIosParameters(DynamicLink.IosParameters.Builder("com.your.bundleid").build()) .setAndroidParameters(DynamicLink.AndroidParameters.Builder("com.your.packageName").build()) .buildDynamicLink() return link.uri }
Java
public static Uri generateContentLink() { Uri baseUrl = Uri.parse("https://your-custom-name.page.link"); String domain = "https://your-app.page.link"; DynamicLink link = FirebaseDynamicLinks.getInstance() .createDynamicLink() .setLink(baseUrl) .setDomainUriPrefix(domain) .setIosParameters(new DynamicLink.IosParameters.Builder("com.your.bundleid").build()) .setAndroidParameters(new DynamicLink.AndroidParameters.Builder("com.your.packageName").build()) .buildDynamicLink(); return link.getUri(); }
获得动态链接后,您可以向界面添加一个分享按钮,以启动标准平台分享流程:
Swift
lazy private var shareController: UIActivityViewController = { let activities: [Any] = [ "Learn how to share content via Firebase", URL(string: "https://firebase.google.com")! ] let controller = UIActivityViewController(activityItems: activities, applicationActivities: nil) return controller }() @IBAction func shareButtonPressed(_ sender: Any) { let inviteController = UIStoryboard(name: "Main", bundle: nil) .instantiateViewController(withIdentifier: "InviteViewController") self.navigationController?.pushViewController(inviteController, animated: true) }
Objective-C
- (UIActivityViewController *)shareController { if (_shareController == nil) { NSArray *activities = @[ @"Learn how to share content via Firebase", [NSURL URLWithString:@"https://firebase.google.com"] ]; UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:activities applicationActivities:nil]; _shareController = controller; } return _shareController; } - (IBAction)shareLinkButtonPressed:(UIView *)sender { if (![sender isKindOfClass:[UIView class]]) { return; } self.shareController.popoverPresentationController.sourceView = sender; [self presentViewController:self.shareController animated:YES completion:nil]; }
Kotlin+KTX
private fun onShareClicked() { val link = DynamicLinksUtil.generateContentLink() val intent = Intent(Intent.ACTION_SEND) intent.type = "text/plain" intent.putExtra(Intent.EXTRA_TEXT, link.toString()) startActivity(Intent.createChooser(intent, "Share Link")) }
Java
private void onShareClicked() { Uri link = DynamicLinksUtil.generateContentLink(); Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, link.toString()); startActivity(Intent.createChooser(intent, "Share Link")); }
在此示例中,默认分享界面会自动显示可用于分享链接的应用的列表,因此您只需使用几行代码即可在自己的应用中设置链接。
无需让用户在您的应用中选择联系人和撰写消息,只需要将这些工作委托给用户从分享对话框中选择的应用。此外,将分享流程委托给其他应用意味着您无需向用户索取通讯录访问权限,而且用户可以从所选应用内更全面的联系人列表中进行选择。为了更好地促进社交分享,您可以向动态链接添加社交媒体预览元数据,这些元数据将随链接一起显示在主流社交渠道中。
但有些时候,仅仅发送没有文字说明的纯链接并不足以实现有吸引力的引荐。如果能给链接附加一条简短消息,并在可能的情况下使用更丰富的呈现形式,用户在收到引荐时就能更好地理解引荐的价值主张:
iOS
![具有多个选项的邀请界面 激励引荐屏幕截图](https://firebase.google.cn/static/docs/dynamic-links/images/inviting-users-ios2.png?authuser=9&hl=vi)
![具有溢出选项的系统分享动作条 具有分享动作条的激励引荐屏幕截图](https://firebase.google.cn/static/docs/dynamic-links/images/inviting-users-ios3.png?authuser=9&hl=vi)
Android
![具有多个选项的邀请界面 激励引荐屏幕截图](https://firebase.google.cn/static/docs/dynamic-links/images/inviting-users-android2.png?authuser=9&hl=vi)
![具有溢出选项的系统分享动作条 具有分享动作条的激励引荐屏幕截图](https://firebase.google.cn/static/docs/dynamic-links/images/inviting-users-android3.png?authuser=9&hl=vi)
虽然这个示例比上一个更复杂,但方法基本相同。这个屏幕有一个展现邀请的价值主张的大图,以及用于分享至主流社交渠道的按钮。此界面流程中存在一些冗余,某些分享渠道会单独显示,以便针对这些渠道进一步定制消息,例如向电子邮件邀请添加一行主题。在这个邀请菜单中,我们可以执行以下操作:
- 显示电子邮件、短信以及“复制链接”分享按钮,并相应地定制各个渠道的消息。电子邮件将包含一个主题,并可包含一个有换行、图片和空白的较长正文。短信应包含一个较短的正文,其中有换行但极少有空白,没有图片。链接复制功能应仅复制链接,而不复制其他任何内容。
- 其他所有分享渠道可以使用系统分享界面,并为链接附上一条简短的邀请消息。
- 借助网址方案 (URL scheme) 或通用链接实现指向另一个应用的深层链接,该应用具有处理您的应用邀请的特殊逻辑。这只适用于您的组织与其他应用之间存在合作关系的情况,可能不适合小型组织。不过,某些应用可能会公开提供有关其通用/深层链接行为的文档。我们将在示例中实现此行为的一个样板版本。
首先,定义邀请内容的类型,其中仅包含邀请信息,不包含任何功能。这样,您可以从数据类型着手,并从如何将相关数据组合到一起的角度来构想您的代码。
Swift
/// The content within an invite, with optional fields to accommodate all presenters. /// This type could be modified to also include an image, for sending invites over email. struct InviteContent { /// The subject of the message. Not used for invites without subjects, like text message invites. var subject: String? /// The body of the message. Indispensable content should go here. var body: String? /// The URL containing the invite. In link-copy cases, only this field will be used. var link: URL }
Objective-C
/// The content within an invite, with optional fields to accommodate all presenters. /// This type could be modified to also include an image, for sending invites over email. @interface InviteContent : NSObject <NSCopying> /// The subject of the message. Not used for invites without subjects, like text message invites. @property (nonatomic, readonly, nullable) NSString *subject; /// The body of the message. Indispensable content should go here. @property (nonatomic, readonly, nullable) NSString *body; /// The URL containing the invite. In link-copy cases, only this field will be used. @property (nonatomic, readonly) NSURL *link; - (instancetype)initWithSubject:(nullable NSString *)subject body:(nullable NSString *)body link:(NSURL *)link NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; @end
Kotlin+KTX
/** * The content of an invitation, with optional fields to accommodate all presenters. * This type could be modified to also include an image, for sending invites over email. */ data class InviteContent( /** The subject of the message. Not used for invites without subjects, like SMS. */ val subject: String?, /** The body of the message. Indispensable content should go here. */ val body: String?, /** The URL containing the link to invite. In link-copy cases, only this field will be used. */ val link: Uri )
Java
/** * The content of an invitation, with optional fields to accommodate all presenters. * This type could be modified to also include an image, for sending invites over email. */ public class InviteContent { /** * The subject of the message. Not used for invites without subjects, like SMS. **/ @Nullable public final String subject; /** * The body of the message. Indispensable content should go here. **/ @Nullable public final String body; /** * The URL containing the link to invite. In link-copy cases, only this field will be used. **/ @NonNull public final Uri link; public InviteContent(@Nullable String subject, @Nullable String body, @NonNull Uri link) { // ... } }
此处唯一必需的数据是网址,如果没有网址,您将无法邀请用户使用您的应用。其他数据显然是为发送电子邮件而设,因而在其他某些情况下就不太适用:当通过短信发送邀请时,链接所附带的简短说明可能读起来与电子邮件的主题类似,但在分享至社交媒体时链接所附带的文本可能更像是电子邮件正文。您必须自己尝试一下,以便为自己的应用找到最佳平衡点;如果您不确定,可以使用 Remote Config 等服务,这些服务让您可以在应用发布后更改文本值。
Swift
/// A type responsible for presenting an invite given using a specific method /// given the content of the invite. protocol InvitePresenter { /// The name of the presenter. User-visible. var name: String { get } /// An icon representing the invite method. User-visible. var icon: UIImage? { get } /// Whether or not the presenter's method is available. iOS devices that aren't phones /// may not be able to send texts, for example. var isAvailable: Bool { get } /// The content of the invite. Some of the content type's fields may be unused. var content: InviteContent { get } /// Designated initializer. init(content: InviteContent, presentingController: UIViewController) /// This method should cause the presenter to present the invite and then handle any actions /// required to complete the invite flow. func sendInvite() }
Objective-C
/// A type responsible for presenting an invite given using a specific method /// given the content of the invite. @protocol InvitePresenter <NSObject> /// The name of the presenter. User-visible. @property (nonatomic, readonly) NSString *name; /// An icon representing the invite method. User-visible. @property (nonatomic, readonly, nullable) UIImage *icon; /// Whether or not the presenter's method is available. iOS devices that aren't phones /// may not be able to send texts, for example. @property (nonatomic, readonly) BOOL isAvailable; /// The content of the invite. Some of the content type's fields may be unused. @property (nonatomic, readonly) InviteContent *content; /// Designated initializer. - (instancetype)initWithContent:(InviteContent *)content presentingViewController:(UIViewController *)controller; /// This method should cause the presenter to present the invite and then handle any actions /// required to complete the invite flow. - (void)sendInvite; @end
Kotlin+KTX
/** * Presents the invite using a specific method, such as email or social. */ open class InvitePresenter( /** The user-visible name of the invite method, like 'Email' or 'SMS' */ val name: String, /** An icon representing the invite method. */ @param:DrawableRes @field:DrawableRes val icon: Int, /** Whether or not the method is available on this device. For example, SMS is phone only. */ val isAvailable: Boolean, /** The Content of the invitation */ val content: InviteContent ) { /** * Send the invitation using the specified method. */ open fun sendInvite(context: Context) { // ... } }
Java
/** * Presents the invite using a specific method, such as email or social. */ public class InvitePresenter { /** * The user-visible name of the invite method, like 'Email' or 'SMS' **/ public final String name; /** * An icon representing the invite method. **/ @DrawableRes public final int icon; /** * Whether or not the method is available on this device. For example, SMS is phone only. **/ public final boolean isAvailable; /** * The Content of the invitation **/ public final InviteContent content; public InvitePresenter(String name, @DrawableRes int icon, boolean isAvailable, InviteContent content) { // ... } /** * Send the invitation using the specified method. */ public void sendInvite(Context context) { // ... } }
最后要做的就是将此邀请流程插入您选择的界面组件中。如需了解实现此邀请流程的完整过程,请参阅 GitHub 上适用于 iOS 和 Android 的示例。
以上都是让用户能够向好友发送邀请的方法,而且是最简便的邀请解决方案。许多热门应用也使用自己的后端发送电子邮件,以此来递送邀请。虽然这需要集成邮件发送服务,但具有许多独特的优点,只有少数小缺点。
优点:
- 实现具有复杂标记的电子邮件,在发送之前用户无法修改。
- 实现更精细的跟踪/分析(即后端的发送成功和失败情况)。
缺点:
- 电子邮件更有可能被标记为垃圾邮件
- 需要与电子邮件递送服务集成
- 需要从应用内访问通讯录的权限
通常情况下,使用自己的电子邮件递送服务来发送邀请可为用户提供更一致且可能更丰富的邀请体验,代价是灵活性有所降低。
在您的应用中打开链接的内容
最后,您需要接收传递至您的应用的链接,以便将链接对应的内容显示给接收者。使用 Dynamic Links SDK 可以轻松实现此功能:
iOS
在 iOS 上,您可通过实现 application:continueUserActivity:restorationHandler:
方法来接收动态链接。在恢复处理程序中,您可以调用 handleUniversalLink:completion:
来获取动态链接。如果已经有一个动态链接传递至您的应用,您可以从 FIRDynamicLink
的 url
属性中获取该链接。例如:
Objective-C
[[FIRDynamicLinks dynamicLinks]
handleUniversalLink:userActivity.webpageURL
completion:^(FIRDynamicLink * _Nullable dynamicLink,
NSError * _Nullable error) {
NSString *link = dynamicLink.url;
BOOL strongMatch = dynamicLink.matchConfidence == FIRDynamicLinkMatchConfidenceStrong;
// ...
}];
Swift
FIRDynamicLinks.dynamicLinks()?.handleUniversalLink(userActivity.webpageURL!) { (dynamiclink, error) in
let link = dynamicLink.url
let strongMatch = dynamicLink.matchConfidence == FIRDynamicLinkMatchConfidenceStrong
// ...
}
此外,您必须在 application:openURL:options:
方法中调用 dynamicLinkFromCustomSchemeURL:
,才能收到作为自定义方案网址传递至您的应用的动态链接。例如:
Objective-C
FIRDynamicLink *dynamicLink = [[FIRDynamicLinks dynamicLinks] dynamicLinkFromCustomSchemeURL:url];
if (dynamicLink) {
NSString *link = dynamicLink.url;
BOOL strongMatch = dynamicLink.matchConfidence == FIRDynamicLinkMatchConfidenceStrong;
// ...
return YES;
}
Swift
let dynamicLink = FIRDynamicLinks.dynamicLinks()?.dynamicLinkFromCustomSchemeURL(url)
if let dynamicLink = dynamicLink {
let link = dynamicLink.url
let strongMatch = dynamicLink.matchConfidence == FIRDynamicLinkMatchConfidenceStrong
// ...
return true
}
现在您已经获得了 link
参数的值,可以向接收者显示链接对应的内容,或者以其他方式处理该参数指定的数据了。JLRoutes 等网址路由库可以帮助执行此任务。
如果您收到的是面向特定接收者的链接,则在运行任何针对特定用户的逻辑之前,请确保动态链接的匹配置信度为 strong
。
Android
在 Android 上,使用 getDynamicLink()
方法从动态链接中获取数据:
Kotlin+KTX
Firebase.dynamicLinks .getDynamicLink(intent) .addOnCompleteListener { task -> if (!task.isSuccessful) { // Handle error // ... } val invite = FirebaseAppInvite.getInvitation(task.result) if (invite != null) { // Handle invite // ... } }
Java
FirebaseDynamicLinks.getInstance() .getDynamicLink(getIntent()) .addOnCompleteListener(new OnCompleteListener<PendingDynamicLinkData>() { @Override public void onComplete(@NonNull Task<PendingDynamicLinkData> task) { if (!task.isSuccessful()) { // Handle error // ... } FirebaseAppInvite invite = FirebaseAppInvite.getInvitation(task.getResult()); if (invite != null) { // Handle invite // ... } } });
现在您已经获得了 link
参数的值,可以向接收者显示链接对应的内容,或者以其他方式处理该参数指定的数据了。网址路由库可以帮助执行此任务。