7

我正在使用抽排库来进行Web服务调用。我做了一个普通的课程,让所有的Web服务调用并从那里进行服务调用,并为成功和错误响应创建匿名监听器。引起内存泄漏的抽象请求的匿名侦听器

但是,当我使用泄漏金丝雀它显示内存泄漏相关的上下文。以下是我的代码片段:

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

而且我创建了一个名为响应侦听器的接口,用于将响应重定向到活动或片段。我提出如下请求。

Request.getRequest().sendRequest(Request.SOME URL, SplashScreenActivity.this, SplashScreenActivity.this, new HashMap<String, String>()); 

但我面对的内存泄漏为:

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 

任何想法,以消除这种泄漏任何帮助表示赞赏。

回答

1

基本上,匿名方法在Android或任何ClientSideSystem可怕,你没有大量的内存。发生的是,你已经通过Context作为参数在方法和anonymous持有它的参考。真正的混乱现在出现在场景中,当线程内部使network call无法完成它的工作,并且在此之前由于某种原因致电活动破坏或回收的情况下,GC无法收集活动,因为wokerThread可能仍然持有参考它。详情请参阅this

该解决方案可以是静态内部类或独立类,在这两种情况下使用WeakReference来保存资源并在使用它们之前进行空检查。

WeakReference的优点是它允许GC收集对象,如果没有其他人如果对其保留引用。

+0

@拿烟塔瓦u能请编辑代码即时消息没有得到如何在现有代码 –

+1

而不是解决这个问题'''新的监听(){ @覆盖 公共无效onResponse(字符串响应){ dismissProgressDialog(上下文) ; try { (responseListener).onResponse(url,response); catch(JsonSyntaxException e){ // Util.showToast(context,context.getResources()。getString(R.string.error)); Crashlytics.logException(e); } } }'''直接创建一个实现这个类的类并传递这个类Object在 –

+0

里我必须创建实现了Listner接口的类,并且必须在那里传递那个类对象。像类ResponseClass实现了Listener,ErrorListener {覆盖public void onResponse(Object response){}覆盖public void onErrorResponse(VolleyError error){}} –

3

通常,匿名类对封闭类实例有强烈的引用。在你的情况下,这将是SplashScreenActivity。现在我猜想,您的Activity已经完成,然后通过Volley从服务器获得响应。由于侦听器对封装Activity有强烈的参考,因此只有在Anonymous类完成之后,才能对该Activity进行垃圾收集。你应该做的是用Activity实例标记你发送的所有请求,并且取消所有在onDestroy()回调Activity的请求。

stringRequest.setTag(activityInstance); 

要取消所有挂起的请求:

requestQueue.cancellAll(activityInstance); 

而且,里面VolleySingleton使用应用程序上下文创建请求队列。

mRequestQueue = Volley.newRequestQueue(applicationContext); 

不要在那里使用你的活动上下文,也不要在你的活动实例中缓存VolleySingleton。

+0

我有这个问题,我试着'cancellAll()'使用标签。但由于某些原因内存泄漏仍在继续。然后我使用'cancellAll()'并传递一个请求过滤器来取消_all_ _requests_。问题消失了。我不知道为什么发生了! – tpk

+0

@Dinesh这是正确的方法,但在活动中注入此代码将使此解决方案与该特定活动紧密结合,并且您需要在进行网络调用的每项活动中执行相同的逻辑。 –

+0

@NayanSrivastava实际上,我不从Activity执行网络调用。我将有查看图层,域图层和数据图层。只有数据层通过Volley发送请求。顺便说一句,setTag接受任何对象stringRequest.setTag(anyObject); – Bob

0

我知道我晚了一点加入派对,但几天后这个问题确实破坏了我的周末。为了弄清楚,我继续研究一下,最终得出了解决方案。

问题在于最后的请求对象在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()); 
      } 
     } 

正如你可以看到一个新的请求对象是从队列中取出之前创建的。这克服了内存泄漏的问题。

P.S:不要在谷歌存储库中使用Volley,因为它已经被弃用,并且从那时起就有了这个错误。为了使用排球,去这样的:

https://github.com/mcxiaoke/android-volley

以上仓库是从任何内存泄漏任何自由。再见。

+0

上面的存储库根据其自述文件也被弃用,我还应该使用它吗? – natinusala

+0

@natinusala使用此 - > https://github.com/google/volley –

+0

@droidster这一个实际上是与泄漏的网络调度程序存储库 – natinusala

1

我有一个与LeakCanary相似的问题,其中Volley的mListener引用了我的响应监听器,而我的监听器引用了一个ImageView,因此它可以用下载的图像更新它。

我在我的响应听者在活动范围内的内部类..

private class MyVolleyResponseListener <T> implements com.android.volley.Response.Listener <Bitmap> { 

     @Override 
     public void onResponse(Bitmap bitmap) { 
      thumbNailView.setImageBitmap(bitmap); 
     } 
} 

..和停止和启动内部的onDestroy()在活动凌空请求队列..

requestQueue.stop(); 
requestQueue.start(); 

这已修复了泄漏。