跳到主要內容
黯羽輕揚每天積累一點點

Android scheme 呼起 App

免費2017-01-15#Android#Android自定义scheme#Android呼起客户端#网页打开App#Android拦截请求

頁面如何呼起 Android 用戶端

一。自定義 Scheme

Android 應用/組件間通信有一種方式是 intent,應用可以註冊intent filter 聲明自己對什麼樣的 intent 感興趣,其它應用發送 intent 時通過系統級廣播傳遞過來,如果與預先註冊的 intent filter 匹配,應用將收到該 intent(無論應用是否正在運行,都會被「喚醒」,也就是隱式啟動 Activity),取出 intent 攜帶的數據,做進一步處理

就是這樣,通過系統廣播拿到一次起來的機會,例如在 manifest 裡靜態註冊intent filter 聲明自定義 scheme

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <!--註冊 scheme-->
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <!--BROWSABLE 指定該 Activity 能被瀏覽器安全調用-->
        <category android:name="android.intent.category.BROWSABLE"/>
        <!--聲明自定義 scheme,類似於 http, https-->
        <data android:scheme="hoho"/>
    </intent-filter>
</activity>

actioncategorydata 都必須完全匹配才能獲得 intent,這裡聲明了 2 個 category,只有在 intent 同時含有這 2 個 category 時才算匹配,而 android.intent.category.DEFAULT 是預設的,有實際意義的是 android.intent.category.BROWSABLE,表示允許通過瀏覽器啟動該 activity(呼起 App)。後續的 data 限定了觸發條件,當 schemehoho 時才匹配,例如瀏覽器訪問 hoho://abc,能夠匹配成功,App 就起來了

二。取出數據

onCreate 裡拿到 intent,取出 uri

@Override
protected void onCreate(Bundle savedInstanceState) {
    //...

    // 獲取 uri 參數
    Intent intent = getIntent();
    String scheme = intent.getScheme();
    Uri uri = intent.getData();
    String str = "";
    if (uri != null) {
        String host = uri.getHost();
        String dataString = intent.getDataString();
        String from = uri.getQueryParameter("from");
        String path = uri.getPath();
        String encodedPath = uri.getEncodedPath();
        String queryString = uri.getQuery();

        //...根據 uri 判斷打開哪個頁,或者打開哪個功能
    }
}

這裡的 URI 就是標準的 URI,有協議、主機名、端口號、路徑、查詢字符串等等,但一般自定義 scheme 不需要這麼麻煩,只用 path/query 做簡單區分就行,比如:

// 通過 path 區分
hoho://toFeature/login
// 通過 query 區分
hoho://open?feature=login

當然,也可以通過端口號等區分,沒什麼區別

三。在線頁面呼起 App

瀏覽器先發出自定義 scheme 請求,系統廣播收到後再分發給各應用,那麼頁面發送請求的方式就多了:

location.href
iframe.src
a.href
img.src
...其它能發出請求的方式

這些方式在強弱上有區別,比如 location.href 是強的,而 img.src 很弱,至少要強到瀏覽器決定把這個請求交給系統廣播才行,比如 img 請求自定義 scheme,瀏覽器認為沒有必要交給系統廣播。一般只用前 2 種最強的方式:location.hrefiframe.src,隱藏 iframe 偷偷請求自定義 scheme 相對用得更多,因為不會有未知的副作用(location 方式或許可能被記入歷史棧或者 unload 當前頁,但 iframe 絕對沒有太嚴重的副作用

但無論哪種方式,都無法得知 App 被呼起了沒,可能沒安裝 App,也可能 intent 沒匹配成功,但頁面肯定沒有辦法得知。所以一般呼起 App 的頁面都會延遲自動跳轉下載頁,無論有沒有成功呼起 App,這也是迫不得已

除了頁面發出請求,還有一種更強的方式:通過應用發出請求,例如:

// 通過 webview 發出請求
webview.loadUrl(mySchemeUri);

這個起點就是應用級,比 WebView 中頁面請求要強一些。所以一般 Hybrid App 中,用戶端會提供這樣的接口,用來跳轉第三方,比頁面請求更強

四。Intent Scheme URL 攻擊

自定義 Scheme 存在安全風險,比如:

  • 註冊優先順序更高的相同 intent filter,竊取 scheme uri

  • 如果知道跳轉的自定義 scheme 格式,可以跳向釣魚頁面(確實是在 App 裡打開的頁面,但它是第三方做的假的)

  • ...其它風險

一般自定義 scheme 都是不公開的,但難免會洩漏出去(反編譯 App 等方式),scheme 接口本身要做好防範,接收 intent 時可以這樣做:

    // forbid launching activities without BROWSABLE category
    intent.addCategory("android.intent.category.BROWSABLE");
    // forbid explicit call
    intent.setComponent(null);
    // forbid intent with selector intent
    intent.setSelector(null);

不信任所有來自自定義 scheme 的輸入,對於跳轉接口,還要有白名單限制

五。WebView Scheme 白名單

WebView 作為頁面容器,可以過濾/攔截頁面請求

class MyWebClient extends WebViewClient {

    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        if (url.startsWith("hoho://")) {
            return null;
        }

        return super.shouldInterceptRequest(view, url);
    }

    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
        String scheme = request.getUrl().getScheme();
        if (scheme.equals("hoho")) {
            return null;
        }

        return super.shouldInterceptRequest(view, request);
    }
}

上面的用於 API[11-20],21 棄用String url,新增了WebResourceRequest request,在 API21+ 只觸發WebResourceRequest request 形式的,所以兼容考慮,兩個都要重寫一遍

對於滿足過濾條件的,攔截掉,所以在微信裡無法呼起 App,因為不在白名單裡,被攔截下來,沒有交給系統廣播

在被攔截的情況下,iframe 方式的優勢就體現出來了,a.hreflocation.href 都會導致頁面跳轉,顯示「網頁無法打開...因為 net::ERR_UNKNOWN_URL_SCHEME」,而 iframe 方式不影響當前頁

六。Demo

apk 下載地址:http://ayqy.net/apk/android-scheme.apk

測試頁面:http://ayqy.net/temp/android-scheme.html

寫在最後

Android Studio 實在太慢了,懷念 eclipse,另外,感謝 @旭

參考資料

評論

暫無評論,快來發表你的看法吧

提交評論