唤醒 App 的那些事

zxmnb68 7年前
   <p><img src="https://simg.open-open.com/show/ad24887e64ba95c9b1404e4eea5902fc.jpg"> 移动互联时代,很多互联网服务都会同时具备网站以及移动客户端,很多人认为APP的能帮助建立更稳固的用户关系,于是经常会接到各种从浏览器、webview、短信、甚至是在其他APP中唤醒APP的运营需求。</p>    <h3><strong>运营推广场景</strong></h3>    <p><strong>1、微信、QQ等 -> 唤醒APP</strong></p>    <p>用户通过某APP分享了一条链接至微信或QQ,用户B点开该链接后,会引导用户B打开该APP或者下载该APP。</p>    <p><strong>2、浏览器 -> 唤醒APP</strong></p>    <p>用户A通过浏览器打开了某APP的M站或者官网,如果检测到A来自手机端,则会引导用户打开该APP或者下载该APP。</p>    <p><strong>3、 短信、邮件、二维码等 -> 唤醒APP</strong></p>    <p>用户A打开了某APP的推广短信,邮件或者扫描二维码等,会引导用户打开该APP或者下载该APP。</p>    <p><strong>4、其他APP -> 唤醒APP</strong></p>    <p>用户A通过第三方APP分享了(任何可以分享信息的品台或工具:IM或者短信等)一条链接至用户B,用户B点开该链接后,链接会引导用户B打开指定APP或者下载指定APP。</p>    <h3><strong>APP服务化理念</strong></h3>    <p><strong>所谓APP的服务化就是利用唤醒功能将APP的特定页面做为一个单独的服务或者内容,通过一定的渠道和载体传播出去,并且能够像传统的网页链接那样被一键唤醒。</strong></p>    <p>更多关于APP服务化理念,推荐大家看看这篇文章。</p>    <p>那么移动平台提供了哪些唤醒APP的方法呢?</p>    <h3><strong>如何唤醒APP</strong></h3>    <p>目前常见的唤醒APP方式有几种:</p>    <ul>     <li> <p><strong>URL Scheme</strong></p> </li>    </ul>    <p>URL Scheme 是iOS,Android平台都支持,只需要原生APP开发时注册 scheme , 那么用户点击到此类链接时,会自动唤醒APP,借助于 URL Router 机制,则还可以跳转至指定页面。比如:</p>    <pre>  <code class="language-java"><!-- 唤醒APP并跳转至指定的path页面 -->  <a href="<scheme>://<path>?<params>=<value>">打开APP</a>    <!-- JS设置iframe src跳转至指定的path页面 -->  //创建一个隐藏的iframe  var ifr = document.createElement('iframe');  ifr.src = '<scheme>://<path>?<params>=<value>';  ifr.style.display = 'none';  document.body.appendChild(ifr);  //记录唤醒时间  var openTime = +new Date();  window.setTimeout(function(){      document.body.removeChild(ifr);      //如果setTimeout 回调超过2500ms,则弹出下载      if( (+new Date()) - openTime > 2500 ){          window.location = '指定的下载页面';      }  },2000)</code></pre>    <p>这种方式是当期使用最广泛,也是最简单的,但是需要手机,APP支持 URL Scheme 。 </p>    <p><strong>优点:</strong>开发成本低,绝大多数都支持,web-native协议制定也简单。 </p>    <p><strong>缺点:</strong>错误处理情况因平台不同,难以统一处理,部分APP会直接跳错误页(比如Android Chrome/41,iOS中老版的Lofter);也有的停留在原页面,但弹出提示“无法打开网页”(比如iOS7);iOS8以及最新的Android Chrome/43 目前都是直接停留在当前页,不会跳出错误提示。 </p>    <p><strong>支持情况:</strong>iOS在实际使用中,腾讯系的微信,QQ明确禁止使用,iOS9以后Safari不再支持通过js,iframe等来触发scheme跳转,并且还加入了确认机制,使得通过js超时机制来自动唤醒APP的方式基本不可用;Android平台则各个app厂商差异很大,比如Chrome从25及以后就同Safari情况一样。</p>    <ul>     <li> <p><strong>Android intent</strong></p> </li>    </ul>    <p>这是Android平台独有的,使用方式如下:</p>    <pre>  <code class="language-java">intent:  HOST/URI-path // Optional host   #Intent;     package=[string];     action=[string];     category=[string];     component=[string];     scheme=[string];   end;</code></pre>    <p>这里的HOST/URI-path, 与普通http URL 的host/path书写方式相同, package是Android APP的包名,其它参数如action、category、component不是很理解, 有兴趣可以去了解官方文档。代码如下:</p>    <pre>  <code class="language-java"><!-- 唤醒APP并跳转至指定的path页面 -->  <a href="intent://<role>/<path>#Intent;scheme=<scheme>;package=com. domain;end"">打开APP</a></code></pre>    <p>如果手机能匹配到相应的APP,则会直接打开;如没有安装,则会跳到手机默认的应用商店,比如Google原生系统Nexus 5,将会直接跳到Google Play,对于国内各厂商定制过的系统,则跳转到各自的默认应用商店,或者弹出商店供选择。 intent 比 scheme 相对完善的一点是,提供一个打开失败去向URL的选项,可以通过指定参数 S.browser_fallback_url 来指定去向URL。比如打开APP动作,如果打开失败,则跳转到APP下载页,这对于国内的特殊网络环境,还是挺有用的。</p>    <ul>     <li> <p><strong>Safari内置APP广告条</strong></p> </li>    </ul>    <p>在页面 Head 中增加如下 meta , 添加智能App广告条,可以自动判断是否已安装应用,只能用于Safari,在第三方应用中就不行了。</p>    <pre>  <code class="language-java"><meta"apple-itunes-app"content"app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL"</code></pre>    <ul>     <li> <p><strong>Android Chrome内置APP安装提示</strong></p> </li>    </ul>    <p>这个是Mobile Chrome 43 beta新加入的特性,在用户浏览某一个网站多次后,如果Chrome发现该站点有原生APP,则会提示用户下载原生APP,此项特性开发者无法干预,完全是Google的推荐行为。</p>    <ul>     <li> <p><strong>Universal Links</strong></p> </li>    </ul>    <p>在2015年的WWDC大会上,Apple推出了iOS 9的一个功能:Universal Links通用链接。如果你的App支持Universal Links,那就可以访问HTTP/HTTPS链接直接唤起APP进入具体页面,不需要其他额外判断;如果未安装App,访问此通用链接时,可以一个自定义网页。</p>    <p><strong>优点:</strong></p>    <p><strong>1、唯一性:</strong>不像自定义的scheme,因为它使用标准的HTTP/HTTPS链接到你的web站点,所以它不会被其它的APP所声明。另外,URL scheme 因为是自定义的协议,所以在没有安装 APP 的情况下是无法直接打开的,而Universal Links本身是一个HTTP/HTTPS链接,所以有更好的兼容性;</p>    <p><strong>2、安全:</strong>当用户的手机上安装了你的 APP ,那么iOS将去你的网站上去下载你上传上去的说明文件(这个说明文件声明了APP可以打开哪些类型的http链接)。因为只有你自己才能上传文件到你网站的根目录,所以你的网站和你的 APP 之间的关联是安全的;</p>    <p><strong>3、可变:</strong>当用户手机上没有安装你的 APP 的时候,Universal Links也能够工作。如果你愿意,在没有安装APP的时候,用户点击链接,会在safari中展示你网站的内容;</p>    <p><strong>4、简单:</strong>一个URL链接,可以同时作用于网站和 APP ,可以定义统一的web-native协议;</p>    <p><strong>5、私有:</strong>其它APP可以在不需要知道是否安装了的情况下和你的APP相互通信;</p>    <p><strong>缺点:</strong></p>    <p>1、只支持iOS9及以上系统;当使用Universal Link打开APP之后,状态栏右上角会出现链接地址,点击它会取消Universal Link,需引导用户重新使用Safari再次打开该链接,弹出Safari内置APP广告条,再点击打开重新开启Universal Link。</p>    <p><strong>iOS9开启Universal Links</strong></p>    <p>首先,你必须有一个域名,且这个域名的网站需要支持https,然后拥有网站的上传到.well-known目录的权限(这个权限是为了上传一个Apple指定的文件 apple-app-site-association ),有了这个先决条件才能够继续下面的步骤:</p>    <p>1、创建一个json格式的命名为 apple-app-site-association 文件,注意这个文件必须没有后缀名,文件名必须为 apple-app-site-association !!! </p>    <pre>  <code class="language-java">{      "applinks": {          "apps": [],          "details": [              {                  "appID": "9JA89QQLNQ.com.apple.wwdc",                   "paths": [ "/wwdc/news/", "/videos/wwdc/2015/*"]              },              {                  "appID": "ABCD1234.com.apple.wwdc",                   "paths": [ "*" ]              }          ]      }  }</code></pre>    <p>说明: appID = teamId.yourapp's bundle identifier paths = APP支持的路径列表,只有这些指定的路径的链接,才能被APP所处理,大小写敏感。举个例子,如果你的网站是 www.domain.com ,你的path写的是"/support/*",那么当用户点击 www.domain.com/support/<path>?<params>=<value> ,就可以唤醒APP了,相反 www.domain.com/other 就不会。此外Apple为了方便开发者,提供了一个网址来验证我们编写的这个 apple-app-site-association 是否合法有效。</p>    <p>2、 激活Xcode工程中的 Associated Domains 能力,在其中的Domains中填入你想支持的域名(这里不是随便填的,是可以支持你需要的Universal Links的域名), 必须以 applinks: 为前缀,例如: applinks:www.domain.com Apple将会在合适的时候,从这个域名请求 apple-app-site-association 文件。注意:当你打开 Associated Domains 后,Xcode会在你的工程中添加 .entitlements 文件,并且登录开发者中心,可以看到 Associated Domains 处于Enable状态。</p>    <p>3、在 AppDelegate 里实现如下代理方法:</p>    <pre>  <code class="language-java">- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler {      // NSUserActivityTypeBrowsingWeb 由Universal Links唤醒的APP      if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {          NSURL *webpageURL = userActivity.webpageURL;          NSString *host = webpageURL.host;          if ([host isEqualToString:@"yohunl.com"]) {              //进行我们需要的处理          } else {              [[UIApplication sharedApplication]openURL:webpageURL];          }      }      return YES;  }</code></pre>    <p>至此APP已经开启 Universal Links ,可以通过链接唤醒APP,并跳转至指定页面了。</p>    <ul>     <li> <p><strong>Android App Links</strong></p> </li>    </ul>    <p>在2015年的Google I/O大会上,Android M宣布了一个新特性:App Links让用户在点击一个普通web链接的时候可以打开指定APP的指定页面,前提是这个APP已经安装并且经过了验证,否则会显示一个打开确认选项的弹出框。在推动deep linking上Google和Apple可谓英雄所见略同,优缺点也大致相同,只支持Android M以上系统。</p>    <p><strong>Android M开启Universal Links</strong></p>    <p>开启 Android App Links 的方式也大致同iOS一致:</p>    <p>先决条件:</p>    <ul>     <li> <p>注册一个域名</p> </li>     <li> <p>域名的SSL通道</p> </li>     <li> <p>具有上传JSON文件到域名的能力</p> </li>     <li> <p>Android Studio 1.3 Preview 及以上</p> </li>     <li> <p>Gradle 版本 — com.android.tools.build:gradle:1.3.0-beta3 及以上</p> </li>     <li> <p>设置 compileSdkVersion 为 android-MNC 及以上</p> </li>     <li> <p>buildToolsVersion — 23.0.0 rc2 及以上</p> </li>    </ul>    <p>创建一个json格式的web-app关联文件,如 assetlinks.json ,上传到web端</p>    <pre>  <code class="language-java">[{    "relation": ["delegate_permission/common.handle_all_urls"],    "target": {      "namespace": "android_app",      "package_name": "example.com.puppies.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"]    }    },    {    "relation": ["delegate_permission/common.handle_all_urls"],    "target": {      "namespace": "android_app",      "package_name": "example.com.monkeys.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"]    }  }]</code></pre>    <p>其中 package_name : manifest中声明的包名。  sha256_cert_fingerprints : 可以使用如下命令生成APP的sha256指纹签名 // keystore中持有app release keys的app路径。 // 这个路径依赖于项目设置,因此不同的app是不同的。 keytool -list -v -keystore my-release-key.keystore 上传这个文件到服务器的 .well-known/assetlinks.json ,为了避免今后每个app链接请求都访问网络,安卓只会在app安装的时候检查这个文件。</p>    <p>1、创建一个处理 App Links 的activity,这个activity的目的是为了实现一种这样的机制:负责捕获与解析深度链接,同时转发用户到正确的视图。同时配置激活 App Links 能力,如下所示:</p>    <pre>  <code class="language-java"><activity    android:name="com.your.app.activity.ParseDeepLinkActivity"    android:alwaysRetainTaskState="true"    android:launchMode="singleTask"    android:noHistory="true"    android:theme="@android:style/Theme.Translucent.NoTitleBar">        // 此处激活 App Links    <intent-filter android:autoVerify="true">      // 注意yourdomain.com 与 www.yourdomain.com 被看成两个不同的域名,因此你需要为每个域名添加一对http和https      <data android:scheme="http" android:host="yourdomain.com" />      <data android:scheme="https" android:host="yourdomain.com" />      <action android:name="android.intent.action.VIEW" />      <category android:name="android.intent.category.DEFAULT" />      <category android:name="android.intent.category.BROWSABLE" />    </intent-filter>  </activity></code></pre>    <p>2、实现 App Links activity的处理逻辑</p>    <pre>  <code class="language-java">public class ParseDeepLinkActivity extends Activity {    public static final String PRODUCTS_DEEP_LINK = "/products";    public static final String XMAS_DEEP_LINK = "/campaigns/xmas";        @Override    public void onCreate(Bundle savedInstanceState) {      super.onCreate(savedInstanceState);      setContentView(R.layout.main);         // Extrapolates the deeplink data      Intent intent = getIntent();      Uri deeplink = intent.getData();         // Parse the deeplink and take the adequate action       if (deeplink != null) {        parseDeepLink(deeplink);      }    }        private void parseDeepLink(Uri deeplink) {      // The path of the deep link, e.g. '/products/123?coupon=save90'      String path = deeplink.getPath();         if (path.startsWith(PRODUCTS_DEEP_LINK)) {        // Handles a product deep link        Intent intent = new Intent(this, ProductActivity.class);        intent.putExtra("id", deeplink.getLastPathSegment()); // 123        intent.putExtra("coupon", deeplink.getQueryParameter("coupon")); // save90        startActivity(intent);      } else if (XMAS_DEEP_LINK.equals(path)) {        // Handles a special xmas deep link        startActivity(new Intent(this, XmasCampaign.class));      }  else {        // Fall back to the main activity        startActivity(new Intent(context, MainActivity.class));      }    }  }</code></pre>    <p>至此APP已经开启 App Links ,可以通过链接唤醒APP,并跳转至指定页面了。</p>    <h3><strong>后记</strong></h3>    <p>总结以上各种方案,唤醒能力似乎都不是很完美,从长远技术趋势来看都是Deep Links,都需要</p>    <ul>     <li> <p><strong>一个支持HTTPS的web站</strong></p> </li>    </ul>    <p>但面对移动互联网浪潮中海量APP的唤醒能力需求,一定会有创业公司来做这件事,比如国外的HoKoLinks,国内的魔窗,是自己造轮子,还是用轮子,各有利弊。</p>    <p> </p>    <p> </p>    <p>来自:http://mp.weixin.qq.com/s?__biz=MzAwNjAzNjMyOQ==&mid=2650215008&idx=1&sn=5de38cea68bb86589680a93e679fc9d1&chksm=83103c66b467b5703c1b78d8bafc79a867a611be5672b9ed259577757fb6fa0ff8d42c954f69&scene=1&srcid=09247GuNPrKh2VyqHt6Pydxe&from=groupmessage&isappinstalled=0</p>    <p> </p>