【问题标题】:Fix activity leak when listener keeps implicit reference当监听器保持隐式引用时修复活动泄漏
【发布时间】:2020-04-24 14:53:15
【问题描述】:

MessageFeedActivity onCreate 方法中,它通过调用 CTFeedAPI 类的 getMessageTypes 方法来加载提要。

public class MessageFeedActivity extends AppCompatActivity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  //Setting the listener
  CTFeedAPI ctFeedAPI = new CTFeedAPI(new CTFeedAPI.CTFeedAPIListener() {
   @Override
   public void feedAPISuccessListener(Object object) {
    // Handle Success
   }

   @Override
   public void feedAPIErrorListener(int error) {
    // Handle Error
   }
  });

  ctFeedAPI.getMessageTypes();
 }

 @Override
 protected void onDestroy() {
  super.onDestroy();
 }
}

并等待 CTFeedAPIListener 响应。 CTFeedAPI 类通过调用 NetworkRequest 类的 performRequest 方法发出网络请求

public class CTFeedAPI implements NetworkListener {
 private CTFeedAPIListener apiListener;

 public CTFeedAPI(CTFeedAPIListener feedAPIListener) {
  apiListener = feedAPIListener;
 }

 public void getMessageTypes() {
  Map < String, String > params = new HashMap < > ();
  params.put("f", "GetMessageTypes");

  NetworkRequest networkRequest = new NetworkRequest(this);
  networkRequest.performRequest();
 }

 public interface CTFeedAPIListener {
  void feedAPISuccessListener(Object object);

  void feedAPIErrorListener(int error);
 }
}

并等待 NetworkListener 响应

public class NetworkRequest {
 private NetworkListener mListener;

 public interface NetworkListener {

  void networkReqSuccessListener(String cacheKey, String tag, String response);

  void networkReqErrorListener(String tag, int error);
 }
 public NetworkRequest(NetworkListener listener) {
  this.mListener = listener;
 }

 public void performRequest(
  // Perform Network Requests and respond as
  if (mListener != null) {
   if (success) {
    mListener.networkReqSuccessListener(getUrl(), getTag(), response);
   } else {
    mListener.networkReqErrorListener(getTag(), err_msg);
   }
  }
 }

当用户按下返回键时,在销毁MessageFeedActivity之前,系统调用'onDestroy'方法。不幸的是,由于后台线程(NetworkRequest 类中的 performRequest 方法)仍然保留对它的引用,所以会发生泄漏。

那么如何在 MessageFeedActivity 中实现 CTFeedAPIListener 引用来消除泄漏。

【问题讨论】:

    标签: java android android-activity memory-leaks listener


    【解决方案1】:

    在这种设计中,您不仅会泄漏内存,而且您的代码也会高度耦合并且很难测试;容易出现难以检测的错误。我建议你实现 MVP 或类似的架构。您的活动永远不应该知道有关您的网络层的任何信息。添加一个 Presenter 层,负责代表您的活动请求某些内容并使用界面来更新您的活动。您的 Presenter 应该访问从存储库层的响应映射的业务实体,该实体负责网络或 Db 访问并将值返回给客户端 Presenter。这样,您的演示者和业务逻辑层将被解耦并且易于独立测试。将来如果业务需求发生变化,您的更改不会影响其他层。请参阅this article for more information on the subject

    【讨论】:

    • 感谢回复,实际上 CTFeedAPI 类有与服务器调用 Feeds 相关的方法,而 NetworkRequest 有使用网络库 volley 调用的方法。这就是我创建两个类的原因。
    • 正如您所说的使用接口来更新您的活动。* *,我想我已经使用 **CTFeedAPIListener 与活动链接。
    【解决方案2】:

    弱引用对象,不会阻止它们的引用对象 被最终化,最终化,然后被回收。弱引用 最常用于实现规范化映射。

    假设垃圾收集器在某个时间点确定 对象弱可达的时间。到时候就会 原子地清除对该对象的所有弱引用和所有弱引用 对任何其他弱可达对象的引用 可以通过一系列强引用和软引用访问对象。在 同时它会声明所有以前的weakly-reachable 要最终确定的对象。在同一时间或稍后的某个时间 将注册的那些新清除的弱引用排入队列 带有参考队列。

    你可以使用弱引用:

    import java.lang.ref.WeakReference;
    
    public class NetworkRequest {
     public interface NetworkListener {
          void networkReqSuccessListener(String cacheKey, String tag, String response);
          void networkReqErrorListener(String tag, int error);
     }
    
     private WeakReference<NetworkListener> mListener;
    
     public NetworkRequest(NetworkListener listener) {
          this.mListener = new WeakReference<NetworkListener>(listener);
     }
    
     public void performRequest(){
      // Perform Network Requests and respond as
      NetworkListener listener = mListener.get();
      if (listener != null) {
         if (success) listener.networkReqSuccessListener(getUrl(), getTag(), response);
         else listener.networkReqErrorListener(getTag(), err_msg);
       }
      }
    }
    

    public class CTFeedAPI implements NetworkListener {
     private WeakReference<CTFeedAPIListener> apiListener;
    
     public CTFeedAPI(CTFeedAPIListener feedAPIListener) {
      apiListener = new WeakReference<>(feedAPIListener);
     }
    
     public void getMessageTypes() {
      Map < String, String > params = new HashMap < > ();
      params.put("f", "GetMessageTypes");
    
      NetworkRequest networkRequest = new NetworkRequest(this);
      networkRequest.performRequest();
     }
    
     public interface CTFeedAPIListener {
      void feedAPISuccessListener(Object object);
    
      void feedAPIErrorListener(int error);
     }
    }
    

    CTFeedAPICTFeedAPIListener 保存为MessageFeedActivity 的实例变量,以防止GC 在活动出现时收集它们:

    public class MessageFeedActivity extends AppCompatActivity {
    
    private CTFeedAPI ctFeedAPI = null;// keeping a reference to CTFeedAPI
    private CTFeedAPIListener listener = null;// keeping a reference to listener
     @Override
     protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
    
      //Setting the listener
      listener = new CTFeedAPI.CTFeedAPIListener() {
       @Override
       public void feedAPISuccessListener(Object object) {
        // Handle Success
       }
    
       @Override
       public void feedAPIErrorListener(int error) {
        // Handle Error
       }
      });
      ctFeedAPI = new CTFeedAPI(listener);
      ctFeedAPI.getMessageTypes();
     }
    

    【讨论】:

    • 如前所述,如果我按照您的建议使用 Wea​​kReference,NetworkRequest 类用于在后台线程中加载网络请求并在执行请求时,NetworkListener 在网络响应时为空,因为 CTFeedAPI 类删除了 NetworkRequest 类的引用。
    • @GiruBhai 将CTFeedAPI 保存为MessageFeedActivity 的实例变量,以防止GC 在活动出现时收集监听器。
    • 感谢回复,但是如果我在循环中调用getMessageTypes或者在Rec​​yclerView Adapter的onBindViewHolder中调用getMessageTypes怎么办?
    • @GiruBhai 在循环或 onBindViewHolder 中调用 getMessageTypes 不需要额外的工作,因为当活动被销毁时,来自 LISTENER TO ACTIVITY 的所有引用都很弱,活动将被 GC 收集。跨度>
    • 在 onBindViewHolder 如何设置实例变量?
    猜你喜欢
    • 2016-04-19
    • 1970-01-01
    • 2013-05-04
    • 2018-05-03
    • 1970-01-01
    • 1970-01-01
    • 2013-04-09
    • 1970-01-01
    相关资源
    最近更新 更多