要吸引新用户安装您的应用,最有效的方式之一是鼓励现有用户与好友分享应用中的内容。借助 Dynamic Links,您可以打造出色的用户间分享体验,即用户在收到好友推荐的内容后,点击链接便可直接访问您应用中分享的内容(即使他们可能必须先转到 App Store 或 Google Play 商店安装您的应用)。
通过将用户引荐的粘性与 Dynamic Links 的持久性结合在一起,您可以打造用户间分享和引荐功能,从而让新用户直接看到相关的应用内容或开展让引荐双方均受益的推广活动,以此吸引新用户。
主要优势
- 第一次打开您的应用的新用户可享受(根据好友想要与其分享的内容)为其特别定制的首次运行体验。例如,您可以显示分享给他们的内容,或者自动在这些用户与邀请他们的朋友之间建立联系。
- 让用户可以与多个平台上的好友轻松分享内容,无论这些好友是否安装了您的应用。
下面介绍如何开始添加这项功能!
设置 Firebase 和 Dynamic Links SDK
建立一个新的 Firebase 项目,并将 Dynamic Links SDK 安装到您的应用中。
安装 Dynamic Links SDK 后,Firebase 便可向应用传递有关 Dynamic Links 动态链接的数据,其中就包括在用户安装应用后传递此类数据。
创建 Dynamic Links 动态链接
现在我们来设置用户可发送给好友的链接。如果您用户的好友还没有安装应用,您也不用担心,因为 Dynamic Links 动态链接可以帮您搞定。
为应用内每个可供分享的内容元素创建 Dynamic Links 动态链接。
创建 Dynamic Links 动态链接时,您需要提供 HTTP 或 HTTPS 网址作为 link
参数,用于标识您要分享的内容。如果您的网站上具有同等内容,您应当使用该网站的网址。这将确保这些链接在不支持 Dynamic Links 动态链接的平台(如桌面浏览器)上正确打开。例如:
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 来生成更简练的网址。短 Dynamic Links 动态链接的形式如下所示:
https://example.page.link/WXYZ
无论您使用哪个链接,当用户在其设备上打开上述 Dynamic Links 动态链接时,如果尚未安装 apn
参数(在 Android 上)或 ibi
和 isi
参数(在 iOS 上)所指定的应用,则会被引导至 Play 商店或 App Store 安装该应用。然后,在用户安装并打开该应用后,系统会随即将“link”参数中指定的网址传递给该应用。
添加用于发送 Dynamic Links 动态链接的“分享”按钮
首先,我们来看一个简单示例。这是一个类似于 Hangouts 的聊天室应用,可生成邀请人们进入聊天室的链接。
iOS


Android


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; }
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(); }
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 }
获得动态链接后,您可以向界面添加一个分享按钮,以启动标准平台分享流程:
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]; }
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")); }
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")) }
在此示例中,默认分享界面会自动显示可用于分享链接的应用的列表,因此您只需使用几行代码即可在自己的应用中设置链接。
您不是让用户在您的应用中选择联系人和撰写消息,而是将这些工作委托给用户从分享对话框中选择的应用。 此外,将分享流程委托给其他应用意味着您无需向用户索取通讯录访问权限,而且用户可以从所选应用内更全面的联系人列表中进行选择。为了更好地促进社交分享,您可以添加社交媒体预览元数据至您的动态链接,这些元数据将与链接一起在大多数社交渠道中显示。
但是,有时仅仅发送没有文字说明的纯链接并不足以实现富有吸引力的引荐。如果能给链接附加一条简短消息,并在可能的情况下使用更丰富的呈现形式,用户在收到引荐时就能更好地理解引荐的价值主张:
iOS


Android


