재생성 된 활동에 Retrofit 콜백을 구현하는 모범 사례?
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 호출이 첫 번째 활동 수명보다 오래 실행될 것이라고 가정합니다). 콜백의 정적 인스턴스를 사용해야할까요? 올바른 길을 보여주세요 ...
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 거의 모든 질문에 대한 답변입니다.
잠재적 인 장기 실행 서버 호출의 경우 AsyncTaskLoader를 사용합니다 . 저에게 로더의 주요 장점은 활동 수명주기 처리입니다. onLoadFinished 는 활동이 사용자에게 표시되는 경우에만 호출됩니다. 로더는 활동 / 조각 및 방향 변경간에 공유됩니다.
그래서 내가 개조를 동기식 호출을 사용하는 APILoader에서 생성 loadInBackground을 .
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 를 사용 하십시오 .
I've been using a kind of MVP (ModelViewPresenter) implementation on my Android apps. For the Retrofit request I made the Activity calls it's respective Presenter, which in turn makes the Retrofit Request and as a parameter I send a Callback with a custom Listener attached to it (implemented by the presenter). When the Callback reach onSuccess
or onFailure
methods I call the Listener's respective methods, which calls the Presenter and then the Activity methods :P
Now in case the screen is turned, when my Activity is re-created it attaches itself to the Presenter. This is made using a custom implementation of Android's Application, where it keeps the presenters' instance, and using a map for recovering the correct presenter according to the Activity's class.
I dunno if it's the best way, perhaps @pareshgoel answer is better, but it has been working for me :D
Examples:
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);
}
}
Implement the listener somewhere on the presenter, and on the overrode methods call a presenter's method that will make the call to the Activity. And call wherever you want on the presenter to init everything :P
Request rsqt = restAdapter.create(Request.class);
rsqt.get(new RequestCallback<YourExpectedObject>(listener));
Hope it helps you.
Firstly, your activity leaks here because this line: api.getUserName(userId, new Callback {...}) creates an anonymous Callback class that holds a strong reference to you MainActivity. When the device is rotated before the Callback is called, then the MainActivity will not be garbage collected. Depending on what you do in the Callback.call(), your app may yield undefined behaviour.
The general idea to handle such scenarios is:
- Never create a non-static inner class (or an anonymous class as mentioned in the problem).
- Instead create a static class that holds a WeakReference<> to the Activity/Fragment.
The above just prevents Leaks. It still does not help you get the Retrofit call back to your Activity.
Now, to get the results back to your component (Activity in your case) even after configuration change, you may want to use a headless retained fragment attached to your Activity, which makes the call to Retrofit. Read more here about Retained fragment - http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)
The general idea is that the Fragment automatically attaches itself to the Activity on configuration change.
I highly recommend you watch this video given at Google I/O.
It talks about how to create REST requests by delegating them to a service (which is almost never killed). When the request is completed it is immediately stored into Android's built-in database so the data is immediately available when your Activity is ready.
With this approach, you never have to worry about the lifecycle of the activity and your requests are handled in a much more decoupled way.
The video doesn't specifically talk about retrofit, but you can easily adapt retrofit for this paradigm.
Use Robospice
All components in your app which require data, register with the spice service. The service takes care of sending your request to the server (via retrofit if you want). When the response comes back, all components which registered get notified. If there is one of them not available any more (like an activity which got kicked because of rotation), it's just not notified.
Benefit: One single request which does not get lost, no matter whether you rotate your device, open new dialogs/fragments etc...
Using Retrofit2 to handle orientation change. I was asked this in a job interview and was rejected for not knowing it at the time but here it is now.
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();
}
}
}
}
'developer tip' 카테고리의 다른 글
dict를 올바르게 하위 클래스로 만들고 __getitem__ 및 __setitem__을 재정의하는 방법 (0) | 2020.10.27 |
---|---|
확장 가능한 목록보기에서 모든 하위 항목 확장 (0) | 2020.10.27 |
grep을 통해 텍스트 파일에서 빈 줄 제거 (0) | 2020.10.26 |
엔티티 프레임 워크 지정된 메타 데이터 리소스를로드 할 수 없습니다. (0) | 2020.10.26 |
yaml에서 연관 배열 목록을 만드는 방법 (0) | 2020.10.26 |