安卓隐式意图(Intent)
本文实现安卓页面间(Activity)的跳转是根据安卓隐式意图来实现的。
在任玉刚的《Android开发艺术探索》一书中,对Android中的Intent跳转以及IntentFilter的匹配规则作了非常详细的说明,这里简单介绍一下。
首先了解下IntentFilter
的匹配规则:
Action
Action至少需要设置一个。目的IntentFilter中至少存在一个和自身的Action一致
Category
Category可以不设置。如果设置了,则目的IntentFilter中必须包含所有已设置的Category。需要说明的是:在Activity跳转过程中,系统会给Intent默认添加名称为:"android.intent.category.DEFAULT"的Category。所以如果一个Activity允许隐式访问的话,则必须添加"android.intent.category.DEFAULT"的Category
Data
Data由两部分构成:mimeType以及URI。mimeType指媒体类型,比如:image/png、audio/mpeg4-generic 、video/*等,可以表示图片、音频、视频等不同的媒体格式。而URI则可以对比一个url地址解释。需要至少有一个完全匹配(支持通配符)。需要说明的几点是:
mimeType
有默认值:content
和file
IntentFilter
的setData
和setType
方法会清除彼此内容,同时设置时需要调用setDataAndType
路由的设计
自定义Action
定义全局的Action
作为跳转的IntentFilter
的Action
比如:
<action android:name="com.github.lotty.action.NAVIGATION" />
添加默认的Category
通过上文对Category的分析,如果一个Activity允许被隐式意图访问,则必须添加默认值:
"android.intent.category.DEFAULT"
,所以所有的Activity的IntentFilter
都必须添加该Category
设计Data
在这里,本文提供一种比较简洁易懂的Data格式,来提供访问策略:
<data
android:host="nav.github.com"
android:path="/lotty/component"
android:scheme="https" />
通过类似Web页面地址的方式来设计Data,schma
选择https
或者http
,host
选择应用的官网HOST,path
设计为对应的页面地址,上图中/lotty/component
就代表组件的Activity
工具类的设计
Builder模式
通过Builder
模式来接收Activity跳转所需要的参数,例如:Bundle、请求码、附加参数、目的页面路径等,本例中的设计为:
public static class Builder {
private Context from;
private Class<? extends Activity> targetActivity;
private String action;
private Uri uri;
private Map<String, String> data = new HashMap<>();
private Bundle bundle;
private Bundle options;
private int reqCode;
public Builder(Context context) {
if (context instanceof Activity) {
this.from = context;
} else {
this.from = context.getApplicationContext();
}
}
/**
* 设置目标页面
*
* @param toActivityClass 目标页面
*/
public Builder to(Class<? extends Activity> toActivityClass) {
this.targetActivity = toActivityClass;
return this;
}
/**
* 添加 extra 参数
*
* @param key 参数的 key
* @param value 参数的 value
*/
public Builder with(String key, String value) {
data.put(key, value);
return this;
}
/**
* 添加 extra bundle 参数
*
* @param bundle bundle 数据
*/
public Builder with(Bundle bundle) {
this.bundle = bundle;
return this;
}
/**
* 添加 extra 数据,Map 中的每一个 entry 作为键值对添加到 intent extra 中
*
* @param data Map 数据
*/
public Builder with(Map<String, String> data) {
if (data != null && !data.isEmpty()) {
this.data.putAll(data);
}
return this;
}
/**
* 设置 startActivity 的 request code 不能设置为 0
*
* @param requestcode startActivity 的 request code
*/
public Builder reqcode(int requestcode) {
this.reqCode = requestcode;
return this;
}
/**
* 设置 intent action
*
* @param intentAction intent action
*/
public Builder action(String intentAction) {
this.action = intentAction;
return this;
}
/**
* 设置目标页 uri
*
* @param uri 目标页 uri
*/
public Builder uri(Uri uri) {
this.uri = uri;
return this;
}
/**
* 设置 Activity 启动选项
*
* @param options Activity 启动选项
*/
public Builder options(Bundle options) {
this.options = options;
return this;
}
/**
* 执行跳转
*/
public void go() {
Router router = new Router(this);
router.go();
}
}
可以看出,该类提供了接收startActivity
所有的输入参数,而且支持多种方式,为跳转提供了参数的统一入口。需要指出的是:通过Application
启动页面跟通过Activity
启动页面是有区别的,所以在接受参数时,作了类型判断。
跳转实现
跳转的实现主要是调用Activity的startActivityForResult
和Application的startActivity
方法,本例中的实现为:
/**
* 路由工具类
*/
public class Router {
private static final String ACTION_NAVIGATION = "com.github.lotty.action.NAVIGATION";
private static final int NO_REQ_CODE = -1;
private Context mFrom;
private Class<? extends Activity> mToActivity;
private String mAction;
private Uri mUri;
private Map<String, String> mData = new HashMap<>();
private Bundle mBundle;
private Bundle mOptions;
private int mReqCode;
private Router(Builder builder) {
mToActivity = builder.targetActivity;
mFrom = builder.from;
mAction = builder.action != null ? builder.action : ACTION_NAVIGATION;
mUri = builder.uri;
mData = builder.data;
mBundle = builder.bundle;
mOptions = builder.options;
mReqCode = builder.reqCode == 0 ? NO_REQ_CODE : builder.reqCode;
}
/**
* 创建 Router 构建器
*/
public static Builder from(Context context) {
return new Builder(context);
}
/**
* 创建 Router 构建器
*
* @param activity 当前页面
*/
public static Builder from(Activity activity) {
return new Builder(activity);
}
private void go() {
Intent intent = new Intent();
if (mToActivity != null) {
intent.setClass(mFrom, mToActivity);
}
intent.setAction(mAction);
if (mUri != null) {
intent.setData(mUri);
}
if (mBundle != null) {
intent.putExtras(mBundle);
}
if (mData != null && !mData.isEmpty()) {
for (Map.Entry<String, String> entry : mData.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (!TextUtils.isEmpty(key)) {
intent.putExtra(key, value);
}
}
}
if (mFrom instanceof Activity) {
startByActivity(intent);
} else {
startByContext(intent);
}
}
private void startByActivity(Intent intent) {
Activity from = (Activity) mFrom;
from.startActivityForResult(intent, mReqCode, mOptions);
}
private void startByContext(Intent intent) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mFrom.startActivity(intent, mOptions);
}
}
以上实现中需要注意的是:
- Application 启动页面时,需要添加
Intent.FLAG_ACTIVITY_NEW_TASK
的Flag - 处理
requestCode
的值问题,本例中屏蔽了0,但实际使用过程中,可能有用到0的情况,本例没有特殊处理
调用方式
调用方式,采用隐示意图跳转的方式:
Router.from(this).uri(Uri.parse("https://nav.github.com/lotty/component")).go();
小结
利用IntentFilter
的匹配规则,来实现页面间的跳转,好处是:
- 避免类中的直接import
- 模块之间解耦
- 统一管理
- 链式调用:更加简洁