メインコンテンツへ移動

Android scheme でアプリを起動

無料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 はすべて完全に一致する必要があります。ここでは 2 つの category が宣言されており、intent がこの 2 つの category を同時に含んでいる場合にのみ一致とみなされます。android.intent.category.DEFAULT はデフォルトのもので、実際的な意味があるのは android.intent.category.BROWSABLE で、この activity をブラウザから起動できること(アプリを呼び出せること)を示します。後続の data はトリガー条件を限定し、schemehoho の場合にのみ一致します。例えばブラウザが hoho://abc にアクセスすると、一致に成功し、アプリが起動します。

二.データの取得

onCreateintent を取得し、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

もちろん、ポート番号などで区別することもできますが、大した違いはありません。

三.オンラインページからアプリを起動

ブラウザがカスタム scheme リクエストを送信すると、システムブロードキャストがそれを受け取り、各アプリに配信します。ページがリクエストを送信する方法は多数あります:

location.href
iframe.src
a.href
img.src
...その他リクエストを送信できる方法

これらの方法には強弱の違いがあります。例えば location.href は強く、img.src は非常に弱く、少なくともブラウザがそのリクエストをシステムブロードキャストに渡すと判断する程度の強さが必要です。例えば img がカスタム scheme をリクエストしても、ブラウザはシステムブロードキャストに渡す必要がないと判断します。一般的には最も強い 2 つの方法のみを使用します:location.hrefiframe.src です。iframe を隠してカスタム scheme をこっそりリクエストする方法がより多く使われています。未知の副作用がないからです(location 方式はおそらく履歴スタックに記録されたり、現在のページが unload されたりする可能性がありますが、iframeそれほど深刻な副作用が絶対にありません)。

しかし、どちらの方法でも、アプリが起動したかどうかを知ることはできません。アプリがインストールされていない可能性もありますし、intent が一致しなかった可能性もありますが、ページには知る手段が確かにありません。そのため、一般にアプリを起動するページは、ダウンロードページへの自動ジャンプを遅延させます。アプリの起動が成功したかどうかに関わらず、これもやむを得ないことです。

ページがリクエストを送信する他に、より強い方法があります:アプリからリクエストを送信する方法です。例えば:

// webview からリクエストを送信
webview.loadUrl(mySchemeUri);

この起点はアプリレベルであり、WebView 内のページのリクエストよりも強くなります。そのため、一般に Hybrid App では、クライアントがこのようなインターフェースを提供し、サードパーティへジャンプするために使用します。ページのリクエストよりも強くなります。

四.Intent Scheme URL 攻撃

カスタム Scheme にはセキュリティリスクが存在します。例えば:

  • より優先度の高い同じ intent filter を登録して、scheme uri を窃取する

  • ジャンプ先のカスタム scheme 形式が分かれば、フィッシングページへジャンプできる(確かにアプリ内で開かれたページだが、それは第三者が作った偽物)

  • ...その他のリスク

一般にカスタム scheme は公開されませんが、漏洩する可能性があります(アプリのデコンパイルなどの方法で)。scheme インターフェース自体に適切な防備が必要です。intent を受信する際には次のようにできます:

    // BROWSABLE カテゴリなしでの Activity 起動を禁止
    intent.addCategory("android.intent.category.BROWSABLE");
    // 明示的な呼び出しを禁止
    intent.setComponent(null);
    // セレクター intent を含む 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 形式のみがトリガーされるため、互換性のために両方を書き直す必要があります。

フィルタ条件を満たすものはインターセプトされるため、WeChat 内ではアプリを起動できません。ホワイトリストにないため、インターセプトされ、システムブロードキャストに渡されないからです。

インターセプトされた場合、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 が懐かしい。また、@旭 さんに感謝します。

参考資料

コメント

コメントはまだありません

コメントを書く