虽然这个示例比上一个更复杂,但方法基本相同。在这个屏幕上有一张用于展现邀请价值主张的大图,以及用于分享至主要社交渠道的按钮。此界面流程中存在一些冗余,某些分享渠道会单独显示,以便针对这些渠道进一步定制消息(例如向电子邮件邀请添加一行主题)。在这个邀请菜单中,我们可以执行以下操作:
- 显示电子邮件、短信以及“复制链接”分享按钮,并相应地定制各个渠道的消息。电子邮件将包含一个主题,并可包含一个有换行、图片和空白的较长正文。短信应包含一个较短的正文,其中有换行但极少有空白,没有图片。链接复制功能应仅复制链接,而不复制其他任何内容。
- 其他所有分享渠道可以使用系统分享界面,并为链接附上一条简短的邀请消息。
- 借助网址架构或通用链接实现指向另一个应用的深层链接,该应用具有处理您的应用邀请的特殊逻辑。这只适用于您的组织与其他应用之间存在合作关系的情况,可能不适合小型组织。不过,某些应用可能会公开提供有关其通用/深层链接行为的文档。我们将在我们的示例中实现此行为的一个样板版本。
首先,定义邀请内容类型,其中仅包含邀请信息,不包含任何功能。这样,您可以从数据类型着手,并从如何将相关数据组合到一起的角度来构想您的代码。
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
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) { // ... } }
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 )
此处唯一必需的数据是网址,如果没有网址,您将无法邀请用户使用您的应用。其他数据显然是为发送电子邮件而设,因而在其他某些情况下就不太适用:当通过短信发送邀请时,链接所附带的简短说明可能读起来与电子邮件的主题类似,但在分享至社交媒体时链接所附带的文本可能更像是电子邮件正文。您必须自己尝试一下,以便为自己的应用找到最佳平衡点;如果您不确定,可以随时使用 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
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) { // ... } }
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) { // ... } }
最后要做的就是将此邀请流程插入您选择的界面组件中。如需了解实现此邀请流程的完整过程,请参阅 GitHub 上适用于 iOS 和 Android 的示例。
以上就是让用户能够向好友发送邀请的所有方法,而且是最简便的邀请解决方案。许多热门应用也使用自己的后端发送电子邮件,以此来递送邀请。这需要集成邮件发送服务,但具有许多独特的优点,只有少数小缺点。
优点:
- 实现具有复杂标记的电子邮件,在发送之前用户无法修改。
- 实现更精细的跟踪/分析(即后端的发送成功和失败情况)。
缺点:
- 电子邮件更有可能被标记为垃圾邮件
- 需要与电子邮件递送服务集成
- 需要从应用内访问通讯录的权限
通常情况下,使用自己的电子邮件递送服务来发送邀请可为用户提供更一致且可能更丰富的邀请体验,代价是灵活性有所降低。
在您的应用中打开链接的内容
最后,您需要接收被传递至应用的链接,以便将链接对应的内容显示给接收者。使用 Dynamic Links SDK 可以轻松实现此功能:
iOS
在 iOS 上,您可通过实现 application:continueUserActivity:restorationHandler:
方法来接收 Dynamic Links 动态链接。在恢复处理程序中,您可以通过调用 handleUniversalLink:completion:
来获取 Dynamic Links 动态链接。如果已经有一个 Dynamic Links 动态链接传递至您的应用,您可以从 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:
,才能收到作为自定义架构网址传递至您的应用的 Dynamic Links 动态链接。例如:
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 等网址路由库可以帮助执行此任务。
如果您收到的是面向特定接收者的链接,则在运行任何针对特定用户的逻辑之前,请确保 Dynamic Links 动态链接的匹配置信度为 strong
。
Android
在 Android 上,您可以使用 getDynamicLink()
方法从 Dynamic Links 动态链接中获取数据:
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 // ... } } });
Kotlin+KTX
Firebase.dynamicLinks .getDynamicLink(intent) .addOnCompleteListener { task -> if (!task.isSuccessful) { // Handle error // ... } val invite = FirebaseAppInvite.getInvitation(task.result) if (invite != null) { // Handle invite // ... } }
现在您已经获得了 link
参数的值,可以向接收者显示链接对应的内容,或者以其他方式处理参数指定的数据了。网址路由库可以帮助执行此任务。