本文为大家介绍利用 URL Scheme 在应用内任意跳转的实现,通过本地配置文件配置打开你想要打开的任意页面. 抛弃传统的 Manifest 中 <intent-filter> 配置.
首先介绍下什么是 URL Scheme ?
URL Scheme 可以通过类似打开网页的方式去通过路由打开一个 Activity,其协议格式和我们打开网页输入的网址类似。
URL Scheme 的格式
一个完整的完整的 URL Scheme 协议格式由 Scheme、Host、Port、Path 和 Query 组成,其结构如下所示:
<scheme>://<host>:<port>/<path>?<query>
来个栗子:
结构 | 对应栗子 |
---|---|
Scheme (协议名称) | internal |
Host (主机) | card |
Port (端口) | 8080 |
Path (路径) | cardDetail |
Query (需要传递的参数) | id |
URL Scheme应用场景
通过指定的 URL 字段,可以让应用在被调起后直接打开某些特定页面,比如商品详情页、活动详情页等等。也可以执行某些指定动作,如完成支付等。也可以在应用内通过 H5 来直接调用显示 App 内的某个页面。综上URL Scheme 使用场景大致分以下几种:
- 服务器下发跳转路径,客户端根据服务器下发跳转路径跳转相应的页面
- H5 页面点击锚点,根据锚点具体跳转路径APP端跳转具体的页面
- APP 端收到服务器端下发的 Push 通知栏消息,根据消息的点击跳转路径跳转相关页面
刚说了抛弃 Manifest 中 <intent-filter> 配置的方式来更加灵活的实现,原因有以下两点:
- 在 Manifest 中配置不安全,APP 被逆向后很容易暴露你的配置.
- 如果你 APP 中有多个需要通过 Scheme 来打开的页面那么你就要配置很多个 Activity.
好了介绍这么多,步入正题,首先看下类图和流程图


