Android App using Volley get race condition - android

I'm developing an Android Mobile Application and one of the most important functionality of the app itself is being able to talk with a third-party API Service.
The third party service, offering these API, wants a "beacon" to be included into every API request i made.
The "beacon" is a "long integer" and it must be unique and incremental for every request.
The problem is:
I'm firing a couple of these request and i do not know which of these requests will complete first so i'm running into a race condition: where the second request ends quickly before the first request invalidating the first request!
When a button is clicked the following action will be executed:
public void fireRequests(View view)
{
long first_beacon = System.nanoTime();
fireFirstRequest(view, first_beacon);
long second_beacon = System.nanoTime();
fireSecondRequest(view, second_beacon);
}
I'm using Volley in a proper way, setting up callback etc.. example here:
fireFirstRequest method:
public void fireFirstRequest(View view, long beacon)
{
final ThirdPartyLib api_lib = new ThirdPartyLib(getActivity());
api_lib.doOperationA(beacon, new ThirdPartyLib.MyOwnCallback()
{
#Override
public void update(JSONObject jsonObject)
{
try
{
JSONObject result = jsonObject.getJSONObject("response");
/* my code */
Log.d("doOperationA", result)
}
catch (JSONException e)
{
e.printStackTrace();
}
}
});
}
fireSecondRequest method:
public void fireSecondRequest(View view, long beacon)
{
final ThirdPartyLib api_lib = new ThirdPartyLib(getActivity());
api_lib.doOperationB(beacon, new ThirdPartyLib.MyOwnCallback()
{
#Override
public void update(JSONObject jsonObject)
{
try
{
JSONObject result = jsonObject.getJSONObject("response");
/* my code */
Log.d("doOperationB", result)
}
catch (JSONException e)
{
e.printStackTrace();
}
}
});
}
Here is the execution log:
03-12 14:26:56.252 18769-18769/it.example.app D/Volley: queued doOperationA
03-12 14:26:58.124 18769-18769/it.example.app D/Volley: queued doOperationB
03-12 14:26:59.433 18769-18769/it.example.app D/App: doOperationB: {
"error": false,
"payload": {
"foo": "bar"
}
}
03-12 14:27:04.181 18769-18769/it.example.app D/App: doOperationA: {
"error": true,
"errorMessage": "invalid beacon"
"payload": {}
}
The question is: what's the best way to keep track of beacon before firing an API request or to maintain a "execution order" separation even if we are talking of ASync request?
My rough solution is to call the fireSecondRequest() inside the callback of the fireFirstRequest() when i'm completely sure that first request is done.
I know, this is the best way to kill the awesome world of async requests.
modified action:
public void fireRequests(View view)
{
long first_beacon = System.nanoTime();
fireFirstRequest(view, first_beacon);
}
fireFirstRequest modified method with final View parameter:
public void fireFirstRequest(final View view, long beacon)
{
final ThirdPartyLib api_lib = new ThirdPartyLib(getActivity());
api_lib.doOperationA(beacon, new ThirdPartyLib.MyOwnCallback()
{
#Override
public void update(JSONObject jsonObject)
{
try
{
JSONObject result = jsonObject.getJSONObject("response");
/* my code */
Log.d("doOperationA", result)
/* fire second request */
// EDIT
fireSecondRequest(view, System.nanoTime());
}
catch (JSONException e)
{
e.printStackTrace();
}
}
});
}

You didn't add the part of your code which initiates the Volley RequestQueue, but I'm assuming you're creating the default way using:
RequestQueue requestQueue = Volley.newRequestQueue(context, stack);
When you do this, you get a request queue which allows for 4 concurrent requests by default. You can see this by looking at the constructor this method uses to create a request queue:
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
...
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
You can overcome this issue if instead of using the default method for creating a RequestQueue, you create your own RequestQueue with a thread pool size of 1. This way, there can be no 2 concurrent requests, and requests will be sent in the order they are dispatched.
The downside with this, of course, is that this can dramatically slow down your app. If all requests must wait until the previous request is finished, this creates a serious bottleneck in your app.
Perhaps consider using more than 1 request queue, and only use this special request queue for requests that rely on this special constraint.
Hope this helps.

Related

Unable to implement callback in Android

