Web 唤起 Android app

Tania7453 7年前
   <p>最近在项目中遇到 web 唤起 Android app 的需求,实现很简单,简单记录下实现方式与背后原理。</p>    <h3>实现</h3>    <p>先不管唤起的原理,用一个简单的例子描述它的实现:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/90e4bae963c811fed0b0628b51409fa9.gif"></p>    <p>首先需要在 AndroidManifest.xml 中定义 scheme, scheme 不能和 http、https、ftp、sms、mailto 等已使用的相同 。</p>    <pre>  <code class="language-java"><activity android:name=".MainActivity">      <intent-filter>          <action android:name="android.intent.action.MAIN"/>          <category android:name="android.intent.category.LAUNCHER"/>      </intent-filter>      <!-- web 唤起添加的 filter -->      <intent-filter>          <action android:name="android.intent.action.VIEW" />          <category android:name="android.intent.category.DEFAULT" />          <category android:name="android.intent.category.BROWSABLE" />          <data              android:scheme="my.scheme"              android:host="my.host"              />      </intent-filter>  </activity>  </code></pre>    <p>下面是测试网页:</p>    <pre>  <code class="language-java"><!DOCTYPE html>  <html lang="zh-CN">  <head>      <meta charset="UTF-8">      <title>Web 唤起 app</title>  </head>  <body style="text-align: center">  <a href="my.scheme://my.host?name=xxx&title=xxx" style="font-: 27px">点击唤起 Demo app</a>  </body>  </html>  </code></pre>    <p>上面的链接中有 name 和 title 两个参数,app 也能接收到,所以在唤起 app 时也能传一些数据</p>    <pre>  <code class="language-java">@Override  protected void onCreate(Bundle savedInstanceState) {      super.onCreate(savedInstanceState);      setContentView(R.layout.activity_main);        Intent intent = getIntent();      if (null != intent && null != intent.getData()) {          Uri uri = intent.getData(); // uri 就相当于 web 页面中的链接          String name = uri.getQueryParameter("name");          String title = uri.getQueryParameter("title");      }  }  </code></pre>    <h3>原理</h3>    <p>my.scheme://my.host?name=xxx&title=xxx 其实也是一个链接,为什么点击这个链接浏览器就会启动相应的 app 呢?</p>    <p>其实关键在 WebView 的 WebViewClient 的 shouldOverrideUrlLoading 方法,基本上所有的浏览器都会有类似的实现,下面分析 Android 浏览器的源码。</p>    <p>Android 6.0 的原生浏览器的 shouldOverrideUrlLoading 方法的核心实现在 <a href="/misc/goto?guid=4959746483368727891" rel="nofollow,noindex">UrlHandler</a> 这个类中。</p>    <pre>  <code class="language-java">boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) {      ...      // The "about:" schemes are internal to the browser; don't want these to      // be dispatched to other apps.      if (url.startsWith("about:")) {          return false;      }      ...        if (startActivityForUrl(tab, url)) {          return true;      }        if (handleMenuClick(tab, url)) {          return true;      }        return false;  }  </code></pre>    <p>从上面第 5 行代码中可以看到 scheme 也不能为 about ,这是原生浏览器内部用的,唤起 app 的关键在第 10 行的 startActivityForUrl 方法。</p>    <pre>  <code class="language-java">boolean startActivityForUrl(Tab tab, String url) {      Intent intent;      // perform generic parsing of the URI to turn it into an Intent.      try {          intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);      } catch (URISyntaxException ex) {          Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());          return false;      }        // check whether the intent can be resolved. If not, we will see      // whether we can download it from the Market.      ResolveInfo r = null;      try {          r = mActivity.getPackageManager().resolveActivity(intent, 0);      } catch (Exception e) {          return false;      }      ...      // sanitize the Intent, ensuring web pages can not bypass browser      // security (only access to BROWSABLE activities).      intent.addCategory(Intent.CATEGORY_BROWSABLE);      intent.setComponent(null);      Intent selector = intent.getSelector();      if (selector != null) {      selector.addCategory(Intent.CATEGORY_BROWSABLE);      selector.setComponent(null);      }      ...      try {          intent.putExtra(BrowserActivity.EXTRA_DISABLE_URL_OVERRIDE, true);      if (mActivity.startActivityIfNeeded(intent, -1)) { // 唤起 app 的最终代码在这里          // before leaving BrowserActivity, close the empty child tab.          // If a new tab is created through JavaScript open to load this          // url, we would like to close it as we will load this url in a          // different Activity.          mController.closeEmptyTab();              return true;          }      } catch (ActivityNotFoundException ex) {          // ignore the error. If no application can handle the URL,          // eg about:blank, assume the browser can handle it.      }        return false;  }  </code></pre>    <p>上面 22 行 intent.addCategory(Intent.CATEGORY_BROWSABLE); 也可以看出我们之前在 加` `的原因。</p>    <p>而如果第三方的浏览器在这个地方对 scheme 屏蔽,就可以让 web 唤起 app 实效,微信中网页不能唤起应用就是这个原因。</p>    <p> </p>    <p>来自:http://johnnyshieh.github.io/android/2017/03/30/web-evoke-app/</p>    <p> </p>