diff --git a/app/src/main/java/com/gh/common/util/LoginUtils.java b/app/src/main/java/com/gh/common/util/LoginUtils.java index 05df84a792..4f93669802 100644 --- a/app/src/main/java/com/gh/common/util/LoginUtils.java +++ b/app/src/main/java/com/gh/common/util/LoginUtils.java @@ -321,7 +321,7 @@ public class LoginUtils { case 400801: Utils.toast(context, "访问过于频繁"); break; - case 403001: + case 403007: Utils.toast(context, "设备异常,获取验证码失败,请更换登陆方式或明天再试"); break; case 403202: diff --git a/app/src/main/java/com/gh/gamecenter/login/UserRepository.java b/app/src/main/java/com/gh/gamecenter/login/UserRepository.java index 3a7e23a7d9..56e8207f40 100644 --- a/app/src/main/java/com/gh/gamecenter/login/UserRepository.java +++ b/app/src/main/java/com/gh/gamecenter/login/UserRepository.java @@ -2,15 +2,12 @@ package com.gh.gamecenter.login; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.MediatorLiveData; -import android.arch.lifecycle.Observer; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; -import android.support.annotation.Nullable; import android.text.TextUtils; import com.gh.common.constant.Constants; -import com.gh.common.util.AppDebugConfig; import com.gh.common.util.DeviceUtils; import com.gh.common.util.GetLoginDataUtils; import com.gh.common.util.LoginUtils; @@ -93,23 +90,13 @@ class UserRepository { protected void checkLogin() { LoginTokenEntity tokenEntity = mAppDatabase.loginTokenDao().getTokenById(mCachedId); - if (tokenEntity == null) return; - - TokenEntity accessToken = tokenEntity.getAccessToken(); - if (accessToken == null) return; - long accessExpire = accessToken.getExpire(); - if (accessExpire > Utils.getTime(mContext)) { - cacheAndNotifyLoginToken(tokenEntity); - getLoginUserInfo(); // 先更新页面 - refreshUserInfo(null); // 再刷新userinfo(目的是确定该用户没在其他设备登录) + if (tokenEntity == null || tokenEntity.getRefreshToken() == null || tokenEntity.getAccessToken() == null) { return; } - TokenEntity refreshToken = tokenEntity.getRefreshToken(); - if (refreshToken == null) return; - long refreshExpire = refreshToken.getExpire(); - if (refreshExpire > Utils.getTime(mContext)) { - refreshToken(tokenEntity); + if (refreshToken.getExpire() > Utils.getTime(mContext)) { + cacheAndNotifyLoginToken(tokenEntity); // 设置登录数据 + refreshUserInfo(null); // 刷新UserInfo,如果refreshToken过期 自动刷新 return; } logout(); @@ -140,13 +127,10 @@ class UserRepository { mLoginObsResponseUserInfo.postValue(new ApiResponse<>(mCacheUserInfoEntity)); } else { final LiveData liveUserInfo = mAppDatabase.userInfoDao().getLiveUserInfoById(mCachedId); - mLoginObsResponseUserInfo.addSource(liveUserInfo, new Observer() { - @Override - public void onChanged(@Nullable UserInfoEntity userInfoEntity) { - mLoginObsResponseUserInfo.removeSource(liveUserInfo); - if (userInfoEntity != null) { - userInfoHandle(userInfoEntity, false); - } + mLoginObsResponseUserInfo.addSource(liveUserInfo, userInfoEntity -> { + mLoginObsResponseUserInfo.removeSource(liveUserInfo); + if (userInfoEntity != null) { + userInfoHandle(userInfoEntity, false); } }); } @@ -241,63 +225,6 @@ class UserRepository { }); } - private void refreshToken(final LoginTokenEntity refreshToken) { - RequestBody body = null; - try { - JSONObject content = new JSONObject(); - JSONObject device = DeviceUtils.getLoginDevice(mContext.getApplicationContext()); - content.put("refresh_token", refreshToken.getRefreshToken().getValue()); - content.put("device", device); - body = RequestBody.create(MediaType.parse("application/json"), content.toString()); - } catch (JSONException e) { - e.printStackTrace(); - } - - mUserseaService - .refreshToken(body) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Response() { - @Override - public void onResponse(LoginTokenEntity response) { - super.onResponse(response); - response.setLoginType(refreshToken.getLoginType()); - response.setId(refreshToken.getId()); - - userTokenHandle(response, null); - } - - @Override - public void onApiFailure(ApiResponse e) { - super.onApiFailure(e); - try { - HttpException httpException = e.getHttpException(); - - String string = httpException.response().errorBody().string(); - JSONObject content = new JSONObject(string); - int code = content.getInt("code"); -// Utils.toast(mContext, mContext.getString(R.string.login_refresh_error)); -// if (code == 400802) { // 其他设备登录了该账号 网络重试已经做了处理 -// EventBus.getDefault().post(new EBShowDialog("loginException", string)); // 打开提示框 -// } - - if (code == 400802 || code == 400401) { // 自动注销 - logout(); - } else { - ApiResponse value = new ApiResponse<>(); - value.setThrowable(value.getThrowable()); - value.setHttpException(httpException); - mLoginObsResponseUserInfo.postValue(value); - } - - if (AppDebugConfig.IS_DEBUG) Utils.log("refreshToken::" + code); - } catch (Exception e1) { - e1.printStackTrace(); - } - } - }); - } - //更改用户信息 public void changeUserInfo(final String content, final String editType) { if (mCacheUserInfoEntity == null) { diff --git a/app/src/main/java/com/gh/gamecenter/manager/RefreshTokenManager.java b/app/src/main/java/com/gh/gamecenter/manager/RefreshTokenManager.java index 02fba9d26f..7e18a51fe2 100644 --- a/app/src/main/java/com/gh/gamecenter/manager/RefreshTokenManager.java +++ b/app/src/main/java/com/gh/gamecenter/manager/RefreshTokenManager.java @@ -8,6 +8,7 @@ import com.gh.common.constant.Constants; import com.gh.common.util.DeviceUtils; import com.gh.gamecenter.BuildConfig; import com.gh.gamecenter.entity.LoginTokenEntity; +import com.gh.gamecenter.entity.TokenEntity; import com.gh.gamecenter.entity.UserInfoEntity; import com.gh.gamecenter.eventbus.EBReuse; import com.gh.gamecenter.eventbus.EBShowDialog; @@ -17,14 +18,12 @@ import com.gh.gamecenter.retrofit.Response; import com.gh.gamecenter.retrofit.RetrofitManager; import com.gh.gamecenter.retrofit.service.ApiService; import com.gh.gamecenter.retrofit.service.UserseaService; +import com.lightgame.utils.Utils; import org.greenrobot.eventbus.EventBus; import org.json.JSONException; import org.json.JSONObject; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - import okhttp3.MediaType; import okhttp3.RequestBody; import retrofit2.HttpException; @@ -36,15 +35,12 @@ import retrofit2.HttpException; public class RefreshTokenManager { private static RefreshTokenManager mInstance; - private Executor SINGLE_EXECUTOR = Executors.newSingleThreadExecutor(); private AppDatabase mDatabase; private UserseaService mUserseaService; private ApiService mApiService; private Context mContext; - private boolean isRefreshing = false; - public RefreshTokenManager(Context context) { mContext = context; @@ -65,20 +61,38 @@ public class RefreshTokenManager { } /** - * @param token + * @param accessToken * @param callBack */ - public void refreshToken(String token, final refreshCallBack callBack) { - if (isRefreshing) { + public synchronized void refreshToken(String accessToken, final refreshCallBack callBack) { + + // LoginTokenEntity 为空,可能已经退出登录 + LoginTokenEntity tokenEntity = UserManager.getInstance().getLoginTokenEntity(); + if (tokenEntity == null) { callBack.onLoginFailure(); return; } - isRefreshing = true; + + // 对比AccessToken 如果接口传的token与UserManager的token不一致 则已被刷新过 + if (!accessToken.equals(tokenEntity.getAccessToken().getValue())) { + callBack.onLogin(); + return; + } + + // 判断RefreshToken是否过期 + TokenEntity refreshToken = tokenEntity.getRefreshToken(); + if (refreshToken.getExpire() < Utils.getTime(mContext)) { + Utils.toast(mContext, "账号过期,请重新登录!"); + EventBus.getDefault().post(new EBReuse(PersonalFragment.LOGOUT_TAG)); + callBack.onLoginFailure(); + return; + } + RequestBody body = null; try { JSONObject device = DeviceUtils.getLoginDevice(mContext.getApplicationContext()); JSONObject content = new JSONObject(); - content.put("refresh_token", token); + content.put("refresh_token", refreshToken.getValue()); content.put("device", device); body = RequestBody.create(MediaType.parse("application/json"), content.toString()); } catch (JSONException e) { @@ -90,11 +104,7 @@ public class RefreshTokenManager { @Override public void onResponse(LoginTokenEntity response) { super.onResponse(response); - LoginTokenEntity loginTokenEntity = UserManager.getInstance().getLoginTokenEntity(); - if (loginTokenEntity != null) { - response.setLoginType(loginTokenEntity.getLoginType()); - response.setId(loginTokenEntity.getId()); - } + saveLoginToken(response, mContext); refreshUserInfo(callBack, response.getId()); @@ -112,7 +122,6 @@ public class RefreshTokenManager { String string = e.response().errorBody().string(); JSONObject content = new JSONObject(string); int code = content.getInt("code"); -// Utils.toast(mContext, mContext.getString(R.string.login_refresh_error)); if (code == 400802) { // 其他设备登录了该账号 EventBus.getDefault().post(new EBShowDialog("loginException", string)); // 打开提示框 } @@ -124,7 +133,6 @@ public class RefreshTokenManager { e1.printStackTrace(); } } - isRefreshing = false; } }); } @@ -132,7 +140,7 @@ public class RefreshTokenManager { private void refreshUserInfo(final refreshCallBack callBack, final String cacheId) { mApiService - .getRetryUserInfo(BuildConfig.API_HOST + "users:validate") + .getRetryUserInfo(BuildConfig.API_HOST + "users:validate", "retry") .subscribe(new Response() { @Override public void onResponse(UserInfoEntity response) { @@ -144,7 +152,6 @@ public class RefreshTokenManager { callBack.onLogin(); } - isRefreshing = false; } @Override @@ -154,38 +161,31 @@ public class RefreshTokenManager { callBack.onLoginFailure(); } - isRefreshing = false; } }); } private void saveLoginToken(final LoginTokenEntity tokenEntity, final Context context) { - UserManager.getInstance().setLoginTokenEntity(tokenEntity); + LoginTokenEntity loginTokenEntity = UserManager.getInstance().getLoginTokenEntity(); + if (loginTokenEntity != null) { + tokenEntity.setLoginType(loginTokenEntity.getLoginType()); + tokenEntity.setId(loginTokenEntity.getId()); + } - SINGLE_EXECUTOR.execute(new Runnable() { - @Override - public void run() { - if (TextUtils.isEmpty(tokenEntity.getId())) return; - if (mDatabase.loginTokenDao().updateToken(tokenEntity) <= 0) { - mDatabase.loginTokenDao().addToken(tokenEntity); - } - PreferenceManager.getDefaultSharedPreferences(context).edit().putString(Constants.LOGIN_TOKEN_ID, tokenEntity.getId()).apply(); - } - }); + UserManager.getInstance().setLoginTokenEntity(tokenEntity); + if (TextUtils.isEmpty(tokenEntity.getId())) return; + if (mDatabase.loginTokenDao().updateToken(tokenEntity) <= 0) { + mDatabase.loginTokenDao().addToken(tokenEntity); + } + PreferenceManager.getDefaultSharedPreferences(context).edit().putString(Constants.LOGIN_TOKEN_ID, tokenEntity.getId()).apply(); } private void saveUserInfo(final UserInfoEntity userInfo) { UserManager.getInstance().setUserInfoEntity(userInfo); - - SINGLE_EXECUTOR.execute(new Runnable() { - @Override - public void run() { - if (TextUtils.isEmpty(userInfo.getId())) return; - if (mDatabase.userInfoDao().updateUserInfo(userInfo) <= 0) { - mDatabase.userInfoDao().addUserInfo(userInfo); - } - } - }); + if (TextUtils.isEmpty(userInfo.getId())) return; + if (mDatabase.userInfoDao().updateUserInfo(userInfo) <= 0) { + mDatabase.userInfoDao().addUserInfo(userInfo); + } } diff --git a/app/src/main/java/com/gh/gamecenter/mvvm/SingleLiveEvent.java b/app/src/main/java/com/gh/gamecenter/mvvm/SingleLiveEvent.java new file mode 100644 index 0000000000..f82c50c1cb --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/mvvm/SingleLiveEvent.java @@ -0,0 +1,59 @@ +package com.gh.gamecenter.mvvm; + +import android.arch.lifecycle.LifecycleOwner; +import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.Observer; +import android.support.annotation.MainThread; +import android.support.annotation.Nullable; +import android.util.Log; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A lifecycle-aware observable that sends only new updates after subscription, used for events like + * navigation and Snackbar messages. + *

+ * This avoids a common problem with events: on configuration change (like rotation) an update + * can be emitted if the observer is active. This LiveData only calls the observable if there's an + * explicit call to setValue() or call(). + *

+ * Note that only one observer is going to be notified of changes. + */ +public class SingleLiveEvent extends MutableLiveData { + + private static final String TAG = "SingleLiveEvent"; + + private final AtomicBoolean mPending = new AtomicBoolean(false); + + @MainThread + public void observe(LifecycleOwner owner, final Observer observer) { + + if (hasActiveObservers()) { + Log.w(TAG, "Multiple observers registered but only one will be notified of changes."); + } + + // Observe the internal MutableLiveData + super.observe(owner, new Observer() { + @Override + public void onChanged(@Nullable T t) { + if (mPending.compareAndSet(true, false)) { + observer.onChanged(t); + } + } + }); + } + + @MainThread + public void setValue(@Nullable T t) { + mPending.set(true); + super.setValue(t); + } + + /** + * Used for cases where T is Void, to make calls cleaner. + */ + @MainThread + public void call() { + setValue(null); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/retrofit/OkHttpRetryInterceptor.kt b/app/src/main/java/com/gh/gamecenter/retrofit/OkHttpRetryInterceptor.kt index 508eee8331..a78b54d62c 100644 --- a/app/src/main/java/com/gh/gamecenter/retrofit/OkHttpRetryInterceptor.kt +++ b/app/src/main/java/com/gh/gamecenter/retrofit/OkHttpRetryInterceptor.kt @@ -2,16 +2,12 @@ package com.gh.gamecenter.retrofit import android.content.Context import android.text.TextUtils -import com.gh.gamecenter.eventbus.EBReuse import com.gh.gamecenter.manager.RefreshTokenManager import com.gh.gamecenter.manager.UserManager -import com.gh.gamecenter.personal.PersonalFragment import com.lightgame.config.CommonDebug -import com.lightgame.utils.Utils import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response -import org.greenrobot.eventbus.EventBus import java.io.IOException @@ -49,36 +45,25 @@ class OkHttpRetryInterceptor internal constructor(context: Context) : Intercepto CommonDebug.logMethodWithParams(this, "Retrying ${request.url()} for $tryCount") } - if (url.contains("user/info") && !TextUtils.isEmpty(request.header("Content-Type")) + if (url.contains("users:validate") && !TextUtils.isEmpty(request.header("retry")) || url.contains("/logout")) { // 过滤在注销/当前类请求的 userinfo tryCount = 4 break } - val loginToken = UserManager.getInstance().loginTokenEntity - if (response != null && token != null && loginToken != null) { // TOKEN 过期处理 - val refreshToken = loginToken.refreshToken - val refreshExpire = refreshToken?.expire - if (refreshExpire != null && refreshExpire > Utils.getTime(mContext)) { - val value = refreshToken.value ?: break + if (response != null && token != null) { // TOKEN 过期处理 + RefreshTokenManager.getInstance(mContext).refreshToken(token, object : RefreshTokenManager.refreshCallBack { + override fun onLogin() { + val newBuilder = request.newBuilder() + newBuilder.header("TOKEN", UserManager.getInstance().token) + request = newBuilder.build() + response = doRequest(chain, request) + } - RefreshTokenManager.getInstance(mContext).refreshToken(value, object : RefreshTokenManager.refreshCallBack { - override fun onLogin() { - val newBuilder = request.newBuilder() - newBuilder.header("TOKEN", UserManager.getInstance().token) - request = newBuilder.build() - response = doRequest(chain, request) - } - - override fun onLoginFailure() { - tryCount = 4 // 只要token刷新异常直接主动退出登录 - } - }) - } else { - // 重新登录 - Utils.toast(mContext, "账号过期,请重新登录!") - EventBus.getDefault().post(EBReuse(PersonalFragment.LOGOUT_TAG)) - } + override fun onLoginFailure() { + tryCount = 4 // 只要token刷新异常直接主动退出登录 + } + }) } else { // 网络错误处理 response = doRequest(chain, request) } diff --git a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java index 2ccf013d00..dc66248846 100644 --- a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java +++ b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java @@ -383,9 +383,9 @@ public interface ApiService { @POST Observable getUserInfo(@Url String url); - @Headers({"Content-Type: application/json"}) // Headers(Content-Type) 作为区分 userinfo 的唯一标识 + // Header作为区分 userinfo 的唯一标识 @POST() - Observable getRetryUserInfo(@Url String url); // 在OkHttpRetryInterceptor使用主要是不允许这个接口重试 + Observable getRetryUserInfo(@Url String url, @Header("retry") String tag); // 在OkHttpRetryInterceptor使用主要是不允许这个接口重试 // /** // * 检查手机号码是否可用 diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml index c5a00ec124..fea16636fe 100644 --- a/app/src/main/res/layout/activity_search.xml +++ b/app/src/main/res/layout/activity_search.xml @@ -3,7 +3,7 @@ android:layout_height = "match_parent" android:orientation = "vertical" > - + - +