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

相关推荐
阿巴斯甜8 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker8 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95279 小时前
Andorid Google 登录接入文档
android
黄林晴10 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android