2

目前我正在使用异步 http 库对我们的服务器执行 http 请求。然而,这带来了一个问题,如果在屏幕旋转期间正在进行 http 调用,我们将在调用完成时引用旧上下文。我通过保持对 onCreate 中捕获的最新实例的静态引用来解决这个问题,并使用该引用调用方法(并在 onDestroy 中将其设为空)。它工作正常,但看起来像一个黑客。我见过有人推荐使用片段来处理这个问题,比如这里:

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

这似乎是个好主意,但我想我可以通过简单地让我的 Activity 扩展 FragmentActivity 并使用专门用于我正在做的事情的 AsyncTaskLoader 子类来实现这一点。

这是我的想法:实现一个带有 ApiRequest 并返回 ApiResponse 的 AsyncTaskLoader。但是,我希望能够继承 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 作为我的通用参数,然后将 onLoadFinished 中返回的 ApiResponse 对象转换为我期望的特定基类,但我宁愿以更类型安全的方式执行此操作。此外,我对完成基本相同但以另一种方式完成的想法持开放态度。

4

2 回答 2

8

好的,这就是我想出的,它实际上似乎工作得很好,并且可以在后台工作期间处理屏幕方向的变化。这是我更新的 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),否则不会调用 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;
    }
}

这是一个使用这个加载器的 Activity:

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

    }
}

请注意,虽然此加载器在用户按下登录按钮之前不会启动,但您必须使用 onCreate 中的 initLoader 重新连接到加载器,以防它已经在进行中,否则当您翻转方向时,您将不会收到任务完成的通知.

有趣的是,这似乎工作得很好,并且不需要使用 TaskFragment。我还没有真正看到其他人为 http 的东西这样做,所以也许有一些缺点,但它似乎工作得很好。

于 2013-11-03T07:27:35.930 回答
0

您对尝试实现专门解决此类问题的库不感兴趣吗?例如,你有谷歌和 Robospice 的 Volley。

http://arnab.ch/blog/2013/08/asynchronous-http-requests-in-android-using-volley/

https://github.com/octo-online/robospice

于 2013-11-04T06:20:35.490 回答