I am building an SDK.
The Scenario
From the app, the developer passes the JsonObject and URL in a method
and inside the SDK.
I add those values in SQLite database and start a JobScheduler.
The Jobscheduler takes the request at 0 indexes out of the database
executes it.
When I get the response, I delete that request from the database and now the request at 1 index comes to the 0 index and again I execute the same code where 0th index request is fired.
The Problem
When I get the response from a server inside the SDK, I need to send it to the developer using a callback.
I can take the callback as an argument when I take the JSON and URL from user, but I don't know how to proceed further because I cannot store it into the database
Suppose I have 5 requests in the database and scheduler executes it one by one, I don't know how to send the response back to the developer. I can not pass context in the jobscheduler. The only way to do that is I get the corresponding context for each row (request) in the database.
What I tried
I tried using a LocalBroadcastManager, but I can not create a generic class and get it's onreceive() method implemented, also the context passing was a problem
Tried using Realm as a database so that I can add Context type and use my model, but it is not working as Context is not supported.
Storing Activity name in the database with other details and then driving out the class from it and activity from class. And then typecasting the activity into my callback. but it throws ClassCastException as the class it tries to derive from the Name is on the developer's app and not in my SDK
..
The App's MainActivity Code
sdkClass.queueRequest(jo.toString(),"url1",12,"MainActivity");
sdkClass.queueRequest(jo2.toString(),"url2",13,"MainActivity");
sdkClass.queueRequest(jo.toString(),"url3",14,"MainActivity");
sdkClass.executeQueue();
The SDK class which adds and executes code
public void queueRequest(String payload, String URL, int requestId, String identifier){
RequestsDatabase database = new RequestsDatabase(context);
database.insertItem(TxnType,payload,url, String.valueOf(requestId), identifier);
}
public JobScheduler getQueueInstance(Context context){
if(jobScheduler==null){
jobScheduler = (JobScheduler)context.getSystemService(JOB_SCHEDULER_SERVICE);
}
return jobScheduler;
}
public void executeQueue(){
getQueueInstance(context);
ComponentName jobService = new ComponentName(context.getPackageName(), MyJobService.class.getName());
JobInfo jobInfo = new JobInfo.Builder(1,jobService).setPersisted(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY). build();
jobScheduler.schedule(jobInfo);
}
MyJobServiceCode
#Override
public boolean onStartJob(JobParameters params) {
// UtilityMethods.showToast(this,params.getExtras().getString("json"));
Toast.makeText(this,"test", Toast.LENGTH_SHORT).show();
database = RequestsDatabase.getInstance(this);
ALlRequests = database.getAllItemId();
executeCall();
return false;
}
public void executeCall(){
ALlRequests = database.getAllItemId();
if(ALlRequests.size()>0) {
requestModel = ALlRequests.get(0);
try {
String payload = getPayload(this, requestModel.getTxnType(), requestModel.getPayload());
SilverLineRequest req = new SilverLineRequest(this, requestModel.getUrl(), payload, Integer.parseInt(requestModel.getRequestId()), this);
req.executeToServer();
Toast.makeText(getApplicationContext(), "requested", Toast.LENGTH_LONG).show();
} catch (JSONException e) {
e.printStackTrace();
}
}
else{
Toast.makeText(this, "Queue empty", Toast.LENGTH_LONG).show();
}
}
#Override
public void onSuccess(String response, int requestId) {
if(requestId ==Integer.parseInt(requestModel.getRequestId())){
Toast.makeText(this,"responded", Toast.LENGTH_SHORT).show();
database.deleteItem(requestModel.getTxnType());
// HERE I WANT TO SEND THE USER RESPONSE LIKE
// listener.onTransactionSuccess(response)
// BUT I DON'T HAVE 'listener' SO IT SHOWS NULLPOINTER
SDKClass sl = new SDKClass(this);
sl.executeQueue();
}
}
Any help will be blessed right now.
You store requests in database, so it can has unique id (autoincrement).
Then you can store callbacks in memory with relation request id -> callback.
On response you can call it.
If you want, return id to developers. Then they can bind and unbind and get results when they need, even the response had got yesterday.
Take a look on TransferUtility from Amazon AWS SDK for example.

Anonymous Listener of volley request causing memory leak

