Android组件化URL之肆意跳转

作者: xiawe_i | 来源:发表于2017-09-15 00:21 被阅读150次
本文为大家介绍利用 URL Scheme 在应用内任意跳转的实现,通过本地配置文件配置打开你想要打开的任意页面. 抛弃传统的 Manifest 中 <intent-filter> 配置.

首先介绍下什么是 URL Scheme ?

URL Scheme 可以通过类似打开网页的方式去通过路由打开一个 Activity,其协议格式和我们打开网页输入的网址类似。

URL Scheme 的格式

一个完整的完整的 URL Scheme 协议格式由 Scheme、Host、Port、Path 和 Query 组成,其结构如下所示:
<scheme>://<host>:<port>/<path>?<query>

来个栗子:

internal://card:8080/cardDetail?id=001

结构 对应栗子
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.

好了介绍这么多,步入正题,首先看下类图和流程图

类图.png 流程图.png

方法说明:

  • 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 模拟启动

效果.gif

完整 Demo 地址 :https://github.com/xiaviv/OpenUrlManger

相关文章

网友评论

    本文标题:Android组件化URL之肆意跳转

    本文链接:https://www.haomeiwen.com/subject/amhjsxtx.html