web-dev-qa-db-ja.com

再作成されたアクティビティにRetrofitコールバックを実装するベストプラクティスですか?

私はレトロフィットに切り替えて、非同期コールバックでそれを使用するための適切なアーキテクチャを理解しようとしています。

たとえば、インターフェイスがあります:

interface RESTService{
    @GET("/api/getusername")
    void getUserName(@Query("user_id") String userId, 
                     Callback<Response> callback);
}

そして、私はこれをメインアクティビティから実行します:

RestAdapter restAdapter = new RestAdapter.Builder()
        .setServer("WEBSITE_URL")     
        .build();
RESTService api = restAdapter.create(RESTService.class);
api.getUserName(userId, new Callback<Response> {...});

次に、ユーザーがデバイスを回転させ、新しくアクティビティを作成しました...ここで何が起きましたか?新しいアクティビティへの応答を取得するにはどうすればよいですか(バックグラウンドでのapi呼び出しは最初のアクティビティの寿命よりも長く実行されると思います)。たぶん、コールバックの静的インスタンスを使用する必要がありますか?正しい方法を教えてください...

72
lordmegamax

otto を使用します。オットーとレトロフィットを組み合わせるサンプルはたくさんあります。たとえば、 https://github.com/pat-dalberg/ImageNom/blob/master/src/com/dalberg/Android/imagenom/async/FlickrClient。 Java

または、この投稿を読む http://www.mdswanson.com/blog/2014/04/07/durable-Android-rest-clients.html ほとんどすべての質問に答えます

42
avgx

潜在的に長時間実行されるサーバー呼び出しには、 AsyncTaskLoader を使用します。私にとって、ローダーの主な利点は、アクティビティライフサイクルの処理です。 onLoadFinishedは、アクティビティがユーザーに表示される場合にのみ呼び出されます。ローダーは、アクティビティ/フラグメントと向きの変更間でも共有されます。

そこで、loadInBackgroundでレトロフィット同期呼び出しを使用するApiLoaderを作成しました。

abstract public class ApiLoader<Type> extends AsyncTaskLoader<ApiResponse<Type>> {

    protected ApiService service;
    protected ApiResponse<Type> response;

    public ApiLoader(Context context) {
        super(context);
        Vibes app = (Vibes) context.getApplicationContext();
        service = app.getApiService();
    }

    @Override
    public ApiResponse<Type> loadInBackground() {
        ApiResponse<Type> localResponse = new ApiResponse<Type>();

        try {
            localResponse.setResult(callServerInBackground(service));
        } catch(Exception e) {
            localResponse.setError(e);
        }

        response = localResponse;
        return response;
    }

    @Override
    protected void onStartLoading() {
        super.onStartLoading();
        if(response != null) {
            deliverResult(response);
        }

        if(takeContentChanged() || response == null) {
            forceLoad();
        }
    }

    @Override
    protected void onReset() {
        super.onReset();
        response = null;
    }


    abstract protected Type callServerInBackground(SecondLevelApiService api) throws Exception;

}

アクティビティでは、このローダーを次のように初期化します。

getSupportLoaderManager().initLoader(1, null, new LoaderManager.LoaderCallbacks<ApiResponse<DAO>>() {
        @Override
        public Loader<ApiResponse<DAO>> onCreateLoader(int id, Bundle args) {
            spbProgress.setVisibility(View.VISIBLE);

            return new ApiLoader<DAO>(getApplicationContext()) {
                @Override
                protected DAO callServerInBackground(ApiService api) throws Exception {
                    return api.requestDAO();
                }
            };
        }

        @Override
        public void onLoadFinished(Loader<ApiResponse<DAO>> loader, ApiResponse<DAO> data) {
            if (!data.hasError()) {
                DAO dao = data.getResult();
                //handle data
            } else {
                Exception error = data.getError();
                //handle error
            }
        }

        @Override
        public void onLoaderReset(Loader<ApiResponse<DAO>> loader) {}
    });

データを複数回要求する場合は、initLoaderの代わりにrestartLoaderを使用します。

34
Benjamin

私はAndroidアプリで一種のMVP(ModelViewPresenter)実装を使用しています。レトロフィットリクエストについては、それぞれのプレゼンターのアクティビティコールを作成し、それがレトロフィットリクエストを作成し、パラメータとしてカスタムリスナーがアタッチされたコールバックを送信します(プレゼンターによって実装されます)。コールバックがonSuccessまたはonFailureメソッドに到達したら、リスナーのそれぞれのメソッドを呼び出します。これらのメソッドは、PresenterメソッドとActivityメソッドを呼び出します:P

