AsyncTaskLoader для HTTP-запросов для обработки изменений ориентации, с использованием дженериков, наследования

В настоящее время я использую асинхронную http-библиотеку для выполнения HTTP-запросов на нашем сервере. Однако это связано с тем, что, если во время вращения экрана выполняется HTTP-вызов, мы будем иметь ссылку на старый контекст, когда вызов завершится. Я как бы обошел это, сохраняя статическую ссылку на последний экземпляр, захваченный в onCreate, и будет вызывать методы с этой ссылкой (и обнулить его в onDestroy). Он работал нормально, но выглядел как хак. Я видел, как некоторые люди рекомендуют использовать фрагменты, чтобы справиться с этим, например:

http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html

которые кажутся хорошими идеями, но я думал, что смогу это сделать, просто включив функцию Activity FragmentActivity и используя подкласс AsyncTaskLoader, который специально предназначен для того, что я делаю.

Вот моя идея: Внедрение AsyncTaskLoader с использованием ApiRequest и возвращает ApiResponse. Однако я хочу иметь возможность подкласса HttpAsyncTask и переопределить метод, который анализирует ответ, чтобы я мог проанализировать ответ и превратить его в объект другого типа, который расширяет ApiResponse. Я не уверен, как указать аргументы типа для этого.

Вот мой код:

public class HttpAsyncTaskLoader</*not sure what to put here*/> extends AsyncTaskLoader<? not sure ?> {
    private ApiClient mClient ;
    private ApiRequest mRequest;
    private volatile boolean isExecuting = false;
    public HttpAsyncTaskLoader(Context context, ApiClient client, ApiRequest request) {
        super(context);
        mClient = client;
        mRequest = request;
    }

    /**
     * Subclasses should override this method to do additional parsing
     * @param response
     * @return
     */
    protected /*subclass of ApiResponse (or ApiResponse itself)*/ onResponse(ApiResponse response) 
    {
        //base implementation just returns the value, subclasses would 
        //do additional processing and turn it into some base class of ApiResponse  
        return response; 
    }

    @Override
    public /** not sure ***/ loadInBackground() {
        HttpResponse response = null;
        ResponseError error = null;
        JSONObject responseJson = null;
        ApiResponse apiResponse = null;
        try {
            isExecuting = true;
            //synchronous call
            response  =  mClient.execute(mRequest);
            isExecuting = false;
            responseJson = new JSONObject(EntityUtils.toString(response.getEntity()));
        } catch (IOException e) {
            error = new ResponseError(e);
        } catch (URISyntaxException e) {
            error = new ResponseError(e);
        } catch (JSONException e) {
            error = new ResponseError(e);
        } finally {
            mClient.getConnectionManager().closeExpiredConnections();
            isExecuting = false;
            apiResponse = new ApiResponse(getContext().getResources(), response, responseJson, error);
        }
        return onResponse(apiResponse);
    }

    @Override
    public void onCanceled(ApiResponse response) {
        if (isExecuting) {
            mClient.getConnectionManager().shutdown();
        }
    }

}

У кого-нибудь есть идея, как я могу это сделать? Я не уверен, как указать параметры типа? Я хочу, чтобы этот класс можно было использовать, как мы надеемся, и также иметь возможность подклассифицировать его. Дело в том, что я не хочу повторно реализовывать функциональность в методе loadInBackground выше. Я уверен, что могу просто использовать ApiResponse в качестве моего общего параметра, а затем применить объекты ApiResponse, возвращенные в onLoadFinished, к определенному базовому классу, который я ожидаю, но я предпочел бы сделать это более безопасным типом. Также я открыт для идей, которые выполняют по существу одно и то же, но по-другому.

Ответ 1

Хорошо, это то, что я придумал, с которым на самом деле работает очень хорошо, и обрабатывает изменения ориентации экрана во время фоновой работы. Вот мой обновленный HttpAsyncTaskLoader.

public class HttpAsyncTaskLoader<T extends ApiResponse> extends AsyncTaskLoader {
    private ApiClient mClient ;
    protected ApiRequest mRequest;
    private ApiResponse mResponse;
    private volatile boolean isExecuting = false;
    public HttpAsyncTaskLoader(Context context, ApiClient client, ApiRequest request) {
        super(context);
        mClient = client;
        mRequest = request;
    }

