应用场景
在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 这里可以跳转登录了
}
提出疑问
- 如果想登录成功后再回调这个页面然后刷新页面怎么实现?
- 跳转页面的时候是否可以保持原参数的传递
- 登录页怎么写
问题解决
思考问题
如果想跳转回来肯定需要告知登录页我当前页面的路径,那么我们跳转登录的时候就必须要传递过去,那么我们定义一个参数存储这个当前页面路径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>
效果图