0x00 写在前面
WMRouter 是美团外卖团队开发的一款 Android 路由框架,和阿里开发的 ARouter 比较类似。不过因为项目的原因,我接触 WMRouter 要更多一点。
有关 WMRouter 的 API 的介绍,可以参考 WMRouter 的 GitHub 页面,这里就不多介绍了:https://github.com/meituan/WMRouter/blob/master/docs/user-manual.md
0x01 Handler 是如何处理请求的
标题里所说的 Handler,其实不是 Android 系统中的那个 Handler,而是 WMRouter 中的 UriHandler。它的用途就是来处理请求。这个请求的来源可能是多种多样的,比如应用内发起的跳转请求,或者是其他应用甚至是网页发起的请求。
对于这些各种来源的请求,必然要有地方处理这些请求,将这些请求分发给 Activity。WMRouter 中就是由各种 UriHandler 来处理请求的。
WMRouter 对这一设计的描述如下图所示:
其中,UriRequest
代表一个请求,这个请求并不是直接就能用来启动 Activity 的,而是通过一个个的 UriHandler 来处理的。
接下来,我们就从最基本的启动 Activity 代码说起。
Router.startUri
Router 类提供了一个 startUri()
方法,用来快速启动一个 Activity。
Router.startUri(MainActivity.this, "/second_activity");
这个 startUri()
方法传入一个 Context
,传入一个 String
。这个 String
字符串就是 Activity 对应的路径。startUri()
方法的源码如下:
public class Router {
// ...
public static void startUri(Context context, String uri) {
getRootHandler().startUri(new UriRequest(context, uri));
}
}
可以看到,实际上它是去获取了 RootUriHandler
,然后执行它的 startUri()
方法。而 RootUriHandler
的 startUri()
方法传入的是一个 UriRequest
。
RootUriHandler.startUri
RootUriHandler
会对传入的 UriRequest
进行检查,例如看看传入的 UriRequest
是否为 null
,Context
或者跳转链接是否为空。
public class RootUriHandler extends ChainedHandler {
// ...
public void startUri(@NonNull UriRequest request) {
if (request == null) {
String msg = "UriRequest为空";
Debugger.fatal(msg);
UriRequest req = new UriRequest(mContext, Uri.EMPTY).setErrorMessage(msg);
onError(req, UriResult.CODE_BAD_REQUEST);
} else if (request.getContext() == null) {
String msg = "UriRequest.Context为空";
Debugger.fatal(msg);
UriRequest req = new UriRequest(mContext, request.getUri(), request.getFields())
.setErrorMessage(msg);
onError(req, UriResult.CODE_BAD_REQUEST);
} else if (request.isUriEmpty()) {
String msg = "跳转链接为空";
Debugger.e(msg);
request.setErrorMessage(msg);
onError(request, UriResult.CODE_BAD_REQUEST);
} else {
// 这个请求是正常的
if (Debugger.isEnableLog()) {
Debugger.i("");
Debugger.i("---> receive request: %s", request.toFullString());
}
// 真正处理请求的方法:
handle(request, new RootUriCallback(request));
}
}
}
可以看到上面这段代码我给出的注释里,提到真正处理请求的方法其实就是 handle()
方法。这个方法是定义在 UriHandler
类里的,用于判断当前 Handler 是否应该处理这个请求,以及执行拦截器相关的逻辑。
UriHandler.handle
UriHandler
是所有 Handler 的基类,它主要是定义了 addInterceptor()
方法和 handle()
方法。其中,addInterceptor()
方法是和拦截器有关的,不在这篇文章的讨论范围内,所以暂时略过。我们现在只看和处理请求有关的 handle()
方法:
public abstract class UriHandler {
// ...
public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
// 是否应该由当前 Handler 来处理这个请求?
if (shouldHandle(request)) {
// 应该由当前 Handler 来处理
Debugger.i("%s: handle request %s", this, request);
// 如果拦截器不为空,那么应该先执行所有的拦截器,最后再处理请求
if (mInterceptor != null && !request.isSkipInterceptors()) {
// 这里 intercept 方法实际上是执行了所有的拦截器
mInterceptor.intercept(request, new UriCallback() {
@Override
public void onNext() {
// onNext() 表示所有拦截器处理完以后,判定要继续处理这个请求
// 如果确定要处理这个请求,那么就执行 handleInternal() 方法来处理
handleInternal(request, callback);
}
@Override
public void onComplete(int result) {
// onComplete() 表示有拦截器拦截了这个请求
// 这样一来,这个请求就不会被处理
callback.onComplete(result);
}
});
} else {
// 没有拦截器,直接处理请求
handleInternal(request, callback);
}
} else {
// 不应该由当前 Handler 来处理这个请求,所以通过 callback.onNext() 交给下一个 Handler
Debugger.i("%s: ignore request %s", this, request);
callback.onNext();
}
}
protected abstract boolean shouldHandle(@NonNull UriRequest request);
protected abstract void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback);
}
在上面的代码中可以看到,handle()
方法通过 shouldHandle()
方法来判断是否需要由当前 Handler 来处理这个请求,然后由 handleInternal()
方法进行处理。这两个方法都是抽象方法,等待子类去实现。
实际上,这两个方法是定义在 ChainedHandler
类中的。ChainedHandler
是 UriHandler
的子类、RootUriHandler
的父类。
ChainedHandler
ChainedHandler
类主要是用来添加多个 Handler 的,并且允许这些 Handler 按照优先级进行排序处理。当一个 Handler 处理不了请求时,它就会将这个请求交给下一个 Handler 进行处理。
public class ChainedHandler extends UriHandler {
private final PriorityList<UriHandler> mHandlers = new PriorityList<>();
// 添加一个 Handler
public ChainedHandler addChildHandler(@NonNull UriHandler handler, int priority) {
mHandlers.addItem(handler, priority);
return this;
}
// ...
@Override
protected boolean shouldHandle(@NonNull UriRequest request) {
// 只要 Handler 不为空,那就应该进行处理
// 因为 ChainedHandler 它是非常基础的 Handler,所以它始终应该处理请求
return !mHandlers.isEmpty();
}
@Override
protected void handleInternal(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
// 把请求交给下一个 Handler 进行处理
next(mHandlers.iterator(), request, callback);
}
private void next(@NonNull final Iterator<UriHandler> iterator, @NonNull final UriRequest request,
@NonNull final UriCallback callback) {
// 还有 Handler 吗?
if (iterator.hasNext()) {
// 有 Handler,就把这个请求交给这个 Handler 来处理
UriHandler t = iterator.next();
// 注意,这里没有使用传入的 callback。因为传入的 callback 是整个处理的 callback,
// 实际上也就是 RootUriHandler.RootUriCallback。而这个 Callback 执行 onNext()
// 和 onComplete() 都是终止处理请求,它是给请求处理兜底的。所以这里是创建新的
// UriCallback
t.handle(request, new UriCallback() {
@Override
public void onNext() {
// 如果当前取出的 Handler 处理不了这个请求,那么就递归执行 next() 方法,
// 继续取出下一个 Handler 来处理请求
next(iterator, request, callback);
}
@Override
public void onComplete(int resultCode) {
// 当前 Handler 处理完成了,是成功还是失败不管,都交给
// RootUriHandler.RootUriCallback 去判断
callback.onComplete(resultCode);
}
});
} else {
// 已经没有 Handler 能处理请求了,这个请求就是处理不了了
// 交给 RootUriHandler.RootUriCallback 去判断
callback.onNext();
}
}
}
那么,知道了 ChainedHandler
类是怎么将一个请求逐一递交给其他的 Handler 以后,我们就应该继续去了解我们的请求到底都交给了谁去处理,它们都是怎么处理的。所以检查一下 addChildHandler()
方法在哪里被调用了:
从图中可以看出来,addChildHandler()
主要是 DefaultRootUriHandler
类用到。
DefaultRootUriHandler
DefaultRootUriHandler
类看起来比较熟悉,实际上它就是应用在初始化 WMRouter 时设置的那个 Handler:
public class App extends Application {
@Override
public void onCreate() {
// ...
DefaultRootUriHandler handler = new DefaultRootUriHandler(this);
Router.init(handler);
}
}
它的主要用处其实就是继承 RootUriHandler
,然后设置一下几种 Handler 以及它们的优先级。
public class DefaultRootUriHandler extends RootUriHandler {
private final PageAnnotationHandler mPageAnnotationHandler;
private final UriAnnotationHandler mUriAnnotationHandler;
private final RegexAnnotationHandler mRegexAnnotationHandler;
public DefaultRootUriHandler(Context context) {
this(context, null, null);
}
public DefaultRootUriHandler(Context context,
@Nullable String defaultScheme, @Nullable String defaultHost) {
super(context);
mPageAnnotationHandler = createPageAnnotationHandler();
mUriAnnotationHandler = createUriAnnotationHandler(defaultScheme, defaultHost);
mRegexAnnotationHandler = createRegexAnnotationHandler();
// 按优先级排序,数字越大越先执行
// 处理RouterPage注解定义的内部页面跳转,如果注解没定义,直接结束分发
addChildHandler(mPageAnnotationHandler, 300);
// 处理RouterUri注解定义的URI跳转,如果注解没定义,继续分发到后面的Handler
addChildHandler(mUriAnnotationHandler, 200);
// 处理RouterRegex注解定义的正则匹配
addChildHandler(mRegexAnnotationHandler, 100);
// 添加其他用户自定义Handler...
// 都没有处理,则尝试使用默认的StartUriHandler直接启动Uri
addChildHandler(new StartUriHandler(), -100);
// 全局OnCompleteListener,用于输出跳转失败提示信息
setGlobalOnCompleteListener(DefaultOnCompleteListener.INSTANCE);
}
// ...
@NonNull
protected PageAnnotationHandler createPageAnnotationHandler() {
return new PageAnnotationHandler();
}
@NonNull
protected UriAnnotationHandler createUriAnnotationHandler(@Nullable String defaultScheme,
@Nullable String defaultHost) {
return new UriAnnotationHandler(defaultScheme, defaultHost);
}
@NonNull
protected RegexAnnotationHandler createRegexAnnotationHandler() {
return new RegexAnnotationHandler();
}
}
因为我们的 Activity 基本上都是通过 @RouterUri
进行注释的,所以应该去看 UriAnnotationHandler
类是怎么处理请求的。
UriAnnotationHandler
UriAnnotationHandler
也并不是直接处理请求的地方,它的作用主要有两个:
- 加载所有 Activity 的路径和 Class 名称,并注册到 PathHandler;
- 根据 Scheme 和 Host 来查找相应的 PathHandler,然后交给 PathHandler 来处理。
public class UriAnnotationHandler extends UriHandler {
// key 是由 scheme + host 生成的字符串,value 是 PathHandler
// 也就是说,只要 scheme 和 host 相同,那么就共用一个 PathHandler
// 所以同一个应用基本都使用同一个 PathHandler
private final Map<String, PathHandler> mMap = new HashMap<>();
public void register(String scheme, String host, String path,
Object handler, boolean exported, UriInterceptor... interceptors) {
// 如果没有设置 Scheme 和 Host,就用默认的值
if (TextUtils.isEmpty(scheme)) {
scheme = mDefaultScheme;
}
if (TextUtils.isEmpty(host)) {
host = mDefaultHost;
}
// 根据 Scheme 和 Host 来拼接一下
String schemeHost = RouterUtils.schemeHost(scheme, host);
// 检查一下有没有已经注册了的 PathHandler,如果没有,就创建一个新的
PathHandler pathHandler = mMap.get(schemeHost);
if (pathHandler == null) {
pathHandler = createPathHandler();
mMap.put(schemeHost, pathHandler);
}
// 让 PathHandler 注册一下
pathHandler.register(path, handler, exported, interceptors);
}
// ...
private PathHandler getChild(@NonNull UriRequest request) {
return mMap.get(request.schemeHost());
}
@Override
protected boolean shouldHandle(@NonNull UriRequest request) {
// 如果 PathHandler 不为空,那么就应该把请求交给对应的 PathHandler 处理
return getChild(request) != null;
}
@Override
protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {
PathHandler pathHandler = getChild(request);
if (pathHandler != null) {
// 把请求交给对应的 PathHandler 处理
pathHandler.handle(request, callback);
} else {
// 没找到的继续分发
callback.onNext();
}
}
}
总结一下,UriAnnotationHandler
的主要用途就是把不同 Scheme + Host 的 Activity 注册到不同的 PathHandler
类实例,然后让 PathHandler
再进一步处理。
而注册 Path 和 Activity 类名的地方是由 WMRouter 自动生成的:
public class UriAnnotationInit_3088f6f0eae502e685b028719c160ee8 implements IUriAnnotationInit {
public void init(UriAnnotationHandler handler) {
handler.register("", "", "/second", "me.maronyea.routertest.activity.SecondActivity", false);
}
}
通过这样的代码,就把一个 Activity 类和 Scheme、Host、Path 给一一对应起来了。在执行 register()
方法时,实际上这些数据都交给了 PathHandler
类进行注册。
PathHandler
PathHandler
实际上也不是真正处理请求的地方,它的作用是在注册时根据 Path 来查找真正处理请求的 Handler 并将请求交给它们处理。
public class PathHandler extends UriHandler {
private final CaseInsensitiveNonNullMap<UriHandler> mMap = new CaseInsensitiveNonNullMap<>();
// 注册 Path 和 Activity 的对应关系
public void register(String path, Object target, boolean exported,
UriInterceptor... interceptors) {
if (!TextUtils.isEmpty(path)) {
path = RouterUtils.appendSlash(path);
// 根据 Path 来查找能够处理请求的 Handler
UriHandler parse = UriTargetTools.parse(target, exported, interceptors);
UriHandler prev = mMap.put(path, parse);
if (prev != null) {
Debugger.fatal("[%s] 重复注册path='%s'的UriHandler: %s, %s", this, path, prev, parse);
}
}
}
// ...
// 根据请求的 Path 来找到注册时查找到的 Handler
private UriHandler getChild(@NonNull UriRequest request) {
String path = request.getUri().getPath();
if (TextUtils.isEmpty(path)) {
return null;
}
path = RouterUtils.appendSlash(path);
if (TextUtils.isEmpty(mPathPrefix)) {
return mMap.get(path);
}
if (path.startsWith(mPathPrefix)) {
return mMap.get(path.substring(mPathPrefix.length()));
}
return null;
}
@Override
protected boolean shouldHandle(@NonNull UriRequest request) {
return mDefaultHandler != null || getChild(request) != null;
}
@Override
protected void handleInternal(@NonNull final UriRequest request,
@NonNull final UriCallback callback) {
// 根据请求来查找真正处理请求的 Handler,并交给它进行处理
UriHandler h = getChild(request);
if (h != null) {
h.handle(request, new UriCallback() {
@Override
public void onNext() {
handleByDefault(request, callback);
}
@Override
public void onComplete(int resultCode) {
callback.onComplete(resultCode);
}
});
} else {
handleByDefault(request, callback);
}
}
private void handleByDefault(@NonNull UriRequest request, @NonNull UriCallback callback) {
UriHandler defaultHandler = mDefaultHandler;
if (defaultHandler != null) {
defaultHandler.handle(request, callback);
} else {
callback.onNext();
}
}
}
注意到里面有一句关键的代码:
UriHandler parse = UriTargetTools.parse(target, exported, interceptors);
通过 UriTargetTools.parse()
方法来判断要启动这一 Activity 应该使用什么 Handler 来处理。
UriTargetTools.parse
UriTargetTools
类提供了一个 public
的方法 parse()
,用来根据注册信息匹配相应的 Handler。这个方法不执行这个 Handler,只是返回 UriHandler
的实例。
public class UriTargetTools {
public static UriHandler parse(Object target, boolean exported,
UriInterceptor... interceptors) {
UriHandler handler = toHandler(target);
if (handler != null) {
if (!exported) {
handler.addInterceptor(NotExportedInterceptor.INSTANCE);
}
handler.addInterceptors(interceptors);
}
return handler;
}
private static UriHandler toHandler(Object target) {
if (target instanceof UriHandler) {
return (UriHandler) target;
} else if (target instanceof String) {
return new ActivityClassNameHandler((String) target);
} else if (target instanceof Class && isValidActivityClass((Class) target)) {
//noinspection unchecked
return new ActivityHandler((Class<? extends Activity>) target);
} else {
return null;
}
}
private static boolean isValidActivityClass(Class clazz) {
return clazz != null && Activity.class.isAssignableFrom(clazz)
&& !Modifier.isAbstract(clazz.getModifiers());
}
}
根据上面的代码总结一下:
- 如果在注册时传入的 Activity 信息是以类名字符串的形式传入的,那么就交给
ActivityClassNameHandler
来处理; - 如果在在注册时传入的 Activity 信息是以
Class
类型实例的形式传入的,那么先验证这个类是不是 Activity,如果是,那么就交给ActivityHandler
来处理。
ActivityClassNameHandler 与 ActivityHandler
ActivityClassNameHandler
是 AbsActivityHandler
的子类,它其实只是提供了创建 Intent 的逻辑,真正实现处理请求的逻辑是在 AbsActivityHandler
类中实现的。
public class ActivityClassNameHandler extends AbsActivityHandler {
// ...
@NonNull
@Override
protected Intent createIntent(@NonNull UriRequest request) {
return new Intent().setClassName(request.getContext(), mClassName);
}
}
ActivityHandler
同样也是 AbsActivityHandler
的子类,它同样是提供了创建 Intent 的逻辑,真正实现处理请求的逻辑也是在 AbsActivityHandler
中实现的。
public class ActivityHandler extends AbsActivityHandler {
// ...
@NonNull
@Override
protected Intent createIntent(@NonNull UriRequest request) {
return new Intent(request.getContext(), mClazz);
}
}
AbsActivityHandler
AbsActivityHandler
类中实现了 shouldHandle()
和 handleInternal()
方法。其中 shouldHandle()
始终返回 true
,而 handleInternal()
方法则调用 createIntent()
抽象方法来创建 Intent,并通过 RouterComponents.startActivity()
方法来启动 Activity。
public abstract class AbsActivityHandler extends UriHandler {
@Override
protected boolean shouldHandle(@NonNull UriRequest request) {
return true;
}
@SuppressWarnings("ConstantConditions")
@Override
protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {
// 创建Intent
Intent intent = createIntent(request);
if (intent == null || intent.getComponent() == null) {
Debugger.fatal("AbsActivityHandler.createIntent()应返回的带有ClassName的显式跳转Intent");
callback.onComplete(UriResult.CODE_ERROR);
return;
}
intent.setData(request.getUri());
UriSourceTools.setIntentSource(intent, request);
// 启动Activity
request.putFieldIfAbsent(ActivityLauncher.FIELD_LIMIT_PACKAGE, limitPackage());
int resultCode = RouterComponents.startActivity(request, intent);
// 回调方法
onActivityStartComplete(request, resultCode);
// 完成
callback.onComplete(resultCode);
}
@NonNull
protected abstract Intent createIntent(@NonNull UriRequest request);
/**
* 回调方法,子类可在此实现跳转动画等效果
*
* @param resultCode 跳转结果
*/
protected void onActivityStartComplete(@NonNull UriRequest request, int resultCode) { }
}
那么截至这里,就已经分析完了 Handler 的处理部分。总结一下,通过这些 UriHandler
一层层地传递请求,已经成功地还原出了用于启动 Activity 的 Intent。接下来就是要实现启动 Activity 的逻辑了。
0x02 如何启动 Activity
启动 Activity 看起来也非常的简单,既然前面已经创建了 Intent,那直接来启动不就行了吗?但实际上没有那么简单。
RouterComponents.startActivity
在上一节提到,WMRouter 通过 RouterComponents.startActivity()
方法来启动 Activity,这个方法需要传入一个 UriRequest
和 Intent。
public class RouterComponents {
// ...
@NonNull
private static ActivityLauncher sActivityLauncher = DefaultActivityLauncher.INSTANCE;
public static void setActivityLauncher(ActivityLauncher launcher) {
sActivityLauncher = launcher == null ? DefaultActivityLauncher.INSTANCE : launcher;
}
// ...
public static int startActivity(@NonNull UriRequest request, @NonNull Intent intent) {
return sActivityLauncher.startActivity(request, intent);
}
}
注意到 RouterComponents
类并没有直接提供启动 Activity 的逻辑,而是调用了 ActivityLauncher
的 startActivity()
方法去启动的。这样做同样是为了可以扩展功能,也就是说,如果将来想要自定义启动 Activity 逻辑的话,只要实现 ActivityLauncher
接口,然后通过 RouterComponents.setActivityLauncher()
方法就可以替换掉默认的启动逻辑。
不过这里暂时不展开,还是先看看默认的启动逻辑是什么样的吧。
DefaultActivityLauncher
DefaultActivityLauncher
的代码比较长,但是实际上可以看到,还是通过 ActivityCompat.startActivity()
或者 ActivityCompat.startActivityForResult()
方法去启动 Activity 的,只不过是加上了一些判断,以及从 UriRequest
中提取出来了 Bundle 和 Flag。
public class DefaultActivityLauncher implements ActivityLauncher {
public static final DefaultActivityLauncher INSTANCE = new DefaultActivityLauncher();
private boolean mCheckIntentFirst = false;
/**
* 跳转前是否先检查Intent
*/
public void setCheckIntentFirst(boolean checkIntentFirst) {
mCheckIntentFirst = checkIntentFirst;
}
@SuppressWarnings("ConstantConditions")
public int startActivity(@NonNull UriRequest request, @NonNull Intent intent) {
if (request == null || intent == null) {
return UriResult.CODE_ERROR;
}
Context context = request.getContext();
// Extra
Bundle extra = request.getField(Bundle.class, FIELD_INTENT_EXTRA);
if (extra != null) {
intent.putExtras(extra);
}
// Flags
Integer flags = request.getField(Integer.class, FIELD_START_ACTIVITY_FLAGS);
if (flags != null) {
intent.setFlags(flags);
}
// request code
Integer requestCode = request.getField(Integer.class, FIELD_REQUEST_CODE);
// 是否限制Intent的packageName,限制后只会启动当前App内的页面,不启动其他App的页面,bool型
boolean limitPackage = request.getBooleanField(FIELD_LIMIT_PACKAGE, false);
// 设置package,先尝试启动App内的页面
intent.setPackage(context.getPackageName());
int r = startIntent(request, intent, context, requestCode, true);
if (limitPackage || r == UriResult.CODE_SUCCESS) {
return r;
}
// App内启动失败,再尝试启动App外页面
intent.setPackage(null);
return startIntent(request, intent, context, requestCode, false);
}
/**
* 启动Intent
*
* @param internal 是否启动App内页面
*/
protected int startIntent(@NonNull UriRequest request, @NonNull Intent intent,
Context context, Integer requestCode, boolean internal) {
if (!checkIntent(context, intent)) {
return UriResult.CODE_NOT_FOUND;
}
if (startActivityByAction(request, intent, internal) == UriResult.CODE_SUCCESS) {
return UriResult.CODE_SUCCESS;
}
return startActivityByDefault(request, context, intent, requestCode, internal);
}
/**
* 检查Intent是否可跳转
*/
protected boolean checkIntent(Context context, Intent intent) {
if (mCheckIntentFirst) {
try {
PackageManager pm = context.getPackageManager();
List list = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
return list != null && list.size() > 0;
} catch (Exception e) {
// package manager has died
Debugger.fatal(e);
return false;
}
} else {
return true;
}
}
/**
* 使用指定的 {@link StartActivityAction} 启动Intent
*
* @param internal 是否启动App内页面
*/
protected int startActivityByAction(@NonNull UriRequest request,
@NonNull Intent intent, boolean internal) {
// ...
}
/**
* 使用默认方式启动Intent
*
* @param internal 是否启动App内页面
*/
protected int startActivityByDefault(UriRequest request, @NonNull Context context,
@NonNull Intent intent, Integer requestCode, boolean internal) {
try {
Bundle options = request.getField(Bundle.class, FIELD_START_ACTIVITY_OPTIONS);
if (requestCode != null && context instanceof Activity) {
ActivityCompat.startActivityForResult((Activity) context, intent, requestCode,
options);
} else {
ActivityCompat.startActivity(context, intent, options);
}
doAnimation(request);
if (internal) {
request.putField(FIELD_STARTED_ACTIVITY, INTERNAL_ACTIVITY);
Debugger.i(" internal activity started"
+ ", request = %s", request);
} else {
request.putField(FIELD_STARTED_ACTIVITY, EXTERNAL_ACTIVITY);
Debugger.i(" external activity started"
+ ", request = %s", request);
}
return UriResult.CODE_SUCCESS;
} catch (ActivityNotFoundException e) {
Debugger.w(e);
return UriResult.CODE_NOT_FOUND;
} catch (SecurityException e) {
Debugger.w(e);
return UriResult.CODE_FORBIDDEN;
}
}
/**
* 执行动画
*/
protected void doAnimation(UriRequest request) {
// ...
}
}
0xFF 写在最后
本来还想写一下如何自定义 Activity 启动逻辑的,但是这篇文章实在是有点太长了,所以只好放到下一篇文章里了。
嗯,就这样吧。