Wie kann LoginActivity
vom Interceptor (Nicht-Aktivitätsklasse) gestartet werden? Ich habe den Code (Interceptor
) unten ausprobiert, aber für mich nicht funktioniert
Abfangjäger
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + auth_token_string)
.build();
Response response = chain.proceed(newRequest);
Log.d("MyApp", "Code : "+response.code());
if (response.code() == 401){
Intent intent = new Intent(SplashActivity.getContextOfApplication(), LoginActivity.class);
startActivity(intent);
finish(); //Not working
return response;
}
return chain.proceed(newRequest);
}
}).build();
Dies ist die aktuelle Lösung, die ich verwende. Gibt es eine bessere Lösung als diese? Diese Lösung muss bei jedem API-Aufruf wiederholt werden.
Hauptaktivität
call.enqueue(new Callback<Token>() {
@Override
public void onResponse(Call<Token> call, Response<Token> response) {
if(response.isSuccessful())
{
//success
}
else
{
Intent intent = new Intent(MainActivity.this.getApplicationContext(), LoginActivity.class);
startActivity(intent);
finish();
}
}
@Override
public void onFailure(Call<Token> call, Throwable t) {
}
});
Erwägen Sie die Einführung einer benutzerdefinierten Implementierung der retrofit2.Callback
-Schnittstelle, z. BaseCallback
:
public abstract class BaseCallback<T> implements Callback<T> {
private final Context context;
public BaseCallback(Context context) {
this.context = context;
}
@Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.code() == 401) {
// launch login activity using `this.context`
} else {
onSuccess(response.body());
}
}
@Override
public void onFailure(Call<T> call, Throwable t) {
}
abstract void onSuccess(T response);
}
Nun sollten Sie von der Anruferseite aus new Callback<Token>
mit new BaseCallback<Token>
ändern:
call.enqueue(new BaseCallback<Token>(context) {
@Override
void onSuccess(Token response) {
// do something with token
}
});
Obwohl dieser Ansatz Ihre folgende Aussage nicht erfüllt:
ich muss also nicht immer den gleichen Code für jeden api-Aufruf wiederholen
trotzdem kann ich keinen besseren Ansatz finden.
Persönlich möchte ich hier die Verwendung des Event Bus Patterns vorschlagen. Sie können die greenrobot-Implementierung oder was auch immer Sie möchten verwenden, da es sich eher um einen Architekturansatz als um eine konkrete Implementierung handelt.
Ereignismodell erstellen
public class UnauthorizedEvent {
private static final UnauthorizedEvent INSTANCE = new UnauthorizedEvent();
public static UnauthorizedEvent instance() {
return INSTANCE;
}
private UnauthorizedEvent() {
}
}
Implementieren Sie benutzerdefinierte Interceptor
, die Ereignisse über nicht autorisierte Anforderungen auslöst
class UnauthorizedInterceptor implements Interceptor {
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if (response.code() == 401) {
EventBus.getDefault().post(UnauthorizedEvent.instance());
}
return response;
}
}
BaseActivity
-Klasse erstellen, die UnauthorizedEvent
behandelt
public class BaseActivity extends Activity {
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
@Subscribe
public final void onUnauthorizedEvent(UnauthorizedEvent e) {
handleUnauthorizedEvent();
}
protected void handleUnauthorizedEvent() {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
}
Start von LoginActivity
von LoginActivity
verhindern
public class LoginActivty extends BaseActivity {
@Override
protected void handleUnauthorizedEvent() {
//Don't handle unauthorized event
}
}
Ein anderer Ansatz besteht darin, BaseActivity
hier nicht zu erweitern.
Registrieren Sie Ihren Interceptor
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new UnauthorizedInterceptor())
.build();
Pros:
handleUnauthorizedEvent
Callback
anstelle von BaseCallback
)Nachteile:
Beachten Sie auch, dass dieses Beispiel Multithreading-Probleme nicht behandelt. Es löst Ihr Problem bei der Bearbeitung nicht autorisierter Anfragen. Wenn also zwei Anfragen 401 empfangen, ist es möglich, dass 2 Instanzen von LoginActivity
gestartet werden.
Generalized Solution: Sie können es lösen, indem Sie die Fehlerbehandlung verallgemeinern. Sie können benutzerdefinierte CallAdapterFactory-Objekte für den Retrofit-Builder verwenden. Bitte beziehen Sie sich auf die folgenden Klassen:
RxErrorHandlingCallAdapterFactory:
public class RxErrorHandlingCallAdapterFactory extends CallAdapter.Factory {
private static Context mContext = null;
private final RxJava2CallAdapterFactory original;
private RxErrorHandlingCallAdapterFactory() {
original = RxJava2CallAdapterFactory.create();
}
public static CallAdapter.Factory create(Context context) {
mContext = context;
return new RxErrorHandlingCallAdapterFactory();
}
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
return new RxCallAdapterWrapper(retrofit, original.get(returnType, annotations, retrofit));
}
private static class RxCallAdapterWrapper<R> implements CallAdapter<R, Object> {
private final Retrofit retrofit;
private final CallAdapter<R,
Object> wrapped;
public RxCallAdapterWrapper(Retrofit retrofit, CallAdapter<R, Object> wrapped) {
this.retrofit = retrofit;
this.wrapped = wrapped;
}
@Override
public Type responseType() {
return wrapped.responseType();
}
@Override
public Object adapt(Call<R> call) {
Object result = wrapped.adapt(call);
if (result instanceof Single) {
return ((Single) result).onErrorResumeNext(new Function<Throwable, SingleSource>() {
@Override
public SingleSource apply(@NonNull Throwable throwable) throws Exception {
return Single.error(asRetrofitException(throwable));
}
});
}
if (result instanceof Observable) {
return ((Observable) result).onErrorResumeNext(new Function<Throwable, ObservableSource>() {
@Override
public ObservableSource apply(@NonNull Throwable throwable) throws Exception {
return Observable.error(asRetrofitException(throwable));
}
});
}
if (result instanceof Completable) {
return ((Completable) result).onErrorResumeNext(new Function<Throwable, CompletableSource>() {
@Override
public CompletableSource apply(@NonNull Throwable throwable) throws Exception {
return Completable.error(asRetrofitException(throwable));
}
});
}
return result;
}
private RetrofitException asRetrofitException(Throwable throwable) {
// We had non-200 http error
Log.v("log", "eror");
throwable.printStackTrace();
if (throwable instanceof HttpException) {
HttpException httpException = (HttpException) throwable;
final Response response = httpException.response();
//if ((mContext instanceof Activity)) {
String s = "Something went wrong."; //mContext.getString(R.string.something_went_wrong);
try {
s = new JSONObject(response.errorBody().string()).getString("message");
if (response.code() == 401) { // 401 Unauthorized
Intent intent = new Intent(mContext, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivity(intent);
}
} catch (JSONException | IOException e) {
e.printStackTrace();
}
return RetrofitException.unexpectedError(s, response, retrofit);
//showErrorDialog(mContext, response);
//}
// return RetrofitException.httpError(response.errorBody().toString(), response, retrofit);
}
// A network error happened
if (throwable instanceof IOException) {
return RetrofitException.networkError((IOException) throwable);
}
// We don't know what happened. We need to simply convert to an unknown error
return RetrofitException.unexpectedError(throwable);
}
}
}
RetrofitException:
public class RetrofitException extends RuntimeException {
private final String url;
private final Response response;
private final Kind kind;
private final Retrofit retrofit;
RetrofitException(String message, String url, Response response, Kind kind, Throwable exception, Retrofit retrofit) {
super(message, exception);
this.url = url;
this.response = response;
this.kind = kind;
this.retrofit = retrofit;
}
public static RetrofitException httpError(String url, Response response, Retrofit retrofit) {
String message = response.code() + " " + response.message();
return new RetrofitException(message, url, response, Kind.HTTP, null, retrofit);
}
public static RetrofitException networkError(IOException exception) {
return new RetrofitException(exception.getMessage(), null, null, Kind.NETWORK, exception, null);
}
public static RetrofitException unexpectedError(Throwable exception) {
return new RetrofitException(exception.getMessage(), null, null, Kind.UNEXPECTED, exception, null);
}
public static RetrofitException unexpectedError(String s, Response response, Retrofit retrofit) {
return new RetrofitException(s, null, null, Kind.UNEXPECTED, null, null);
}
/**
* The request URL which produced the error.
*/
public String getUrl() {
return url;
}
/**
* Response object containing status code, headers, body, etc.
*/
public Response getResponse() {
return response;
}
/**
* The event kind which triggered this error.
*/
public Kind getKind() {
return kind;
}
/**
* The Retrofit this request was executed on
*/
public Retrofit getRetrofit() {
return retrofit;
}
/**
* HTTP response body converted to specified {@code type}. {@code null} if there is no
* response.
*
* @throws IOException if unable to convert the body to the specified {@code type}.
*/
public <T> T getErrorBodyAs(Class<T> type) throws IOException {
if (response == null || response.errorBody() == null) {
return null;
}
Converter<ResponseBody, T> converter = retrofit.responseBodyConverter(type, new Annotation[0]);
return converter.convert(response.errorBody());
}
/**
* Identifies the event kind which triggered a {@link RetrofitException}.
*/
public enum Kind {
/**
* An {@link IOException} occurred while communicating to the server.
*/
NETWORK,
/**
* A non-200 HTTP status code was received from the server.
*/
HTTP,
/**
* An internal error occurred while attempting to execute a request. It is best practice to
* re-throw this exception so your application crashes.
*/
UNEXPECTED
}
}
Retrofit Builder:
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxErrorHandlingCallAdapterFactory.create(context))
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(API_URL)
.client(client)
.build();
Sie können 401
in RxErrorHandlingCallAdapterFactory
und andere Fehler durch Throwable
behandeln.
Am einfachsten ist es, den Aktivitätskontext in die Interceptor-Instanz einzufügen. Wenn Sie DI-Werkzeuge wie Dagger2 oder Toothpick verwenden, ist dies sehr einfach. Ich empfehle, Zahnstocher zu verwenden)
https://github.com/stephanenicolas/toothpick
Der meiste Code in der Nähe wird in kotlin sein, weil es mein Code für die Boilerplate ist. Die meinen, dass Sie Ihr Problem lösen müssen, schreibe ich in Java.
Die Lösung sieht so aus:
@Qualifier
annotation class BackendUrl
class ActivityModule(activity: BaseActivity): Module() {
init {
bind(Activity::class.Java).toInstance(activity)
}
}
class NetworkModule: Module() {
init {
bind(String::class.Java).withName(BackendUrl::class.Java).toInstance(Constants.URL)
bind(Gson::class.Java).toInstance(GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create())
bind(CacheHolder::class.Java).toProvider(CacheProvider::class.Java).singletonInScope()
bind(OkHttpClient::class.Java).toProvider(OkHttpProvider::class.Java).instancesInScope()
bind(BackendApi::class.Java).toProvider(BackendApiProvider::class.Java).instancesInScope()
bind(RedirectInterceptor::class.Java).to(RedirectInterceptor::class.Java)
}
}
Dann müssen Sie Providers für die Injektionsabhängigkeit erstellen
class BackendApiProvider @Inject constructor(
private val okHttpClient: OkHttpClient,
private val gson: Gson,
@BackendUrl private val serverPath: String
) : Provider<BackendApi> {
override fun get() =
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.baseUrl(serverPath)
.build()
.create(BackendApi::class.Java)
}
Und Ihr Ablenker:
public class RedirectInterceptor implements Interceptor {
private final Context context;
@Inject
public RedirectInterceptor(Activity context) {
this.context = context;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.build();
Response response = chain.proceed(newRequest);
Log.d("MyApp", "Code : "+response.code());
if (response.code() == 401){
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent);
((Activity) context).finish();
return response;
}
return chain.proceed(newRequest);
}
}
Oh ja. Für den Autorisierungsheader ist es besser, eine neue Instanz eines anderen Interceptors anzulegen
class HeaderInterceptor(private val token: String?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val newRequest = request.newBuilder()
Log.d(TAG, "token: $token")
if (token != null && token.isNotBlank()) {
newRequest.addHeader("Authorization", "Bearer $token")
}
return chain.proceed(newRequest.build())
}
companion object {
private val TAG = HeaderInterceptor::class.Java.toString()
}
}
Und dein OkhttpProvder
class OkHttpProvider @Inject constructor(cacheHolder: CacheHolder, prefs: IPreferences, redirectInterceptor: RedirectInterceptor) : Provider<OkHttpClient> {
private val client: OkHttpClient
init {
val builder = OkHttpClient.Builder()
builder
.addNetworkInterceptor(redirectInterceptor)
.addNetworkInterceptor(HeaderInterceptor(prefs.getAuthToken()))
.readTimeout(30, TimeUnit.SECONDS)
.cache(cacheHolder.okHttpCache)
client = builder.build()
}
override fun get() = client
}
Das ist alles! Jetzt müssen Sie nur noch Ihre Module in den richtigen Bereich platzieren.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.init_view)
Toothpick.openScopes("activity scope").apply {
installModules(ActivityModule([email protected]))
Toothpick.inject([email protected], this)
}
Toothpick.openScopes("activity scope", "network scope").apply {
installModules(NetworkModule())
}
// your activity code
}