IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Activity界面路由的一种简单实现 -> 正文阅读

[移动开发]Activity界面路由的一种简单实现

1. 引言

平时Android开发中,启动Activity是非常常见的操作,而打开一个新Activity可以直接使用Intent,也可以每个Activity提供一个静态的启动方法。但是有些时候使用这些方法并不那么方便,比如:一个应用内的网页需要打开一个原生Activity页面时。这种情况下,网页的调用代码可能是app.openPage("/testPage")这样,或者是用app.openPage("local://myapp.com/loginPage")这样的方式,我们需要用一种方式把路径和页面关联起来。Android可以允许我们在Manifest文件中配置<data>标签来达到类似效果,也可以使用ARouter框架来实现这样的功能。本文就用200行左右的代码实现一个类似ARouter的简易界面路由。

2. 示例

2.1 初始化

这个操作建议放在Application的onCreate方法中,在第一次调用Router来打开页面之前。

public class AppContext extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Router.init(this);
    }
}

2.2 启动无参数Activity

这是最简单的情况,只需要提供一个路径,适合“关于我们”、“隐私协议”这种简单无参数页面。

Activity配置:

@Router.Path("/testPage")
public class TestActivity extends Activity {
    //......
}

启动代码:

Router.from(mActivity).toPath("/testPage").start();
//或
Router.from(mActivity).to("local://my.app/testPage").start();

2.3 启动带参数Activity

这是比较常见的情况,需要在注解中声明需要的参数名称,这些参数都是必要参数,如果启动的时候没有提供对应参数,则发出异常。

Activity配置:

@Router.Path(value = "/testPage",args = {"id", "type"})
public class TestActivity extends Activity {
        @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //加载布局...

        String id = getIntent().getStringExtra("id"); //获取参数
        int type = getIntent().getIntExtra("type", 0);//获取参数
    }
}

启动代码:

Router.from(mActivity).toPath("/testPage").with("id", "t_123").with("type", 1).start();

2.4 启动带有静态启动方法的Activity

有一些Activity需要通过它提供的静态方法启动,就可以使用Path中的method属性和Entry注解来声明入口,可以提供参数。在提供了method属性时,需要用Entryargs来声明参数。

Activity配置:

@Router.Path(value = "/testPage", method = "open")
public class TestActivity extends Activity {

    @Router.Entry(args = {"id", "type"})
    public static void open(Activity activity, Bundle args) {
        Intent intent = new Intent(activity, NestWebActivity.class);
        intent.putExtras(args);
        activity.startActivity(intent);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //加载布局...

        String id = getIntent().getStringExtra("id"); //获取参数
        int type = getIntent().getIntExtra("type", 0);//获取参数
    }
}

启动代码:

Router.from(mActivity).toPath("/testPage").with("id", "t_123").with("type", 1).start();

3. API介绍

3.1 Path注解

这个注解只能用于Activity的子类,表示这个Activity需要页面路由的功能。这个类有三个属性:

  • value:表示这个Activity的相对路径。
  • args:表示这个Activity需要的参数,都是必要参数,如果打开页面时缺少指定参数,就会发出异常。
  • method:如果这个Activity需要静态方法做为入口,就将这个属性指定为方法名,并给对应方法添加Entry注解。(注意:这个属性值不为空时,忽略这个注解中的args属性内容)

3.1 Entry注解

这个注解只能用于Activity的静态方法,表示这个方法作为打开Activity的入口。仅包含一个属性:

  • args:表示这个方法需要的参数。

3.2 Router.init方法

  • 方法签名:public static void init(Context context)
  • 方法说明:这个方法用于初始化页面路由表,必须在第一次用Router打开页面之前完成初始化。建议在Application的onCreate方法中完成初始化。

3.3 Rouater.from方法

  • 方法签名:public static Router from(Activity activity)
  • 方法说明:这个方法用于创建Router实例,传入的参数通常为当前Activity。例如,要从AActivity打开BActivity,那么传入参数为AActivity的实例。

3.4 Rouater.to和Rouater.toPath方法

  • 方法签名:
  1. public RouterBuilder to(String urlString)
  2. public RouterBuilder toPath(String path)
  • 方法说明:这个方法用于指定目标的路径,to需要执行绝对路径,而toPath需要指定相对路径。返回的RouterBuilder用于接收打开页面需要的参数。

3.4 RouterBuilder.with方法

  • 方法签名:
  1. public RouterBuilder with(String key, String value)
  2. public RouterBuilder with(String key, int value)
  • 方法说明:这个方法用于添加参数,对应Bundle的各个put方法。目前只有常用的Stringint两个类型。如有需要可自行在RouterBuilder中添加对应的方法。

3.4 RouterBuilder.start方法

  • 方法签名:public void start()
  • 方法说明:这个方法用于打开页面。如果存在路径错误、参数错误等异常情况,会发出对应运行时异常。

4. 实现

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;

import androidx.annotation.Keep;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

@Keep
public class Router {

    public static final String SCHEME = "local";
    public static final String HOST = "my.app";
    public static final String URL_PREFIX = SCHEME + "://" + HOST;

