I. Custom Scheme
Communication between Android applications/components can be achieved through intent. Applications can register intent filter to declare what kind of intent they are interested in. When other applications send intent, it is delivered through system-level broadcasting. If it matches the pre-registered intent filter, the application will receive the intent (even if the application is not running, it will be "woken up", that is, Activity is implicitly started), extract the data carried by the intent, and perform further processing.
That's how it works, getting a chance to start through system broadcasting. For example, statically register intent filter in manifest to declare a custom scheme:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!--Register scheme-->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<!--BROWSABLE specifies that the Activity can be safely called by browser-->
<category android:name="android.intent.category.BROWSABLE"/>
<!--Declare custom scheme, similar to http, https-->
<data android:scheme="hoho"/>
</intent-filter>
</activity>
action, category, and data must all match completely to receive the intent. Here 2 category are declared, and only when the intent contains both category does it match. android.intent.category.DEFAULT is the default and has practical meaning. What actually matters is android.intent.category.BROWSABLE, which indicates that the activity is allowed to be started by a browser (launching the App). The subsequent data limits the trigger condition: only when the scheme is hoho will it match. For example, when a browser accesses hoho://abc, it matches successfully and the App starts.
II. Extracting Data
Get intent in onCreate and extract uri:
@Override
protected void onCreate(Bundle savedInstanceState) {
//...
// Get uri parameter
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();
//...Determine which page to open or which feature to open based on uri
}
}
The URI here is a standard URI with protocol, hostname, port, path, query string, etc. However, custom scheme usually doesn't need to be this complicated. Simple path/query differentiation is sufficient, for example:
// Differentiate by path
hoho://toFeature/login
// Differentiate by query
hoho://open?feature=login
Of course, it can also be differentiated by port number, etc. There's no significant difference.
III. Launch App from Web Pages
The browser first sends a custom scheme request, and the system broadcasts it to each application. There are many ways for pages to send requests:
location.href
iframe.src
a.href
img.src
...other ways to send requests
These methods differ in strength. For example, location.href is strong, while img.src is very weak. At minimum, it must be strong enough for the browser to decide to hand over the request to the system broadcast. For example, if img requests a custom scheme, the browser thinks there's no need to hand it over to the system broadcast. Generally, only the first 2 strongest methods are used: location.href and iframe.src. Hiding iframe to secretly request custom scheme is relatively more common because there are no unknown side effects (the location method might be recorded in the history stack or unload the current page, but the iframe method definitely has no serious side effects).
However, regardless of which method is used, there's no way to know whether the App has been launched. The App might not be installed, or the intent might not match successfully, but the page has no way to know. Therefore, pages that launch Apps usually delay automatic redirection to a download page, regardless of whether the App launch was successful. This is a last resort.
In addition to pages sending requests, there's a stronger method: sending requests through the application, for example:
// Send request through webview
webview.loadUrl(mySchemeUri);
This starts at the application level, which is stronger than page requests in WebView. Therefore, in Hybrid Apps, the client usually provides such interfaces for jumping to third parties, which is stronger than page requests.
IV. Intent Scheme URL Attacks
Custom Scheme has security risks, for example:
-
Register the same
intent filterwith higher priority to stealscheme uri -
If the custom
schemeformat for redirection is known, it can redirect to phishing pages (pages that are indeed opened in the App, but are fake ones made by third parties) -
...other risks
Generally, custom scheme are not public, but they may leak out somehow (reverse engineering Apps, etc.). The scheme interface itself should have proper protection. When receiving intent, you can do the following:
// 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);
Do not trust all input from custom scheme. For redirection interfaces, there should also be whitelist restrictions.
V. WebView Scheme Whitelist
WebView, as a page container, can filter/intercept page requests:
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);
}
}
The above is for API[11-20]. Version 21 deprecated String url and added WebResourceRequest request. For API21+, only the WebResourceRequest request form is triggered. Therefore, for compatibility, both must be overridden.
For requests that meet the filtering conditions, intercept them. This is why Apps cannot be launched in WeChat—because they're not on the whitelist and are intercepted before being handed over to the system broadcast.
When intercepted, the advantage of the iframe method becomes apparent. Both a.href and location.href cause page redirection, displaying "Web page not available...because net::ERR_UNKNOWN_URL_SCHEME", while the iframe method does not affect the current page.
VI. Demo
APK download link: http://ayqy.net/apk/android-scheme.apk
Test page: http://ayqy.net/temp/android-scheme.html
Final Notes
Android Studio is really too slow. I miss Eclipse. Additionally, thanks to @Xu.
No comments yet. Be the first to share your thoughts.