    /** Subclasses should call this from loadInBackground   */
    protected ApiResponse executeRequest(ApiRequest request) {
        HttpResponse response = null;
        ResponseError error = null;
        JSONObject responseJson = null;
        try {
            isExecuting = true;
            Log.d(TAG, "executing api");
            response  =  mClient.execute(request);
            Log.d(TAG, "got a response");
            isExecuting = false;
            responseJson = new JSONObject(EntityUtils.toString(response.getEntity()));
            Log.d(TAG, "parsed response to json");
        } catch (IOException e) {
            error = new ResponseError(e);
        } catch (URISyntaxException e) {
            error = new ResponseError(e);
        } catch (JSONException e) {
            error = new ResponseError(e);
        } finally {
            mClient.getConnectionManager().closeExpiredConnections();
            isExecuting = false;
            mResponse = new ApiResponse(getContext().getResources(), response, responseJson, error);
        }
        return mResponse;
    }

    protected void onStartLoading() {
        super.onStartLoading();
        if (takeContentChanged() ||  mResponse == null) {
            forceLoad();
        }
        if (getResponse() != null) {
            deliverResult(getResponse());
        }
    }

    /** 
    * Subclasses should also override this so the correct object 
    * gets delivered in all cases (see onStartLoading above)
    */
    public ApiResponse getResponse() {
        return mResponse;
    }

    @Override
    public void onCanceled(Object data) {
        super.onCanceled(data);
        if (isExecuting) {
            mClient.getConnectionManager().shutdown();
        }
    }

    @Override
    public ApiResponse loadInBackground() {
        return executeRequest(mRequest);
    }
}

Обратите внимание, что в приведенном выше примере метод onCanceled принимает объект. Я получил ошибки компиляции, если попытался использовать ApiResponse. как тип. Кроме того, вы должны реализовать onStartLoading, как я сделал выше (вызов forceLoad, если объект result равен null), иначе loadInBackground не получит вызов

Затем приведен пример подкласса HttpAsyncTaskLoader:

public class LoginAsyncTaskLoader extends HttpAsyncTaskLoader {
    private LoginResponse mLoginResponse;
    public LoginAsyncTaskLoader(Context context, ApiClient client, ApiRequest request) {
        super(context, client, request);
    }

    @Override
    public LoginResponse loadInBackground() {
        ApiResponse apiResponse = executeRequest(mRequest);
        mLoginResponse = new LoginResponse(apiResponse.getResources(), apiResponse.response, apiResponse.responseJson, apiResponse.getError());
        return mLoginResponse;
    }

    @Override
    public ApiResponse getResponse() {
        return mLoginResponse;
    }
}

Вот активность, которая использует этот загрузчик:

public class LoginActivity extends FragmentActivity implements LoaderManager.LoaderCallbacks<LoginResponse> {

    private String username,password;       
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.login);
        Loader loader = getSupportLoaderManager().getLoader(0);
        if (loader != null) {
            getSupportLoaderManager().initLoader(0, null, this);
        }
    }

    public void loginSubmit(View button) {
            Bundle data = new Bundle();
            data.putString("username", getUsername());
            data.putString("password", getPassword());  
            getSupportLoaderManager().restartLoader(0, data, this);
    }   


    @Override
    public Loader<LoginResponse> onCreateLoader(int i, Bundle bundle) {
    //might want to start a progress bar
        ApiClient client = new ApiClient();
        LoginApi loginApi = new LoginApi(bundle.getString("username"), bundle.getString("password"));
        return new LoginAsyncTaskLoader(this, apiClient, loginApi);
    }


    @Override
    public void onLoadFinished(Loader<LoginResponse> loginLoader,
                               LoginResponse loginResponse)
    {
        //handle result, maybe send to a new activity if response doesn't have an error

    }

    @Override
    public void onLoaderReset(Loader<LoginResponse> responseAndJsonHolderLoader)
    {
        //not sure if anything needs to be done here to do

    }
}

Обратите внимание, что пока этот загрузчик не запускается до тех пор, пока пользователь не нажмет кнопку входа в систему, вы должны подключиться к загрузчику с помощью initLoader в onCreate в случае, если он уже выполняется, иначе, когда вы перевернете ориентацию, вы не получите уведомление о том, что задача завершена.

Интересно, что это работает хорошо и не требует использования TaskFragment. Я действительно не видел, чтобы кто-то еще делал это для http-материала, поэтому, возможно, есть некоторые стороны, но, похоже, все работает нормально.