studio - volley android post
Escucha anónima de solicitud de volley que causa pérdida de memoria (4)
Estoy usando una biblioteca de volley para hacer llamadas a servicios web. Hice una clase general para hacer que todos los servicios web llamen y realicen llamadas de servicio desde allí e hice un oyente anónimo para una respuesta exitosa y de error.
Pero cuando uso leak canary, está mostrando pérdida de memoria relacionada con el contexto. A continuación está mi fragmento de código:
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));
}
}
Y creé una interfaz llamada oyente de respuesta para redireccionar las respuestas a la actividad o fragmento. Hice una solicitud de la siguiente manera.
Request.getRequest().sendRequest(Request.SOME URL, SplashScreenActivity.this, SplashScreenActivity.this, new HashMap<String, String>());
Pero estoy frente a la fuga de memoria como:
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
Cualquier idea para eliminar esta fuga se agradece cualquier ayuda.
En general, las clases anónimas tienen una fuerte referencia a la instancia de clase adjunta. En tu caso, eso sería SplashScreenActivity. Ahora supongo que tu Activity
ha terminado antes de que tengas la respuesta de tu servidor a través de Volley. Dado que el oyente hace una fuerte referencia a la Actividad adjunta, esa Actividad no se puede recolectar como basura hasta que la clase Anónimo haya finalizado. Lo que debe hacer es etiquetar todas las solicitudes que está enviando con la instancia Activity, y cancelar todas las solicitudes en la devolución de llamada onDestroy()
de Activity.
stringRequest.setTag(activityInstance);
Para cancelar todas las solicitudes pendientes:
requestQueue.cancellAll(activityInstance);
Además, use el contexto de la aplicación dentro de VolleySingleton para crear RequestQueue.
mRequestQueue = Volley.newRequestQueue(applicationContext);
No use su contexto de actividad allí y no almacene en caché su instancia de actividad dentro de VolleySingleton.
Sé que llegué un poco tarde para unirme a la fiesta, pero hace unos días este problema arruinó mi fin de semana. Para averiguarlo, investigué un poco y finalmente obtuve la solución.
El problema radica en el último objeto de solicitud que se filtró en 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());
}
}
Como puede ver, se crea un nuevo objeto de solicitud antes de que salga de la cola. Esto supera el problema de la pérdida de memoria.
PD: No use Volley del repositorio de Google ya que está en desuso y tiene este error desde entonces. Para usar Volley, ve por esto:
https://github.com/mcxiaoke/android-volley
El repositorio anterior está libre de cualquier pérdida de memoria. Ciao.
Básicamente, el enfoque anónimo es terrible en Android
o en cualquier ClientSideSystem
donde no tienes memoria masiva. Lo que está sucediendo es que ha pasado Context
como parámetro en el método y anonymous
contiene una referencia del mismo. El verdadero desastre viene ahora en la escena cuando el hilo interno hace network call
no pueda terminar su trabajo y antes de eso, la actividad de llamada por alguna razón destruye o recicla, en ese caso GC
no puede recolectar la actividad ya que wokerThread
aún podría estar sosteniendo referencia en él. Por favor revise esto para obtener una descripción detallada.
La solución podría ser clases internas estáticas o clases independientes, en ambos casos use WeakReference
para mantener los recursos y hacer una comprobación nula antes de usarlos.
La ventaja de WeakReference
es que le permitirá a GC
recoger el objeto si nadie más tiene referencia en él.
Tuve un problema similar detectado con LeakCanary donde el mListener de Volley estaba haciendo referencia a mi oyente de respuesta, y mi oyente estaba haciendo referencia a un ImageView, por lo que podría actualizarlo con la imagen descargada.
Hice mi oyente respuesta una clase interna dentro de la actividad ..
private class MyVolleyResponseListener <T> implements com.android.volley.Response.Listener <Bitmap> {
@Override
public void onResponse(Bitmap bitmap) {
thumbNailView.setImageBitmap(bitmap);
}
}
.. y se detuvo y comenzó la cola de solicitud de voley dentro de onDestroy () en la actividad ..
requestQueue.stop();
requestQueue.start();
Esto ha arreglado la fuga.