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

作用

在履行保藏的之前,会检测用户状况,假如用户状况不满足,会主动跳转到相关页面。最终全面满足以后会主动履行保藏操作。

动态代理View 实现无感化的用户状态检测框架

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("保藏成功");
        });
    }

作用

动态代理View 实现无感化的用户状态检测框架

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

作用

动态代理View 实现无感化的用户状态检测框架

用户不同意

会抛出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);
    }

作用

只是单纯的弹出提示框

动态代理View 实现无感化的用户状态检测框架

运用

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

动态代理View 实现无感化的用户状态检测框架

后来我发现这个库用的是dexmaker所以,直接运用dexmaker

dexmaker

dexmaker能够对只要有参结构函数的类完成动态署理。

可是又遇到下一个问题。动态署理view。有些办法不能被署理

假如一个办法被@UnsupportedAppUsage 注解注释。那么无法经过反射获取。

动态代理View 实现无感化的用户状态检测框架

而咱们要署理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);
        }
    }
}