这篇文章的实现略有侵入性,但不妨碍你对这方面的思考,更好的实现可以参考另外一篇文章。
在App项目中有一些Activity是需要登录成功后才能进去的,比如订单详情页,因为订单是跟账号挂钩的,登录的账号一般会有个ID,需要带着ID和订单号去查订单信息。
很多刚开始做App的同学会认为应该在进入订单详情页前先确保登录成功,也就是说把登录判断和发起登录都是进入订单详情页前搞定,的确这么做没有问题,但是恐怕体力活会很多吧,如果能理解拦截器的原理就可以简化我们的工作量。
@InterceptWith(LoginInterceptor.class)
public class OrderDetailActivity extends InterceptorActivity {
private static final String EXTRA_ORDER_ID = "orderId";
private TextView mOrderInfoText;
private String mOrderId;
public static void startActivity(Context context, String orderId) {
Intent intent = new Intent(context, OrderDetailActivity.class);
intent.putExtra(EXTRA_ORDER_ID, orderId);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_order_detail);
mOrderId = getIntent().getStringExtra(EXTRA_ORDER_ID);
mOrderInfoText = (TextView) findViewById(R.id.orderInfo);
}
@Override
protected void invoked() {
super.invoked();
mOrderInfoText.setText("订单信息(order id: " + mOrderId + ")");
// 根据orderId请求完整的订单信息
}
}
看到以上订单详情页只要在Acitvity class之上加上一个登录校验的注解然后并在invoke()回调里执行跟登录相关的接口查询及初始化页面即可,这样就不用关注登录的实现细节,一定程度上解耦了你的业务实现。
下面我们说说实现方式,当然这里的Interceptor并不是纯粹利用Java的语言的动态代理,这只是一种模仿,依附Activity的生命周期完成的:
- 定义Annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface InterceptWith {
/**
* @return a Interceptor class array (must have a constructor without parameters inside)
*/
Class<? extends Interceptor>[] value();
}
注:如果对Annotation不是很了解最好查阅下相关资料稍微了解下,这里就不过啰嗦了。
- 定义Interceptor父类的基本的结构:
public abstract class Interceptor {
/**
* Request code used to start activity for result.
*
* @return request code
*/
public abstract int getRequestCode();
/**
* Check interceptor's condition is meet or no.
*
* @param context Android context
* @return condition is meet or no
*/
public abstract boolean isSatisfied(Context context);
/**
* if condition was not satisfied, it'll be called to acquire resource or permission and so on.
*
* @param activity see {@link Activity}
*/
public abstract void process(Activity activity);
}
注:以上是每个拦截器需要实现的抽象父类,以下以登录校验的拦截器举例:
public class LoginInterceptor extends Interceptor {
@Override
public int getRequestCode() {
return LoginActivity.REQUEST_CODE_LOGIN;
}
@Override
public boolean isSatisfied(Context context) {
return UserConfigCache.isLogin(context);
}
@Override
public void process(Activity activity) {
LoginActivity.startActivityForResult(activity, getRequestCode());
}
}
- 有拦截检测功能的Activity:
public class InterceptorActivity extends AppCompatActivity {
private List<Interceptor> mInterceptors = new ArrayList<>();
/**
* Called only when all interceptors verified OK,
* so do your work here which all interceptors are passed.
*/
protected void invoked() {
}
@Override
protected void onStart() {
super.onStart();
if (mInterceptors.size() == 0) {
scanInterceptors();
verifyInterceptors();
}
}
private void scanInterceptors() {
mInterceptors.clear();
InterceptWith annotation = getClass().getAnnotation(InterceptWith.class);
if (annotation != null) {
Class<? extends Interceptor>[] classes = annotation.value();
for (Class<? extends Interceptor> clazz : classes) {
try {
mInterceptors.add(clazz.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
private void verifyInterceptors() {
if (mInterceptors.isEmpty()) {
return;
}
for (int i = 0; i < mInterceptors.size(); i++) {
Interceptor interceptor = mInterceptors.get(i);
if (interceptor.isSatisfied(this)) {
if (i == mInterceptors.size() - 1) {
invoked();
break;
}
} else {
interceptor.process(this);
break;
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
for (Interceptor interceptor : mInterceptors) {
if (interceptor.getRequestCode() == requestCode) {
if (resultCode == RESULT_OK) {
verifyInterceptors();
break;
} else if (resultCode == RESULT_CANCELED) {
finish();
break;
}
}
}
}
}
注:
主要通过在onStart()进行对配置的拦截器进行检查,一旦有拦截器不满足条件就跳转对应的页面(如登录页面)请求资源,当资源请求获取到后(登录成功后)到了ActivityResult再校验其他拦截器,如果配置了多个拦截器则当所有的拦截器都被满足条件后会触发invoked()回调函数执行, 所以需要类似登录成功才能执行的代码就放在invoked()里好了。
当然配置多个拦截器也很方便:
@InterceptWith({FirstInterceptor.class, SecondInterceptor.class, ThirdInterceptor.class})
public class XXXActivity extends InterceptorActivity
- 以上代码除了LoginInterceptor.java是按业务定义的拦截器,其余都可以作为common模块里的代码或者library中的代码,完整Demo源码请参考Android Interceptor.