これで、画面がオンになった場合、アクティビティが再作成されると、プレゼンターにアタッチされます。これは、プレゼンターのインスタンスを保持するAndroidのアプリケーションのカスタム実装を使用して行われ、アクティビティのクラスに従って正しいプレゼンターを回復するためのマップを使用します。

それが最良の方法であるかどうかわからない、おそらく@pareshgoelの答えが優れていますが、それは私のために働いています:D

例:

public abstract interface RequestListener<T> {

    void onSuccess(T response);

    void onFailure(RetrofitError error);
}

...

public class RequestCallback<T> implements Callback<T> {

    protected RequestListener<T> listener;

    public RequestCallback(RequestListener<T> listener){
        this.listener = listener;
    }

    @Override
    public void failure(RetrofitError arg0){
        this.listener.onFailure(arg0);
    }

    @Override
    public void success(T arg0, Response arg1){
        this.listener.onSuccess(arg0);
    }

}

リスナーをプレゼンターのどこかに実装し、オーバーライドしたメソッドで、Activityを呼び出すプレゼンターのメソッドを呼び出します。そして、プレゼンター上のすべての場所を初期化するには、Pを呼び出します。

Request rsqt = restAdapter.create(Request.class);
rsqt.get(new RequestCallback<YourExpectedObject>(listener));

お役に立てば幸いです。

21
LeoFarage

まず、この行:api.getUserName(userId、new Callback {...})がMainActivityへの強力な参照を保持する匿名のCallbackクラスを作成するため、アクティビティがここでリークします。 Callbackが呼び出される前にデバイスがローテーションされると、MainActivityはガベージコレクションされません。 Callback.call()で行う内容によっては、アプリで未定義の動作が発生する場合があります。

このようなシナリオを処理する一般的な考え方は次のとおりです。

  1. 静的でない内部クラス(または問題で言及されている匿名クラス)を作成しないでください。
  2. 代わりに、アクティビティ/フラグメントへのWeakReference <>を保持する静的クラスを作成します。

上記はリークを防ぐだけです。それでも、アクティビティにRetrofitコールを返すのに役立ちません。

ここで、構成の変更後でも結果をコンポーネント(この場合はアクティビティ)に戻すには、アクティビティにアタッチされたヘッドレスの保持フラグメントを使用して、Retrofitを呼び出します。 Retained fragmentについて詳しくはこちらをご覧ください- http://developer.Android.com/reference/Android/app/Fragment.html#setRetainInstance(boolean)

一般的な考え方は、フラグメントが構成変更時にアクティビティに自動的にアタッチされるということです。

10
pareshgoel

Google I/Oでこのビデオを見る を強くお勧めします。

RESTリクエストをサービス(ほとんど殺されない)に委任して作成する方法について説明します。リクエストが完了すると、すぐにAndroidの組み込みデータベースに保存されるため、アクティビティの準備が整うとすぐにデータが利用可能になります。

このアプローチを使用すると、アクティビティのライフサイクルを心配する必要がなくなり、リクエストがはるかに分離された方法で処理されます。

ビデオでは特にレトロフィットについて説明していませんが、このパラダイムにレトロフィットを簡単に適合させることができます。

5
Martin Konecny

Retrofit2を使用して方向の変更を処理します。私は就職の面接でこれを尋ねられ、その時にそれを知らなかったために拒否されましたが、ここでは今です。

public class TestActivity extends AppCompatActivity {
Call<Object> mCall;
@Override
    public void onDestroy() {
        super.onDestroy();
        if (mCall != null) {
            if (mCall.isExecuted()) {
                //An attempt will be made to cancel in-flight calls, and
                // if the call has not yet been executed it never will be.
                mCall.cancel();
            }
        }
    }
    }
3
John61590

私が改装の初心者だったとき、これらのサイトから最良の方法を学びました

レトロフィットでAPIを使用

レトロフィットチュートリアル

2
Ameen Maheen

Robospice を使用します

データを必要とするアプリ内のすべてのコンポーネントは、spiceサービスに登録します。サービスは、リクエストをサーバーに送信します(必要に応じて後付けを使用)。応答が戻ると、登録したすべてのコンポーネントが通知されます。それらのいずれかがもう使用できない場合(ローテーションのためにキックされたアクティビティなど)、通知されません。

利点:デバイスを回転したり、新しいダイアログ/フラグメントを開いたりするかどうかに関係なく、失われない単一の要求...

2
stoefln