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
特点时,需求用Entry
的args
来声明参数。
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办法
- 办法签名:
public RouterBuilder to(String urlString)
public RouterBuilder toPath(String path)
- 办法阐明:这个办法用于指定方针的途径,
to
需求执行绝对途径,而toPath
需求指定相对途径。回来的RouterBuilder
用于接收翻开页面需求的参数。
3.4 RouterBuilder.with办法
- 办法签名:
public RouterBuilder with(String key, String value)
public RouterBuilder with(String key, int value)
- 办法阐明:这个办法用于增加参数,对应
Bundle
的各个put
办法。现在只要常用的String
和int
两个类型。如有需求可自行在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. 留意
- 这个东西的一些功能与
ARouter
相似,实际项目中主张运用ARouter
。假如有特殊需求,例如,页面参数的查看或定制详细翻开行为,能够考虑基于这个东西进行修正。 - 运用了
Path
的method
特点时留意增加对应的混淆设置,避免因混淆而导致找不到对应办法。