user-state-check
根据AOP完成用户状况检测的结构
github地址: user-state-check
功用
- 经过dexmaker 完成动态署理,经过设置ViewFactory2,动态生成view的子类。合作xml中界说属性。能够无感的阻拦任意view的点击事情
- 经过dexMaker 完成AOP,能够生成任意类的子类。便于和viewDataBing联合运用。
- 能够和RxJava联合运用。
- 能够自界说多个用户状况(最多32个,用的int保存的,能够自行扩展成long类型)
- 能够主动跳转相关页面
示例
一切的示例都在demo的MainActivity中
1.合作viewDataBiding运用
布局文件如下
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#eee">
<Button
android:id="@+id/btn_collection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:onClick="@{activity::doCollection}"
android:text="保藏(需求登陆,绑定手机号,实名认证)"
tools:ignore="HardcodedText" />
</RelativeLayout>
在需求阻拦的办法上运用注解
设置需求检测的状况为: 登录,绑定手机号,实名认证。
@CheckUserState(states = Login|BindPhoneNumber|BindRealName,policy = UserStateCheckPolicy.autoGo)
public void doCollection(View view){
GZToast.success().show("保藏成功");
}
动态生成Activity的子类
private void aopActivity() {
binding.setActivity( UserStateManager.getProxy(compositeDisposable,this,this,e->{
GZToast.error().show(e.getMessage());
}));
}
作用
在履行保藏的之前,会检测用户状况,假如用户状况不满足,会主动跳转到相关页面。最终全面满足以后会主动履行保藏操作。
2.阻拦View的点击事情
1.设置ViewFactory2
留意需求在onCreate()办法之前注入。避免AppCompatActivity先注入
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//注入factory2
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), UserStateManager.getLayoutInflaterFactory(this,compositeDisposable, e->{
GZToast.error().show(e.getMessage());
}));
super.onCreate(savedInstanceState);
currentActivity=this;
}
设置view要检测的状况
经过 app:checkUserPolicy设置要检测的状况
经过app:checkUserPolicy设置状况不满足情况下的履行战略
包含两种:
战略值 | 履行的动作 |
---|---|
autoGo | 主动跳转到相关界面 |
justCheck | 只检查状况,假如不满足会抛出异常 |
<LinearLayout
android:id="@+id/layout_collection"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:background="#25DA6E"
android:orientation="vertical"
app:checkUserPolicy="autoGo"
app:checkUserState="login">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="阻拦LinearLayout 需求登录以后才干履行保藏,没有登录主动登录"
android:textColor="@color/white" />
</LinearLayout>
运用
正常设置点击事情即可,经过结构完成阻拦工作对业务代码无感。
private void aopView() {
binding.layoutCollection.setOnClickListener(v->{
GZToast.success().show("保藏成功");
});
binding.tvCollection.setOnClickListener(v -> {
GZToast.success().show("保藏成功");
});
}
作用
3.和RXJava合作运用
重点是下面这句
compose(new UserStateTransform<>(this, Login|BindRealName))
private void aopApi() {
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://tenapi.cn/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
BaiduApi baiduApi = retrofit.create(BaiduApi.class);
BaiduApi finalBaiduApi = baiduApi;
binding.btnGetBaiduHot.setOnClickListener(v->{
Disposable disposable = finalBaiduApi.getHotList()
//检测用户状况
.compose(new UserStateTransform<>(this, Login|BindRealName))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(BaiduHotListResult::getData)
.subscribe(list -> {
StringBuffer buffer=new StringBuffer();
for(HotItemBean itemBean:list){
buffer.append(itemBean.getName()).append("\n");
}
binding.tvBaiduContent.setText(buffer);
}, e -> {
e.printStackTrace();
GZToast.error().show(e.getMessage());
});
compositeDisposable.add(disposable);
});
}
作用
用户不同意
会抛出UserStateCheckException经过其 getState() 办法能够获取匹配不成功的用户状况
在动态署理相关类的时分都能够传入一个过错的处理器,自界说过错的处理逻辑
/**
* 获取署理类,该类为 delegate的子类
* 并且自会重写具有{@link com.zhuguohui.demo.userstate.CheckUserState}注解的办法
* 主动刺进用户状况检测的逻辑
* @param compositeDisposable 用于撤销恳求
* @param context 上下文
* @param delegate 被署理的类
* @param errorFunction 出错时的回调
* @param <T> delegate的类型
* @return 回来delegate的子类对象
*/
public static <T> T getProxy(CompositeDisposable compositeDisposable, Context context, T delegate, CallBack<Throwable> errorFunction) {
return UserStateCheckUtil.getProxy(compositeDisposable,context,delegate,errorFunction);
}
作用
只是单纯的弹出提示框
运用
1.添加依靠
该库现已上传到 jitpack
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
添加依靠
dependencies {
implementation 'com.github.zhuguohui:user-state-check:1.0.0'
}
详细代码看demo
1.自界说用户状况
示例代码:
重点是要在结构函数中穿入,状况称号,和对应于xml中的属性称号。这样才干和xml属性联动。
public final class DemoUserState extends IUserState {
private static final DemoUserState login=new DemoUserState("登录",1);
private static final DemoUserState bindPhoneNumber=new DemoUserState("绑定手机号",2);
private static final DemoUserState bindRealName=new DemoUserState("实名认证",4);
public static final int Login=1;
public static final int BindPhoneNumber=2;
public static final int BindRealName=4;
public static final DemoUserState[] values=new DemoUserState[]{login,bindPhoneNumber,bindRealName};
protected DemoUserState(String desc, int attrFlagValue) {
super(desc, attrFlagValue);
}
public static IUserState[] getUserStateByFlags(int flags) {
DemoUserState[] values = DemoUserState.values;
List<DemoUserState> stateList=new ArrayList<>(0);
for(DemoUserState state:values){
boolean match =( flags & state.getAttrFlagValue()) == state.getAttrFlagValue();
if(match){
stateList.add(state);
}
}
return stateList.toArray(new IUserState[0]);
}
}
声明xml属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--用户检查相关的属性,放置在类下方便查找-->
<!--注释运用flag的属性不能界说format-->
<attr name="checkUserState">
<flag name="login" value="1"/>
<flag name="bindPhone" value="2"/>
<flag name="bindRealName" value="4"/>
</attr>
</resources>
完成自己的状况办理器
/**
* <pre>
* Created by zhuguohui
* Date: 2023/3/1
* Time: 13:27
* Desc:用户状况办理的接口
* 界说成接口方便不同的项目完成详细的类
* </pre>
*/
public interface IUserStateManager {
/**
* 是否达到了用户状况
* @param userState
* @return
*/
boolean isMatchUserState(IUserState userState);
/**
* 履行相应的恳求
* @param state
* @return
*/
void doMatchUserState(Context context,IUserState state);
/**
* 用于判别 当前的页面的用途,因为每种用户状况的确认或许触及多个页面
* 比方登录或许触及到登录和注册两个页面。只要相关的页面都销毁了。
* 才干够判别是否登录成功,回调相关的callback
* @param activity
* @return 假如当前页面和登录相关回来 用户状况数组
* 以此类推,假如都不相关,回来null。
*/
IUserState[] getActivityUserStateType(Activity activity);
/**
* 经过flags获取用户状况
* @param flags
* @return
*/
IUserState[] getUserStateByFlags(int flags);
}
改造BaseActivity
1.要完成View阻拦,需求注入ViewFactory2
2.要完成回调。需求在 onPostCreate() 和 onDestroy() 办法中回调结构的办法。
public class BaseActivity extends AppCompatActivity {
private static Activity currentActivity;
protected CompositeDisposable compositeDisposable=new CompositeDisposable();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//注入factory2
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this), UserStateManager.getLayoutInflaterFactory(this,compositeDisposable, e->{
GZToast.error().show(e.getMessage());
}));
super.onCreate(savedInstanceState);
currentActivity=this;
}
@Override
protected void onResume() {
super.onResume();
currentActivity=this;
}
public static Activity getCurrentActivity() {
return currentActivity;
}
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
UserStateManager.getInstance().onActivityPostCreate(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
UserStateManager.getInstance().onActivityDestroy(this);
compositeDisposable.dispose();
}
}
用户状况相关的界面需求完成IUserStatePage接口
package com.zhuguohui.demo.userstate;
import androidx.annotation.Nullable;
/**
* <pre>
* Created by zhuguohui
* Date: 2023/3/1
* Time: 14:07
* Desc:通常由activity完成。
* 表明这个界面和用户的状况的流程有关,比方登录,绑定手机号,实名认证
* 因为一个流程比方登录,或许触及到多个页面。比方注册,登录,找回暗码
* 只要相关的页面全部finish以后,才去检查是否登录成功,回调相关的办法。
* </pre>
*/
public interface IUserStatePage {
/**
* 回来当前页面和那些流程相关
* @return 回来相关用户状况的数组,能够为null
*/
@Nullable
IUserState[] getUserSatePageTypes();
}
将状况办理器设置给结构
/**
* Created by zhuguohui
* Date: 2023/3/26
* Time: 11:18
* Desc:
*/
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
UserStateManager.getInstance().setUserStateManagerImpl(DemoUserStateManager.getInstance());
}
}
详细代码还得看demo
原理
动态署理
根据开源库
最开始运用的是 下面这个cglib库
CGLib-for-Android
可是这个库有个问题,不支持对没有无参结构函数类的动态署理,我还提了Issues
后来我发现这个库用的是dexmaker所以,直接运用dexmaker
dexmaker
dexmaker能够对只要有参结构函数的类完成动态署理。
可是又遇到下一个问题。动态署理view。有些办法不能被署理
假如一个办法被@UnsupportedAppUsage 注解注释。那么无法经过反射获取。
而咱们要署理onClick办法也不需求重写一切办法
所以我在原有的dexmaker的基础上,添加了能够自界说要重写那个办法的功用。
public final class ProxyBuilder<T> {
public interface MethodOverrideFilter{
boolean isOverrideMethod(Method method);
}
MethodOverrideFilter methodOverrideFilter;
public ProxyBuilder<T> setMethodOverrideFilter(MethodOverrideFilter methodOverrideFilter) {
this.methodOverrideFilter = methodOverrideFilter;
return this;
}
}
为了便于运用生成了自己的库。
zhuguohui/Android-Cglib
运用该库完成对setOnClickListener的动态署理。
还有个优点,假如不单独重写这一个办法的话。光是LinearLayout中就要800多个public办法需求重写。每个办法都经过反射调用。性能比较差。
return (View) ProxyBuilder.forClass(viewClass)
.constructorArgTypes(Context.class, AttributeSet.class)
.constructorArgValues(context, attrs)
.dexCache(context.getDir("dx", Context.MODE_PRIVATE))
//只重写setOnClickListener
// 因为android中的View中的一些方式被 @UnsupportedAppUsage注解润饰
//客户端无法经过反射来获取,无法重写
.setMethodOverrideFilter(method -> method.getName().equals("setOnClickListener"))
.handler(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
if(method.getName().equals("setOnClickListener")){
if(objects.length>0&& objects[0] instanceof View.OnClickListener){
View.OnClickListener onClickListener= (View.OnClickListener) objects[0];
objects=new Object[]{new CheckUserStateOnClickListener(context,compositeDisposable,onClickListener,userStateFlags,policy,errorFunction)};
}
}
return ProxyBuilder.callSuper(proxy, method, objects);
}
}).build();
用户状况检测
根据rxjava的ObservableTransformer完成,代码如下。
package com.zhuguohui.demo.userstate.transform;
import android.content.Context;
import android.os.Looper;
import com.zhuguohui.demo.userstate.IUserState;
import com.zhuguohui.demo.userstate.UserStateCallBack;
import com.zhuguohui.demo.userstate.UserStateCheckException;
import com.zhuguohui.demo.userstate.manager.UserStateManager;
import com.zhuguohui.demo.userstate.policy.UserStateCheckPolicy;
import java.util.Arrays;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.ObservableSource;
import io.reactivex.ObservableTransformer;
import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
/**
* <pre>
* Created by zhuguohui
* Date: 2023/2/28
* Time: 15:30
* Desc:
* </pre>
*/
public class UserStateTransform<T> implements ObservableTransformer<T, T> {
private Context context;
private List<IUserState> userStateList;
private UserStateCheckPolicy policy;
public UserStateTransform(Context context,int userStateFlags) {
this(context,false, userStateFlags);
}
public UserStateTransform(Context context,boolean justCheck, int userStateFlags) {
this(context,justCheck ? UserStateCheckPolicy.justCheck : UserStateCheckPolicy.autoGo, userStateFlags);
}
public UserStateTransform(Context context,UserStateCheckPolicy policy,int userStateFlags) {
this.context=context;
IUserState[] states = UserStateManager.getInstance().getUserStateByFlags(userStateFlags);
userStateList=Arrays.asList(states);
this.policy = policy == null ? UserStateCheckPolicy.autoGo : policy;
}
private static Object object = new Object();
Scheduler scheduler;
@Override
public ObservableSource<T> apply(Observable<T> upstream) {
//先验证用户状况
return Observable.just(object)
.doOnNext(obj -> scheduler = getCallSchedulers())
.flatMap(obj->{
Observable<Object> next=Observable.just(obj);
for(IUserState userState:userStateList){
next=next.flatMap(o->matchUserState(o,userStateList,userState));
}
return next;
})
.flatMap(obj -> upstream) //全部验证经过才订阅上游
;
}
/**
* 获取上游的Scheduler,这样在回调的时分切换回原先的Scheduler
*
* @return
*/
private Scheduler getCallSchedulers() {
if (Looper.myLooper() == Looper.getMainLooper()) {
return AndroidSchedulers.mainThread();
} else {
return Schedulers.io();
}
}
private Observable<Object> matchUserState(Object t, List<IUserState> userStateList, IUserState userState) {
boolean needMatch = userStateList.contains(userState);
if (needMatch && !UserStateManager.getInstance().isMatchUserState(userState)) {
if (policy == UserStateCheckPolicy.autoGo) {
return Observable.create((ObservableOnSubscribe<Object>) emitter -> {
UserStateManager.getInstance().doMatchUserState(context,userState, new UserStateCallBack() {
@Override
public void onMath() {
scheduler.scheduleDirect(() -> {
//登录成功
emitter.onNext(t);
emitter.onComplete();
});
}
@Override
public void onCancel() {
scheduler.scheduleDirect(() -> {
emitter.onError(new UserStateCheckException(userState));
});
}
});
});
} else {
return Observable
.error(new UserStateCheckException(userState))
.observeOn(scheduler);
}
} else {
return Observable
.just(t)
.subscribeOn(scheduler);
}
}
}