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

相关推荐
晨曦_子画4 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
孤客网络科技工作室26 分钟前
AJAX 全面教程:从基础到高级
android·ajax·okhttp
Mr Lee_2 小时前
android 配置鼠标右键快捷对apk进行反编译
android
顾北川_野2 小时前
Android CALL关于电话音频和紧急电话设置和获取
android·音视频
&岁月不待人&2 小时前
Kotlin by lazy和lateinit的使用及区别
android·开发语言·kotlin
Winston Wood4 小时前
Android Parcelable和Serializable的区别与联系
android·序列化
清风徐来辽4 小时前
Android 项目模型配置管理
android
帅得不敢出门5 小时前
Gradle命令编译Android Studio工程项目并签名
android·ide·android studio·gradlew
problc5 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
帅得不敢出门16 小时前
安卓设备adb执行AT指令控制电话卡
android·adb·sim卡·at指令·电话卡