为Activity的启动添加约束条件

回想起刚开始做Android开发工作时,有一个烦恼:当拿到一个新项目的时候,想从界面的跳转来梳理整个App的功能,结果发现根本没有对应的产品文档,然后代码中启动Activity的代码散落在Activity以外的各种地方,例如自定义view中、Fragment中、各种Adapter中…这样就造成了如下麻烦:

  1. 难以寻找当前Activity的所有跳转点:想要知道当前Acitivity有多少跳转点,得把这个Activity用到的好多类都看一遍;
  2. 缺少规范性:各个组件都有跳转Activity的能力,职责的划分就比较模糊;
  3. Activity逃逸严重:各种类可能都持有Activity的引用,为内存溢出埋下隐患。

而启动方法直接new Intent(...),然后startActivity也有以下问题:

  1. Activity参数有效性的检查需要延后到Activity的onCreate方法及以后;
  2. Activity参数字段名外泄;
  3. Activity多参数时难以区分必要参数和非必要参数,参数的组合形式也难以表达;
  4. 没法方便又准确的知道某个Activity的启动点位于何处。

可能还有其他问题,鉴于上面提出的几点,后来在开发项目中对启动Activity作出了如下约束:

  1. 仅Activity/Fragment可以启动新的Activity/Fragment(允许特殊情况例外,需备注);
  2. 启动方法必须通过对应Activity提供的静态方法或全局路由(例如ARouter)的方式(通知、闹钟等系统机制除外);

下面分享下添加这两点约束条件的理由。

一、限制启动Activity的范围

第一点是在限制启动Activity的范围,只有特定组件有权利去启动Activity,这样使得职责更为集中、单一,查找功能的时候也更为方便。而且这两个组件本身就可以直接拿到当前Activity:Activity自身可以通过this来取得,Fragment则可以通过getActivity方法。

但是这样也会给编程带来一些麻烦,有些点击事件的处理需要额外的步骤,最常见是自定义View以及RecyclerView的Adapter和ViewHolder。

自定义View点击启动Activity的处理

禁止自定义View中去启动Activity的理由是:自定义View应当聚焦控件自身的功能,不应该涉及跳转业务逻辑。

处理方法就是设置监听回调:

public class CustomView extends View {
    private Runnable mOnXXXClickListener;
    private View mXXX;

    public CustomView(Context context) {
        super(context);
        //....

        mXXX.setOnClickListener(v -> {
            if(mOnXXXClickListener != null){
                mOnXXXClickListener.run();
            }
        });
    }

    public void setOnXXXClickListener(Runnable onXXXClickListener) {
        this.mOnXXXClickListener = onXXXClickListener;
    }
}

RecyclerView的Adapter和ViewHolder的点击处理

如果之前是在Adapter中处理点击的,那么可以考虑用上述监听的方式,或者像下面这样在Activity中使用匿名类的方式(这里的XXXAdapter已经实现了对应抽象方法):

mRecyclerView.setAdapter(new XXXAdapter() {
   @Override
   public void onBindViewHolder(@NonNull @NotNull RecyclerView.ViewHolder holder, int position) {
       super.onBindViewHolder(holder, position);
       holder.itemView.setOnClickListener(v -> {
           //start activity
       });
   }
});

如果之前是在ViewHolder中处理点击的,那么就得通过监听,或者直接把ViewHolder作为内部类放到Activity中。

二、限制启动Activity的方法

如果启动Activity的方法没有限制,那么检查Activity的启动点就不太方便,并且new Intent有上面所说的问题。

一种简单的方法是在每个Activity添加静态的启动方法,例如:

public class XXXActivity extends AppCompatActivity {
    public static void start(Activity activity) {
        Intent intent = new Intent(activity, XXXActivity.class);
        activity.startActivity(intent);
    }
    //......
}

如果有参数且要检查有效性就是:

public class XXXActivity extends AppCompatActivity {
    private static final String KEY_ARG_1 = "key_arg_1";

    public static void start(Activity activity, String arg) {
        Intent intent = new Intent(activity, XXXActivity.class);
        if(!TextUtils.isEmpty()){
             intent.putExtra(KEY_ARG_1, arg);
        } else {
            throw new IllegalArgumentException();
        }
        activity.startActivity(intent);
    }
    //......
}

需要处理返回参数时,可以添加一个startForResult版本的启动方法:

public class XXXActivity extends AppCompatActivity {
    public static void start(Activity activity) {
        Intent intent = new Intent(activity, XXXActivity.class);
        activity.startActivity(intent);
    }

    public static void startForResult(Activity activity, int requestCode) {
        Intent intent = new Intent(activity, XXXActivity.class);
        activity.startActivityForResult(intent, requestCode);
    }
    //......
}

有些情况只需要Intent,而不用start的时候可以提供一个getIntentForStart方法:

public class XXXActivity extends AppCompatActivity {
    private static final String KEY_ARG_1 = "key_arg_1";

    public static Intent getIntentForStart(Activity activity, String arg) {
        Intent intent = new Intent(activity, XXXActivity.class);
        if(!TextUtils.isEmpty()){
             intent.putExtra(KEY_ARG_1, arg);
        } else {
            throw new IllegalArgumentException();
        }
        return intent;
    }
    //......
}

你可能感兴趣的:(Java,android,java,Activity,Intent,startActivity)