I am using volley library for making web-services call. I made a general class for making all web services call and making service call from there and made anonymous listener for successful and error response.
But when I use leak canary it is showing memory leak related to context. Below is my snippet of code:
public void sendRequest(final int url, final Context context, final ResponseListener responseListener, final Map<String, String> params) {
StringRequest stringRequest;
if (isNetworkAvailable(context)) {
stringRequest = new StringRequest(methodType, actualURL + appendUrl, new Listener<String>() {
#Override
public void onResponse(String response) {
dismissProgressDialog(context);
try {
(responseListener).onResponse(url, response);
} catch (JsonSyntaxException e) {
// Util.showToast(context, context.getResources().getString(R.string.error));
Crashlytics.logException(e);
}
}
}, new ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
// Util.showToast(context,context.getString(R.string.error));
dismissProgressDialog(context);
if (error instanceof NetworkError) {
Util.showToast(context, context.getResources().getString(R.string.network_error));
} else if (error instanceof NoConnectionError) {
Util.showToast(context, context.getResources().getString(R.string.server_error));
} else if (error instanceof TimeoutError) {
Util.showToast(context, context.getResources().getString(R.string.timeout_error));
} else {
Util.showToast(context, context.getResources().getString(R.string.default_error));
}
}
}) {
#Override
protected Map<String, String> getParams() throws AuthFailureError {
return params;
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
return request.getHeaders(context, actualURL, false);
}
};
stringRequest.setRetryPolicy(new DefaultRetryPolicy(30000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
VolleySingleton.getInstance(context).addRequest(stringRequest);
} else {
Util.showToast(context, context.getString(R.string.internet_error_message));
}
}
And I created an interface named response listener for redirecting responses to activity or fragment. I made request as follows.
Request.getRequest().sendRequest(Request.SOME URL, SplashScreenActivity.this, SplashScreenActivity.this, new HashMap<String, String>());
But I am facing memory leak as:
In 2.1.1:31.
* activity.SplashScreenActivity has leaked:
* GC ROOT com.android.volley.NetworkDispatcher.<Java Local>
* references network.Request$5.mListener (anonymous subclass of com.android.volley.toolbox.StringRequest)
* references network.Request$3.val$responseListener (anonymous implementation of com.android.volley.Response$Listener)
* leaks activity.SplashScreenActivity instance
* Retaining: 1.2MB.
* Reference Key: b8e318ea-448c-454d-9698-6f2d1afede1e
* Device: samsung samsung SM-G355H kanas3gxx
* Android Version: 4.4.2 API: 19 LeakCanary: 1.4 6b04880
* Durations: watch=5052ms, gc=449ms, heap dump=2617ms, analysis=143058ms
Any idea to remove this leak any help is appreciated.
Generally, Anonymous classes have a strong reference to the enclosing class instance. In your case, that would be SplashScreenActivity. Now I guess, your Activity is finished before you get the response from your server through Volley. Since the listener has a strong reference to enclosing Activity, that Activity cannot be garbage collected until the Anonymous class is finished. What you should do is tag all the requests you are sending with the Activity instance, and cancel all the requests at onDestroy() callback of Activity.
stringRequest.setTag(activityInstance);
To cancel all pending requests:
requestQueue.cancellAll(activityInstance);
Also, use Application context inside VolleySingleton to create the RequestQueue.
mRequestQueue = Volley.newRequestQueue(applicationContext);
Don't use your Activity context there and don't cache your Activity instance inside VolleySingleton.
Basically the anonymous approach is terrible in Android or in any ClientSideSystem where you don't have massive memory. What is happening is, you have passed Context as parameter in method and anonymous holds a reference of it. The real mess comes now in the scene when the thread inside which makes network call could not finish it's job and before that the calling activity for some reason either destroys or recycles in that case GC is not able to collect the activity as wokerThread might still be holding reference onto it. Please go through this for detail description.
The solution could be either static inner classes or independent classes, in both cases use WeakReference to hold resources and do a null check before using them.
Advantage of WeakReference is it will allow GC to collect the object if no-one else if holding reference onto it.
I had a similar problem detected with LeakCanary where Volley's mListener was referencing my response listener, and my listener was referencing an ImageView, so it could update it with the downloaded image.
I made my response listener an inner class within the activity ..
private class MyVolleyResponseListener <T> implements com.android.volley.Response.Listener <Bitmap> {
#Override
public void onResponse(Bitmap bitmap) {
thumbNailView.setImageBitmap(bitmap);
}
}
.. and stopped and started the volley request queue inside onDestroy() in the activity ..
requestQueue.stop();
requestQueue.start();
This has fixed the leak.
I know I m a bit late to join the party, but few days back this problem did spoil my weekend. In order to figure out, I went on to research a bit which finally got the solution.
The issue lies in the last request object getting leaked in Network Dispatcher & Cache Dispatcher.
#Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
mCache.initialize();
Request<?> request;
while (true) {
// release previous request object to avoid leaking request object when mQueue is drained.
request = null;
try {
// Take a request from the queue.
request = mCacheQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
final Request<?> finalRequest = request;
mDelivery.postResponse(request, response, new Runnable() {
#Override
public void run() {
try {
mNetworkQueue.put(finalRequest);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
}
}
As you can see a new request object is created before it takes from the queue. This overcomes the problem of memory leak.
P.S: Don't use Volley from the Google repository as it is deprecated and has this bug since then. In order to use Volley, go for this :
https://github.com/mcxiaoke/android-volley
The above repository is free from any memory leaks whatsoever. Ciao.

Login function with ASync networking request?

I'm using 'Retrofit' for making asynchronous network requests, how might i right a function for handling logins? For instance i've currently attempted:
public UserAuthResponse Login(String username, String password) {
try {
Callback<UserAuthResponse> getAuthCallback = new Callback<UserAuthResponse>() {
#Override
public void failure(RetrofitError arg0) {
if (arg0 != null) {
if (arg0.getMessage() != null
&& arg0.getMessage().length() > 0) {
Log.e("KFF-Retrofit", arg0.getMessage());
}
}
}
#Override
public void success(UserAuthResponse listItem,
retrofit.client.Response arg1) {
Log.e("dg", listItem.getUser().getFirstname());
}
};
service.authUser(username, MD5(password), getAuthCallback);
return response;
} catch (RetrofitError e) {
e.printStackTrace();
return null;
}
}
But this is flawed: there is no way of returning the 'UserAuthResponse' from the function? How can i pass back the result?
It seems like i need a synchronous call to the web service but then i'm hit with a 'NetworkOnMainThreadException'
What is the best practice for things like this? Sorry about the poor explanation, struggling to form the right words.
Well the things is that when you're using the Callback as your means of getting the results from Retrofit you automatically giving away the possibility of having the response returned inline. There's a few ways this can be solved. I suppose it's up to you to choose which one fits best with your design.
You could decide to not use the Callback approach and use the inline result from Retrofit but then you'd need to handle the scheduling yourself otherwise you'll hit the Exception of NetworkOnMainThreadException like you mentioned.
You could also pass in a listener to your login method. This listener could then be called by the result Callback. This could be useful if you're trying to hide Retrofit behind some sort of service layer and expose a simple login interface.
interface OnLoginListener {
onLoginSuccessful(UserAuthResponse response);
onLoginFailed(Throwable t);
}
public void Login(String username, String password, final OnLoginListener listener) {
Callback<UserAuthResponse> getAuthCallback = new Callback<UserAuthResponse>() {
#Override
public void failure(RetrofitError e) {
// You can handle Retrofit exception or simply pass them down to the listener as is
listener.onLoginFailed(e);
}
#Override
public void success(UserAuthResponse listItem,
retrofit.client.Response arg1) {
// handle successful case here and pass down the data to the listener
listener.onLoginSucessful(listItem);
}
};
service.authUser(username, MD5(password), getAuthCallback);
}
use this line i Manifest
<uses-permission android:name="android.permission.INTERNET"/>
or use this before network operation (not suggestible)
if (Build.VERSION.SDK_INT>= 10) {
ThreadPolicy tp = ThreadPolicy.LAX;
StrictMode.setThreadPolicy(tp);
}

First connection to userEndpoint takes long time in Android with appEngine

In my android app, after sometime (hour or so.. not something determined) the connection and response to Google-AppEngine takes very long, something like 10 seconds or more.
After the first connection all other enpoint requests are done pretty quickly and this is why I believe this is SW issue and not internet connection issue.
Should I establish a 'dummy' connection as the app is loaded ?
Here is a sample code of an AsyncTask which tried to get User entity from AppEngine endpoint :
private class getUser extends AsyncTask<Void, Void, Boolean> {
long mTaskUserId = Constants.USER_ID_NO_ID_INFDICATOR;
String mIdInPlatform = Constants.USER_ID_NO_ID_INFDICATOR.toString();
Long mServerScore;
Context mContext;
String mUserName;
getUser(String idInPlatform, String userName, Context c) {
mIdInPlatform = idInPlatform;
mUserName = userName;
mContext = c;
}
#Override
protected Boolean doInBackground(Void... params) {
Userendpoint.Builder builder = new Userendpoint.Builder(
AndroidHttp.newCompatibleTransport(), new JacksonFactory(), null);
builder = CloudEndpointUtils.updateBuilder(builder);
Userendpoint endpoint = builder.build();
try {
User user = endpoint.getUser().execute();
} catch (IOException e) {
Log.e(TAG, "Error getting user details from server ", e);
return false;
}
this.mUserName = user.getUserName();
this.mServerScore = user.getScore();
this.mTaskUserId = user.getId();
return true;
}
#Override
protected void onPostExecute(Boolean result) {
if (result) {
setUserFacebookIdInPreferences(mIdInPlatform, mContext);
setUserIdInPreferences(this.mTaskUserId, mContext);
setScoreInPreferences(this.mServerScore, mContext);
setUserNameInPreferences(this.mUserName, mContext);
} else {
Toast.makeText(mContext, R.string.string_login_failed, Toast.LENGTH_SHORT).show();
}
// Restart login activity.
moveToLoginActivity(result);
super.onPostExecute(result);
}
}
Your application in Google App Engine uses two types of server instances: Dynamic instances and Resident instances. The difference is that dynamic instances are created in demand to serve traffic requests. Resident instances are always on.
When traffic stops, all your dynamic instances will shut down to save resources (and help you save money). The first time a request hits the server, a new dynamic instance will spin off to serve that request. The process of starting a new instance might take some time.
This is very likely what you are seeing in your application. To avoid that initial latency you can do two different things:
1) Optimize the time it takes for your code to load up.
2) Set up a Resident instance.
You can find more information on the Google documentation here:
https://developers.google.com/appengine/docs/adminconsole/instances#Introduction_to_Instances
You can warm-up your instances so that they're live before any query hits them - saving you this 10s delay. See documentation at:
https://developers.google.com/appengine/docs/adminconsole/instances#Warmup_Requests

Android not waiting for DB response before finishing statement

I have an interesting problem that I've never run into in programming before. I have an onClickListener that does a lot of username and password checks (makes sure the username is proper length, not taken, etc). I'm using MobDB, and I was using a conditional statement that would return a row if the username already existed. The problem is that the Listener skips the DB and goes to the final check that, if everything works, posts a new username and password to my DB. How can I make it wait for a response from the DB before skipping to the last check?
Here is the relevant code:
usernamecheck3 = true;
MobDB.getInstance().execute(APP_KEY, null, rd, null, false, new MobDBResponseListener() {
#Override public void mobDBSuccessResponse() {
usernamecheck3 = false;
Log.e("mobdbSuccess:", "success");
}
#Override public void mobDBResponse(Vector<HashMap<String, Object[]>> row) {
}
#Override public void mobDBResponse(String jsonObj) {
/*Log.e("mobdbSuccess:", "jsonObj");
Log.e("mobdbSuccess:", jsonObj);
JSONObject mainObject;
try {
mainObject = new JSONObject(jsonObj);
// need to parse the json object.
} catch (JSONException e1) {
e1.printStackTrace();
} */
}
#Override public void mobDBFileResponse(String fileName, byte[] fileData) {
//get file name with extension and file byte array
}
#Override public void mobDBErrorResponse(Integer errValue, String errMsg) {
usernamecheck3 = false;
Log.e("doesnt", "work");
}
});
if(usernamecheck3 == false){
Toast.makeText(getApplicationContext(), "Username is taken, please choose another", Toast.LENGTH_SHORT).show();
}
Basically the check always returns true, and then logcat will say mobdbSuccess: success, which should have set the Bool to false.
Thanks.
MobDBResponseListener is executing on a different thread. What happens here is that the processing is split, while a thread is doing the query, the main thread on which you added the listener, skips right ahead to the validation. Your best bet is to place the validation inside the MobDBResponseListener, on the mobDBResponse method.
Try to debug your code and calls, the Listener may be using an async task. If so, you may do anything you please from the response method, as it will be executing in the main thread again. Otherwise, you should look at solutions that handle threaded execution like Handlers

Categories

Resources