#android #ipc #android-binder
#Android #ipc #android-binder
Вопрос:
Я думаю, что я отследил утечку памяти и хочу подтвердить, что, по моему мнению, может быть правдой о том, как реализован связующий Android. В этом случае у меня есть сервис и действие, каждое в своем собственном процессе. Я создал AIDL, который позволяет мне передавать объект обратного вызова из действия в службу через метод ipc, а затем вызывать обратный вызов, когда служба выполняется с запрошенной задачей.
Долгое время мне было интересно: если я передаю новый объект обратного вызова службе, и я не сохраняю указатель на объект обратного вызова в своей деятельности, почему сборщик мусора просто не выполняет и не собирает обратный вызов в процессе моей деятельности?Поскольку этого, похоже, не происходит, как JVM узнает, когда собирать мусор для обратного вызова в моей деятельности.
Я думаю, что ответ заключается в том, что система Binder сохраняет указатель на мой обратный вызов в процессе Activity до тех пор, пока соответствующий объект обратного вызова в процессе Service не вызовет свой метод finalize(), который затем отправляет сообщение Activity для освобождения указателя. Это правильно? Если нет, то как это работает?
Я считаю, что это так, и это приводит к интересной ситуации, когда, если обратный вызов в Activity указывает на что-то очень ресурсоемкое, оно не будет собрано до тех пор, пока не будет собран обратный вызов в службе. Если службе не хватает памяти, она может не собирать обратный вызов в течение длительного времени, и обратные вызовы могут просто накапливаться в Activity, пока в Activity не появится OutOfMemoryError.
Ответ №1:
Юрий в значительной степени прав.
Моя служба запускает поток, который содержит обратный вызов, и когда поток завершает свою работу, он вызывает обратный вызов, и поток заканчивается. Когда вызывается обратный вызов, он может выполнить небольшую часть работы в моей деятельности, а затем вернуться, и в этот момент у меня нет указателей в процессе моей деятельности на обратный вызов.
Однако система binder Android будет продолжать указывать на объект обратного вызова в Activity до тех пор, пока соответствующий объект обратного вызова в службе не будет собран мусором.
Если объект обратного вызова в процессе Activity доминирует над некоторыми другими объектами, которые потребляют много памяти, тогда я трачу память в процессе Activity без уважительной причины и даже могу получить OutOfMemoryError. Решение состоит в том, чтобы создать простой метод в моем классе обратного вызова, вызываемый destory()
для обнуления всех полей обратного вызова и вызова этого метода, когда я закончу с обратным вызовом.
Если класс обратного вызова является нестатическим внутренним классом, вы можете рассмотреть возможность изменения его на статический внутренний класс и передачи родительского класса в конструкторе, таким образом, вы также можете обнулить его в destory()
методе.
Это наводит на интересную мысль, если родительский класс нестатического внутреннего класса обратного вызова является Activity и происходит изменение конфигурации (например, поворот экрана) после отправки обратного вызова через binder, но до его обратного вызова, тогда обратный вызов будет указывать на старый объект Activity, когдаон выполняется!
Обновление: я обнаружил этот код внутри Binder.java конечно, он отключен, но было бы неплохо, если бы они упоминали подобные вещи в Javadocs.
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Binder> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) amp;amp;
(klass.getModifiers() amp; Modifier.STATIC) == 0) {
Log.w(TAG, "The following Binder class should be static or leaks might occur: "
klass.getCanonicalName());
}
}
Ответ №2:
Если я правильно понимаю, как работает Binder, проблема в вашем случае заключается в следующем. Для каждого входящего входящего вызова ваша служба создает отдельный поток. Когда вы передаете объект в этот поток, ваша связующая система создает локальную копию вашего объекта для потока. Таким образом, пока ваш метод обслуживания не вернет результат, поток с копией объекта продолжает работать.
Чтобы проверить это, просто попробуйте просмотреть потоки вашего процесса обслуживания (в DDMS).