Android 通过自定义注解实现Activity间跳转时登录路由的自动拦截

应用场景

在Android 中部分软件需要登录才能使用,但是有的页面又不需要登录,Android不同于Web可以直接拦截重定向路由,因此如果在Android中如果需要检测是否登录,如果没登录跳转登录的话就需要再每个页面中判断,当然也可以写成公共方法,但是这样的方式还是比较麻烦。这里讲一个自定义注解实现这个需求的方法

编写注解

先直接编写一个注解

java 复制代码
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME) //运行时有效
public @interface NeedLogin {
    /**
     * 开关,可以不需要,但是我觉得还有有比较好,看个人需求,默认为不开启检测是否登录
     */
    boolean enable() default false;
}

编写公共代码

我们可以再onStart生命周期中进行检测:是否启用注解和是否登录,记得写在BaseActivity中,这样后面继承BaseActivity时方法自动生效,在需要登录拦截的Activity中只需要添加一个注解就可以实现自动拦截、登录、回显!
是否启动注解:这里需要一点自定义注解的理论知识,请自行学习

java 复制代码
    private boolean isNeedLogin() {
        // 通过反射或注解处理器获取当前 Activity 是否需要登录
        boolean isAnnotation = getClass().isAnnotationPresent(NeedLogin.class);
        if (!isAnnotation) {
            return false;
        }
        NeedLogin needLogin = getClass().getAnnotation(NeedLogin.class);
        if (needLogin == null) {
            return false;
        }
        return needLogin.enable();
    }

是否登录:这个没任何讲解的,你是使用SharedPreferences还是MMKV还是别的存储登录信息都可以无所谓,简单写个示例:

复制代码
    private boolean checkLogin() {
        // 检查登录状态的逻辑,true代表已登录,false代表未登录
        return !errorService.isLogin();
    }

然后在onStart生命周期方法中进行检测

java 复制代码
    @Override
    protected void onStart() {
        super.onStart();
        if (errorService == null) {
            return;
        }
        //不包含注解或者登录注解未开启
        if (!isNeedLogin()) {
            return;
        }
        //已登录,则跳转登录
        if (!checkLogin()) {
            return;
        }
      //TODO 这里可以跳转登录了
    }

提出疑问

  1. 如果想登录成功后再回调这个页面然后刷新页面怎么实现?
  2. 跳转页面的时候是否可以保持原参数的传递
  3. 登录页怎么写

问题解决

思考问题

如果想跳转回来肯定需要告知登录页我当前页面的路径,那么我们跳转登录的时候就必须要传递过去,那么我们定义一个参数存储这个当前页面路径TARGET_ACTIVITY

复制代码
    /**
     * 跳转目标Activity页面,目前用于自动检测登录的作用
     */
    public static final String TARGET_ACTIVITY = "targetActivity";

那么我稍微修改下跳转登录,修改完善一下上面的onStart

java 复制代码
    @Override
    protected void onStart() {
        super.onStart();
        if (errorService == null) {
            return;
        }
        //不包含注解或者登录注解未开启
        if (!isNeedLogin()) {
            return;
        }
        //已登录,则跳转登录
        if (!checkLogin()) {
            return;
        }
        //如果未登录跳转登录并且把当前页的信息传递过去,以便于登录后回传
        Bundle bundle = getIntent().getExtras();
        if (bundle == null) {
            bundle = new Bundle();
        }
        bundle.putString(ConstantsHelper.TARGET_ACTIVITY, getClass().getName());
        errorService.toLogin(this, bundle);//就是一个简单的Intent跳转
        finish();
    }

完善登录页面代码

简单思考一下我们再登录页需要跳转到哪几个目标页:首页、指定目标页、返回上一页

那么我们编写几个接口方法

java 复制代码
public interface UserView extends BaseView {
    /**
     * 直接返回上个页面
     */
    void toLast();

    /**
     * 是否有需要跳转的目标页面
     * @return true有目标页面
     */
    boolean hasTarget();

    /**
     * 跳转到目标页面,结合hasTarget使用
     */
    void toTarget();

    /**
     * 跳转到主页
     */
    void toMain();

    /**
     * 关闭键盘
     */
    void hideKeyboard();
}

我们在登录页实现接口,然后模拟下登录操作

点击登录

