一种Android简单页面路由的实现

安卓隐式意图(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地址解释。需要至少有一个完全匹配(支持通配符)。需要说明的几点是:

  1. mimeType 有默认值:contentfile
  2. IntentFiltersetDatasetType 方法会清除彼此内容,同时设置时需要调用setDataAndType

路由的设计

自定义Action

定义全局的Action作为跳转的IntentFilterAction
比如:

<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或者httphost选择应用的官网HOST,path设计为对应的页面地址,上图中/lotty/component就代表组件的Activity

扫描二维码关注公众号,回复: 9858436 查看本文章

工具类的设计

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);
  }
}

以上实现中需要注意的是:

  1. Application 启动页面时,需要添加Intent.FLAG_ACTIVITY_NEW_TASK的Flag
  2. 处理requestCode的值问题,本例中屏蔽了0,但实际使用过程中,可能有用到0的情况,本例没有特殊处理

调用方式

调用方式,采用隐示意图跳转的方式:

Router.from(this).uri(Uri.parse("https://nav.github.com/lotty/component")).go();

小结

利用IntentFilter的匹配规则,来实现页面间的跳转,好处是:

  1. 避免类中的直接import
  2. 模块之间解耦
  3. 统一管理
  4. 链式调用:更加简洁

链接

项目github地址

发布了36 篇原创文章 · 获赞 9 · 访问量 4886

猜你喜欢

转载自blog.csdn.net/lotty_wh/article/details/103813343