    private static final Map<String, ActivityStarter> activityPathMap = new ConcurrentHashMap<>();

    public static void init(Context context) {
        try {
            PackageManager packageManager = context.getPackageManager();
            PackageInfo packageInfo = packageManager.getPackageInfo(
                    context.getPackageName(), PackageManager.GET_ACTIVITIES);

            for (ActivityInfo activityInfo : packageInfo.activities) {
                Class<?> aClass = Class.forName(activityInfo.name);
                Path annotation = aClass.getAnnotation(Path.class);
                if (annotation != null && !TextUtils.isEmpty(annotation.value())) {
                    activityPathMap.put(annotation.value(), (Activity activity, Bundle bundle) -> {
                        if (TextUtils.isEmpty(annotation.method())) {
                            for (String arg : annotation.args()) {
                                if (!bundle.containsKey(arg)) {
                                    throw new IllegalArgumentException(String.format("Bundle does not contains argument[%s]", arg));
                                }
                            }
                            Intent intent = new Intent(activity, aClass);
                            intent.putExtras(bundle);
                            activity.startActivity(intent);
                        } else {
                            try {
                                Method method = aClass.getMethod(annotation.method(), Activity.class, Bundle.class);
                                Entry entry = method.getAnnotation(Entry.class);
                                if (entry != null) {
                                    for (String arg : entry.args()) {
                                        if (!bundle.containsKey(arg)) {
                                            throw new IllegalArgumentException(String.format("Bundle does not contains argument[%s]", arg));
                                        }
                                    }
                                    method.invoke(null, activity, bundle);
                                } else {
                                    throw new IllegalStateException("can not find a method with [Entry] annotation!");
                                }
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                        }
                    });
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Router from(Activity activity) {
        return new Router(activity);
    }

    private final Activity activity;

    private Router(Activity activity) {
        this.activity = activity;
    }

    public RouterBuilder to(String urlString) {
        if (TextUtils.isEmpty(urlString)) {
            return new ErrorRouter(new IllegalArgumentException("argument [urlString] must not be null"));
        } else {
            return to(Uri.parse(urlString));
        }
    }

    public RouterBuilder toPath(String path) {
        return to(Uri.parse(URL_PREFIX + path));
    }

    public RouterBuilder to(Uri uri) {
        try {
            if (SCHEME.equals(uri.getScheme())) {
                if (HOST.equals(uri.getHost())) {
                    String path = uri.getPath();//note: 二级路径暂不考虑
                    ActivityStarter starter = activityPathMap.get(path);
                    if (starter == null) {
                        throw new IllegalStateException(String.format("path [%s] is not support", path));
                    } else {
                        NormalRouter router = new NormalRouter(activity, starter);
                        for (String key : uri.getQueryParameterNames()) {
                            if (!TextUtils.isEmpty(key)) {
                                router.with(key, uri.getQueryParameter(key));
                            }
                        }
                        return router;
                    }
                } else {
                    throw new IllegalArgumentException(String.format("invalid host : %s", uri.getHost()));
                }
            } else {
                throw new IllegalArgumentException(String.format("invalid scheme : %s", uri.getScheme()));
            }
        } catch (RuntimeException e) {
            return new ErrorRouter(e);
        }
    }

    public static abstract class RouterBuilder {
        public abstract RouterBuilder with(String key, String value);

        public abstract RouterBuilder with(String key, int value);

        public abstract void start();
    }


    private static class ErrorRouter extends RouterBuilder {
        private final RuntimeException exception;

        private ErrorRouter(RuntimeException exception) {
            this.exception = exception;
        }

        @Override
        public RouterBuilder with(String key, String value) {
            return this;
        }

        @Override
        public RouterBuilder with(String key, int value) {
            return this;
        }

        @Override
        public void start() {
            throw exception;
        }
    }

    private static class NormalRouter extends RouterBuilder {
        final Activity activity;
        final Bundle bundle = new Bundle();
        final ActivityStarter starter;

        private NormalRouter(Activity activity, ActivityStarter starter) {
            this.activity = Objects.requireNonNull(activity);
            this.starter = Objects.requireNonNull(starter);
        }

        @Override
        public RouterBuilder with(String key, String value) {
            bundle.putString(key, value);
            return this;
        }

        @Override
        public RouterBuilder with(String key, int value) {
            bundle.putInt(key, value);
            return this;
        }

        @Override
        public void start() {
            starter.start(activity, bundle);
        }
    }

    @FunctionalInterface
    private interface ActivityStarter {
        void start(Activity activity, Bundle bundle);
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Path {
        String value();

        String method() default "";

        String[] args() default {};
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Entry {
        String[] args() default {};
    }
}

5. 注意

  1. 这个工具的一些功能与ARouter类似,实际项目中建议使用ARouter。如果有特殊需求,例如,页面参数的检查或定制具体打开行为,可以考虑基于这个工具进行修改。
  2. 使用了Pathmethod属性时注意添加对应的混淆设置,避免因混淆而导致找不到对应方法。
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-05-05 11:29:58  更:2022-05-05 11:31:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 23:41:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码