方法说明:
-
OpenUrlManager:(外部调用类)
方法:
openURL: 将外部传入的URL塞给 OpenUrlContextFactory 工厂(调用
contextWithURL 方法),用以创建对应的 OpenUrlContext. -
OpenUrlContextFactory:(生成 OpenUrlContext 的工厂类)
属性:
从本地配置文件中读取一个 Scheme、界面类(可选)和 OpenUrlContext 之间的对应关系. -
OpenUrlContext: 控制每个 URL 跳转页面逻辑的上下文类的基类.
方法:
contextWithURL:根据传入的 URL,从以上对应关系中找到对应的
属性:
url: 对应跳转的 URL
UIClassName:一个可选的跳转到的 UI 类名.(对于 OpenUrlContext 基类来说,跳转逻辑是直接跳转到 UIClassName 对应的 UI;对于其他上下文子类来说,具体逻辑可以单独控制.)
urlParams: 从 URL 属性中解析出来的 URL参数. -
OpenUrlContext 子类并创建对象,将 URL 和可选的 UI 类名传过去.
方法:
parseParams:从 URL 中解析出来 URL 参数。
verifyParams: 验证一些公共 URL 参数是否合法。
startContext: 调用以上两个方法先做 URL 解析和参数验证,之后调用 jump 方法做跳转逻辑.
jump: 跳转逻辑. -
WebOpenUrlContext: 控制网页url跳转页面逻辑的上下文子类
属性:
webviewConfig: 针对网页界面定制化需求的自定义配置数据.
方法:
verifyParams: 网页地址特有参数的校验逻辑
jump: 做一般的本地界面跳转(这里可以做一些特殊的本地逻辑,根据不同条件做不同的跳转)
verifyParams: 不同本地 URL 的特有参数的校验逻辑
接下来我们看代码:
所有的 URL 都通过这个 Manager 来打开,APP 里大部分页面可能需要登录才能查看,有的则不需要,比如说一个静态的 html 等.是否需要登录这个参数 (needLogin) 我们可以通过URL拿到,如果需要登录并且此刻用户还未登录,那么直接启动登录界面,后续让用户登录以后自行打开想要打开的页面。
public class OpenUrlManager {
private static final String TAG = "OpenUrlManager";
public static final String NATIVE_SCHEME_CONFIG = "openurl.json";
public static final String NATIVE_SCHEME = "internal";
public static void openUrl(String url, Context context) {
OpenUrlContext openUrlContext = OpenUrlContextFactory.contextWithURL(url);
if (openUrlContext != null) {
if (openUrlContext.needLogin && TextUtils.isEmpty(Util.getToke())) {
// TODO: 2017/9/15 需要登录
// LoginManager.getInstance().launchLoginActivity(context);
Log.e(TAG, "openUrl: 需要登录");
} else {
openUrlContext.startContext(context);
}
}
}
}
接下来我们看 OpenUrlContextFactory.contextWithURL 这个方法
public static OpenUrlContext contextWithURL(String url) {
try {
Uri uri = Uri.parse(url);
Log.e("OpenUrlContextFactory ", "contextWithURL:path " + uri.getPath());
switch (uri.getScheme()) {
case "http":
case "https":
return new WebOpenUrlContext(url);
case OpenUrlManager.NATIVE_SCHEME:
if (schemeMap == null) {
AssetManager assetManager = App.getInstance().getAssets();
InputStream is = assetManager.open(OpenUrlManager.NATIVE_SCHEME_CONFIG);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuilder stringBuilder = new StringBuilder();
String str;
while ((str = br.readLine()) != null) {
stringBuilder.append(str);
}
schemeMap = new JSONObject(stringBuilder.toString());
}
if (schemeMap.has(uri.getHost())) {
JSONObject schemeObject = schemeMap.getJSONObject(uri.getHost());
Class openUrlContextClass = Class.forName(schemeObject.getString("openUrlContext"));
Constructor constructor = openUrlContextClass.getConstructor(String.class, Class.class, boolean.class);
return (OpenUrlContext) constructor.newInstance(url, Class.forName(schemeObject.getString("uiClass")), schemeObject.getBoolean("needLogin"));
}
return null;
default:
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
在这里通过 URL 获取 Scheme 判断,如果是 http 或 https 那么直接打开H5 页面,如果是你定义的要跳转到 Native 页面的 Scheme,那么获取 URL 中的 Host,读取本地配置文件中的 Json 串,根据 Json 串中的 key 匹配Host,然后获取到 Native 页面的类名通过反射传给 OpenUrlContext 的子类。(本地存储放在了 assets 文件夹下,当然你也可以使用别的存储方式)。看下JSON 数据:
{
"recharge":{
"needLogin":true,
"uiClass":"com.xiawe_i.openurl.activity.RechargeActivity",
"openUrlContext":"com.xiawe_i.openurl.openurl.OpenUrlContext"
},
"orderDetail":{
"needLogin":true,
"uiClass":"com.xiawe_i.openurl.activity.OrderDetailActivity",
"openUrlContext":"com.xiawe_i.openurl.openurl.OpenUrlContext"
},
"cardList":{
"needLogin":true,
"uiClass":"com.xiawe_i.openurl.activity.CardListActivity",
"openUrlContext":"com.xiawe_i.openurl.openurl.OpenUrlContext"
},
"voucherList":{
"needLogin":true,
"uiClass":"com.xiawe_i.openurl.activity.VoucherListActivity",
"openUrlContext":"com.xiawe_i.openurl.openurl.OpenUrlContext"
}
}
接下来看OpenUrlContext
public OpenUrlContext(String url, Class uiClass, boolean needLogin) {
this.url = url;
this.uiClass = uiClass;
this.needLogin = needLogin;
parseParams();
}
protected void startContext(Context context) {
if (verifyParams()) {
buildIntent(context);
jump(context);
} else {
Log.e(TAG, url);
}
}
protected void parseParams() {
urlParams = new HashMap<>();
Uri uri = Uri.parse(url);
for (String key : uri.getQueryParameterNames()) {
urlParams.put(key, uri.getQueryParameter(key));
if (key.equals(NEEDSLOGIN_PARAMS)) {
if (uri.getQueryParameter(key).equals("1")) {
needLogin = true;
} else if (uri.getQueryParameter(key).equals("0")) {
needLogin = false;
}
}
}
}
protected boolean verifyParams() {
return true;
}
protected void buildIntent(Context context) {
intent = new Intent(context, uiClass);
Bundle bundle = new Bundle();
for (Map.Entry<String, String> entry : urlParams.entrySet()) {
bundle.putString(entry.getKey(), entry.getValue());
}
intent.putExtra(OPENURL_PARAMS_KEY, bundle);
}
protected void jump(Context context) {
context.startActivity(intent);
}
这里就三个方法,解析 URL 里的参数,做校验,通过 Bundle 对象传给要启动的 Native 页面。
看下我的 Demo 模拟启动

完整 Demo 地址 :https://github.com/xiaviv/OpenUrlManger
网友评论