wake-up-neo.com

Retrofit: Umleitung zu LoginActivity, wenn der Antwortcode 401 lautet

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) {

            }
        });
15
Rick

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.

7
azizbekian

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. 

  1. Ereignismodell erstellen

    public class UnauthorizedEvent {
    
        private static final UnauthorizedEvent INSTANCE = new UnauthorizedEvent();
    
        public static UnauthorizedEvent instance() {
            return INSTANCE;
        }
    
        private UnauthorizedEvent() {
        }
    }
    
  2. 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;
        }
    }
    
  3. 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);
        }
    }
    
  4. 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.

  5. Registrieren Sie Ihren Interceptor

    OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(new UnauthorizedInterceptor())
            .build();
    

Pros:

  • Lose Kupplung zwischen Komponenten
  • Einfache Erweiterung der Logik durch Überschreiben von handleUnauthorizedEvent
  • Code muss nicht umgeschrieben werden, um neue Arten von Rückrufen zu verwenden
  • Reduzieren Sie den menschlichen Faktor bei Fehlern (verwenden Sie Callback anstelle von BaseCallback)

Nachteile:

  • Das EventBus-Muster macht das Debuggen komplizierter
  • Eine weitere Abhängigkeit oder eigene Implementierung, die dem Projekt neuen Code hinzufügt

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.

2
hluhovskyi

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.

1
SANAT

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
    }
1
Scrobot