java 复制代码
    public MutableLiveData<UserInfo> getLiveData() {
        return liveData;
    }
	//点击按钮触发的方法,仅用于模拟
    public void loginClick(View v, RequestLoginBean requestLoginBean, String password) {
        int id = v.getId();
        if (id == R.id.login_submit) {
            if (StringUtil.isEmpty(requestLoginBean.getUsername())) {
                baseView.showToast( "请填写用户名");
                return;
            }
            if (StringUtil.isEmpty(password)) {
                baseView.showToast(  "请填写密码");
                return;
            }
            try {
                requestLoginBean.setPassword(MD5Util.md5Encode(password));
            } catch (Exception e) {
                e.printStackTrace();
                baseView.showToast("密码加密异常");
            }
//            iRepository.login(requestLoginBean, liveData);
            //模拟登录情况
            baseView.showLoading("正在登录,请稍后...");
            UserAccountHelper.setToken("this is token !!!");
            UserAccountHelper.setRefreshToken("this is refresh_token !!!");
            UserInfo userInfo = new UserInfo() {{
                setId("1");
                setAvatar("https://img2.baidu.com/it/u=2948556484,2204941832&fm=253&fmt=auto&app=120&f=JPEG?w=655&h=436");
                setEmail("fzkf3318@163.com");
                setName("张三");
                setPhone("15210230000");
                setRealName("张韶涵");
                setRoleName("演员");
                setSex(1);
            }};
            new Handler(Looper.getMainLooper()).postDelayed(() -> {
                baseView.hideLoading();
                liveData.setValue(userInfo);
            }, 3000);
        }
    }

LoginActivity中监听liveData

java 复制代码
mViewModel.getLiveData().observe(this, userInfo -> mViewModel.loginCallback(userInfo, binding.userEdit.getText().toString()));

//mViewModel中
    public void loginCallback(UserInfo userInfo, String userName) {
        //存储登录信息和登录状态
        UserAccountHelper.saveLoginState(userInfo, true);
        //这里只是判断本地账号和上次账号是否为同一个,如果不是同一个则不能继续之前操作,则需要返回App首页刷新,并且同事判断下当前app是不是只有当前登录页一个页面
        if (TextUtils.isEmpty(userName) || !userName.equals(UserAccountHelper.getAccount()) ||
                AppManager.getAppManager().getActivityStack().size() == 1) {
            UserAccountHelper.saveAccount(userName);
            //打开MainActivity
            baseView.toMain();
            return;
        }
        //存储本地登录的账号
        UserAccountHelper.saveAccount(userName);
        if (baseView.hasTarget()) {
            baseView.toTarget();
            return;
        }
        baseView.toLast();
    }

现在完善一下LoginActivity

java 复制代码
 @SuppressLint("UnsafeIntentLaunch")
    @Override
    public void toLast() {
        showToast("登录成功!");
        setResult(RESULT_OK, getIntent().putExtras(bundle));
        finish();
    }

    @Override
    public boolean hasTarget() {
        String targetActivity = bundle.getString(ConstantsHelper.TARGET_ACTIVITY);
        if (TextUtils.isEmpty(targetActivity)) {
            return false;
        }
        try {
            //是否报错,不报错说明目标页面存在
            Class.forName(targetActivity);
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    @Override
    public void toTarget() {
        String targetActivity = bundle.getString(ConstantsHelper.TARGET_ACTIVITY);
        if (TextUtils.isEmpty(targetActivity)) {
            toLast();
            return;
        }
        try {
            //是否报错,不报错说明目标页面存在
            Intent intent = new Intent(this, Class.forName(targetActivity));
            intent.putExtras(bundle);
            startActivity(intent);
            finish();
        } catch (ClassNotFoundException e) {
            toLast();
        }
    }

    @Override
    public void toMain() {
        showToast("登录成功!");
        AppManager.getAppManager().finishAllActivity();
        startActivity(errorService.getMainActivity());
    }

编写案例测试效果

编写一个页面

java 复制代码
@NeedLogin(enable = true)
@AndroidEntryPoint
public class TargetActivity extends BaseActivity<EmptyViewModel, ActivityTargetBinding> {
    public final static String ARGS = "ARGS";

    @Override
    protected int getLayoutId() {
        return R.layout.activity_target;
    }

    @Override
    public String setTitleBar() {
        return "测试登录拦截";
    }

    @Override
    public void initView(Bundle savedInstanceState) {
        binding.buttonLogin.setOnClickListener(v-> errorService.toLogin(this));
    }

    @Override
    public void initData(Bundle bundle) {
        String args = bundle.getString(ARGS);
        binding.tvArgs.setText(TextUtils.isEmpty(args) ? "暂无参数" : args);
    }
}
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".activity.TargetActivity">

        <TextView
            android:id="@+id/tv_args"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/auto_color"
            android:textSize="@dimen/font_18"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <Button
            android:id="@+id/button_login"
            android:text="前往登录"
            android:textColor="@color/auto_color"
            android:textSize="@dimen/font_18"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_args"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

效果图

完结

代码地址
https://github.com/fzkf9225/mvvm-componnent-master/blob/master/app/src/main/java/com/casic/titan/demo/activity/TargetActivity.java

相关推荐
传奇开心果编程37 分钟前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程2 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程2 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓3 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程3 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
supabc1233 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium
Dovis(誓平步青云)3 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物
五彩小白20 小时前
自回归和上下文
android
事圆则缓1 天前
Android 多渠道打包实战:Product Flavors、签名、AAB 与发包校验
android