简体   繁体   English

AsyncTaskLoader用于处理方向更改的http请求,使用泛型,继承

[英]AsyncTaskLoader for http requests to handle orientation changes, using generics, inheritance

Currently I am using an asynchronous http library to execute http requests against our server. 目前我正在使用异步http库来对我们的服务器执行http请求。 However this carries the problem where if an http call is in progress during a screen rotation we will have a reference to the old context when the call finishes. 然而,这带来了一个问题,即如果在屏幕旋转期间正在进行http调用,我们将在调用结束时引用旧的上下文。 I sort of got around this by keeping a static reference to the latest instance captured in onCreate and would call methods with that reference (and null it out in onDestroy). 我通过保持对onCreate中捕获的最新实例的静态引用来解决这个问题,并使用该引用调用方法(并在onDestroy中将其置空)。 It worked ok but seemed like a hack. 它运作正常但似乎是一个黑客。 I've seen some people recommend the use of fragments to deal with this, like here: 我见过有人推荐使用片段来解决这个问题,比如这里:

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

which seem like good ideas but I was thinking I could accomplish this by simply having my Activity extend FragmentActivity and use an AsyncTaskLoader subclass which is specifically purposed for what I'm doing. 这似乎是好主意,但我想我可以通过简单地让我的Activity扩展FragmentActivity并使用AsyncTaskLoader子类来实现这一点,该子类专门用于我正在做的事情。

Here is my Idea: Implement an AsyncTaskLoader with takes an ApiRequest and returns an ApiResponse. 这是我的想法:使用ApiRequest实现AsyncTaskLoader并返回ApiResponse。 However I want to be able to subclass HttpAsyncTask and override a method that parses the response so I can parse the response and turn it into another kind of object which extends ApiResponse. 但是,我希望能够继承HttpAsyncTask并覆盖一个解析响应的方法,这样我就可以解析响应并将其转换为另一种扩展ApiResponse的对象。 I'm not sure how to specify the type arguments to achieve this though. 我不知道如何指定类型参数来实现这一点。

Here is my code: 这是我的代码:

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();
        }
    }

}

Anyone have an idea how I can accomplish this? 任何人都知道如何才能做到这一点? I'm not sure how to specify the type parameters? 我不确定如何指定类型参数? I want this class to be usable as-is hopefully and also to be able to subclass it. 我希望这个类可以正常使用,也可以将它子类化。 The point is that I don't want to re-implement the functionality in the loadInBackground method above. 关键是我不想在上面的loadInBackground方法中重新实现该功能。 I'm sure I could just use ApiResponse as my generic parameter and then cast the ApiResponse objects returned in onLoadFinished to the specific base class that I'm expecting but I'd rather do this in a more type-safe manner. 我确信我可以使用ApiResponse作为我的通用参数,然后将onLoadFinished中返回的ApiResponse对象转换为我期望的特定基类,但我宁愿以更加类型安全的方式执行此操作。 Also I'm open to ideas that accomplish essentially the same thing but in another way. 此外,我对那些基本上以同样的方式完成同样的事情的想法持开放态度。

Ok this is what I came up with which actually seems to work pretty good and handles screen orientation changes during the background work. 好吧,这就是我提出的实际上看起来非常好并且在后台工作期间处理屏幕方向更改的内容。 Here is my updated HttpAsyncTaskLoader. 这是我更新的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);
    }
}

Note that in the above example the onCanceled method takes an Object. 请注意,在上面的示例中,onCanceled方法采用Object。 I got compile errors if I attempted to use ApiResponse. 如果我试图使用ApiResponse,我会遇到编译错误。 as the type. 作为类型。 Also, you must implement onStartLoading like I did above (calling forceLoad if the result object is null) or else loadInBackground won't get called 此外,您必须像我上面所做的那样实现onStartLoading(如果结果对象为null则调用forceLoad),否则将不会调用loadInBackground

Then here is an example of a subclass of HttpAsyncTaskLoader: 那么这里是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;
    }
}

Here is an Activity that uses this loader: 这是一个使用此加载器的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

    }
}

Note that while this loader doesn't start until the user presses the Login button, You must reconnect to the loader using initLoader in onCreate in case it was already in progress, otherwise when you flip orientations you won't get notified that the task finished. 请注意,虽然此加载器在用户按下“登录”按钮之前不会启动,但您必须使用onCreate中的initLoader重新连接到加载程序,以防它正在进行中,否则当您翻转方向时,您将不会收到任务已完成的通知。

Interesting that this seems to work good and doesn't require using a TaskFragment. 有趣的是,这似乎工作得很好,不需要使用TaskFragment。 I haven't really seen anyone else do this for http stuff so maybe there are some down sides but it seems to work just fine. 我还没有真正看到其他任何人为http的东西这样做,所以也许有一些不好的方面,但似乎工作得很好。

Are you not interested in trying to implement libraries dedicated to this kind of problems? 您是否对尝试实施专门针对此类问题的库感兴趣? You Have Volley by Google and Robospice for example. 例如,谷歌和Robospice都有排球。

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM