android - 刷新消息队列以防止基于消息的泄漏。但是要刷新哪个 HandlerThread?

标签 android memory-leaks leakcanary

我基本上有描述的内存泄漏here由 LeakCanary 检测到。

LC stacktrace

我正在尝试通过使用上述帖子中的“管道工修复”来解决此问题,该帖子使用空消息刷新消息队列。每次空闲时提供的代码示例刷新消息队列。在我的对话框被解除后,我只需要刷新一次队列:

public class ExampleDialogFragment extends AppCompatDialogFragment {

    public static ExampleDialogFragment newInstance() {
        return new ExampleDialogFragment();
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(final Bundle savedInstanceState) {
        return new AlertDialog.Builder(getContext())
                .setPositiveButton(android.R.string.ok, (dialog, which) -> onClicked())
                .create();
    }

    private void onClicked() {
        if (getTargetFragment() instanceof Callbacks) {
            ((Callbacks) getTargetFragment()).onButtonClicked();
            flushStackLocalLeaks();
        }
    }

    private static void flushStackLocalLeaks() {
        final Handler handler = new Handler(Looper.myLooper());
        handler.post(() -> Looper.myQueue().addIdleHandler(() -> {
            Timber.d("Flushing on thread %s", Thread.currentThread().getName());
            handler.sendMessageDelayed(handler.obtainMessage(), 1000);
            return false; // we only want to flush once, not *every* 1000 mSec
        }));
    }

    public interface Callbacks {
        void onButtonClicked();
    }
}

我面临的问题是,在 LeakCanary 报告中,泄漏根源处的 HandlerThread 绝不是我期望的线程。有时是 ConnectivityThread , 有时是 HandlerThread , 其中 mName = "queued-work-looper" , 其他是 HandlerThread我的分析库使用。在这些情况下,它都不是主线程。我希望消息会从主线程泄漏。所以
  • 为什么这种泄漏发生在主线程以外的 HandlerThread/ConnectivityThread 上?
  • 我怎么知道哪个 HandlerThread 需要刷新?
  • 最佳答案

    我在 12 月在这里提交了问题:https://issuetracker.google.com/issues/146144484 .

    核心问题是每个空闲的 HandlerThread 都保留了他们最后一次回收的 Message。

    我们当前的解决方法是随着时间的推移找到新的处理程序线程并设置重复刷新:

    import android.os.Handler
    import android.os.HandlerThread
    import android.os.Looper
    import java.util.concurrent.Executors
    import java.util.concurrent.TimeUnit.SECONDS
    
    object AndroidLeaks {
    
      /**
       * HandlerThread instances keep local reference to their last handled message after recycling it.
       * That message is obtained by a dialog which sets on an OnClickListener on it and then never
       * recycles it, expecting it to be garbage collected but it ends up being held by the HandlerThread.
       */
      fun flushHandlerThreads() {
        val executor = Executors.newSingleThreadScheduledExecutor()
    
        val flushedThreadIds = mutableSetOf<Int>()
        // Wait 2 seconds then look for handler threads every 3 seconds.
        executor.scheduleWithFixedDelay({
          val newHandlerThreadsById = findAllHandlerThreads()
              .mapNotNull { thread ->
                val threadId = thread.threadId
                if (threadId == -1 || threadId in flushedThreadIds) {
                  null
                } else {
                  threadId to thread
                }
              }
          flushedThreadIds += newHandlerThreadsById.map { it.first }
          newHandlerThreadsById
              .map { it.second }
              .forEach { handlerThread ->
                var scheduleFlush = true
                val flushHandler = Handler(handlerThread.looper)
                flushHandler.onEachIdle {
                  if (scheduleFlush) {
                    scheduleFlush = false
                    // When the Handler thread becomes idle, we post a message to force it to move.
                    // Source: https://developer.squareup.com/blog/a-small-leak-will-sink-a-great-ship/
                    try {
                      flushHandler.postDelayed({
                        // Right after this postDelayed executes, the idle handler will likely be called
                        // again (if the queue is otherwise empty), so we'll need to schedule a flush
                        // again.
                        scheduleFlush = true
                      }, 1000)
                    } catch (ignored: RuntimeException) {
                      // If the thread is quitting, posting to it will throw. There is no safe and atomic way
                      // to check if a thread is quitting first then post it it.
                    }
                  }
                }
              }
        }, 2, 3, SECONDS)
      }
    
      private fun Handler.onEachIdle(onIdle: () -> Unit) {
        try {
          // Unfortunately Looper.getQueue() is API 23. Looper.myQueue() is API 1.
          // So we have to post to the handler thread to be able to obtain the queue for that
          // thread from within that thread.
          post {
            Looper
                .myQueue()
                .addIdleHandler {
                  onIdle()
                  true
                }
          }
        } catch (ignored: RuntimeException) {
          // If the thread is quitting, posting to it will throw. There is no safe and atomic way
          // to check if a thread is quitting first then post it it.
        }
      }
    
      private fun findAllHandlerThreads(): List<HandlerThread> {
        // Based on https://stackoverflow.com/a/1323480
        var rootGroup = Thread.currentThread().threadGroup!!
        while (rootGroup.parent != null) rootGroup = rootGroup.parent
        var threads = arrayOfNulls<Thread>(rootGroup.activeCount())
        while (rootGroup.enumerate(threads, true) == threads.size) {
          threads = arrayOfNulls(threads.size * 2)
        }
        return threads.mapNotNull { if (it is HandlerThread) it else null }
      }
    }
    

    关于android - 刷新消息队列以防止基于消息的泄漏。但是要刷新哪个 HandlerThread?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55659863/

    相关文章:

    robolectric - 运行 Robolectric 测试时来自 LeakCanary 的 NullPointerException

    android - 如何在 Android Studio Bumblebee 中添加项目级依赖类路径

    android - 如何在不创建新位图的情况下创建带有中心裁剪的部分圆角矩形可绘制对象?

    ios - AVAssetWriter 东西泄露

    android - LoadedApk$ServiceDispatcher.mContext 正在泄漏

    android - LeakCanary DialogFragment 泄漏检测

    java - 将 bundle 从 recyclerview 适配器传递到 Activity

    android - 将 IntelliJ 设置传输到 Android Studio

    页面刷新时 Javascript 内存泄漏;补救?

    c++ - 为什么指针会导致内存泄漏到动态分配的整数?